Skip to main content

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::Arc;
7use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
8
9use app_units::Au;
10use atomic_refcell::{AtomicRef, AtomicRefCell};
11use euclid::Point2D;
12use layout_api::LayoutDamage;
13use malloc_size_of_derive::MallocSizeOf;
14use servo_arc::Arc as ServoArc;
15use style::computed_values::position::T as Position;
16use style::logical_geometry::WritingMode;
17use style::properties::ComputedValues;
18use style::values::specified::align::AlignFlags;
19use style_traits::CSSPixel;
20
21use crate::context::LayoutContext;
22use crate::dom::{LayoutBox, WeakLayoutBox};
23use crate::flow::CollapsibleWithParentStartMargin;
24use crate::formatting_contexts::Baselines;
25use crate::fragment_tree::{
26    BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, Fragment, FragmentStatus,
27    SpecificLayoutInfo,
28};
29use crate::geom::LogicalSides1D;
30use crate::positioned::{PositioningContext, relative_adjustement};
31use crate::sizing::{ComputeInlineContentSizes, InlineContentSizesResult, SizeConstraint};
32use crate::traversal::ElementDamageSet;
33use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize};
34
35/// A box tree node that handles containing information about style and the original DOM
36/// node or pseudo-element that it is based on. This also handles caching of layout values
37/// such as the inline content sizes to avoid recalculating these values during layout
38/// passes.
39///
40/// In the future, this will hold layout results to support incremental layout.
41#[derive(MallocSizeOf)]
42pub(crate) struct LayoutBoxBase {
43    pub base_fragment_info: BaseFragmentInfo,
44    pub style: ServoArc<ComputedValues>,
45    pub cached_inline_content_size:
46        AtomicRefCell<Option<Box<(SizeConstraint, InlineContentSizesResult)>>>,
47    pub outer_inline_content_sizes_depend_on_content: AtomicBool,
48
49    /// The cached layout results for this [`LayoutBoxBase`]. These are either cached
50    /// independent formatting context results or a cached block layout for use within
51    /// a block flow.
52    cached_layout_result: AtomicRefCell<Option<LayoutResultAndInputs>>,
53
54    /// Whether or not the cached layout result for this [`LayoutBoxBase`] is dirty.
55    /// This flag is used to preserve the cache when it can be used to do a faster
56    /// layout, but cannot be reused directly.
57    cached_layout_result_dirty: AtomicBool,
58
59    /// A count of the number of boxes are in this box's subtree (including itself).
60    /// This is used as a heuristic to know when to perform parallel layout.
61    subtree_size: AtomicUsize,
62
63    pub fragments: AtomicRefCell<Vec<Fragment>>,
64    pub parent_box: Option<WeakLayoutBox>,
65}
66
67impl LayoutBoxBase {
68    pub(crate) fn new(
69        base_fragment_info: BaseFragmentInfo,
70        style: ServoArc<ComputedValues>,
71    ) -> Self {
72        Self {
73            base_fragment_info,
74            style,
75            cached_inline_content_size: AtomicRefCell::default(),
76            outer_inline_content_sizes_depend_on_content: AtomicBool::new(true),
77            cached_layout_result: AtomicRefCell::default(),
78            cached_layout_result_dirty: AtomicBool::default(),
79            subtree_size: AtomicUsize::default(),
80            fragments: AtomicRefCell::default(),
81            parent_box: None,
82        }
83    }
84
85    /// Set the subtree size on this [`LayoutBoxBase`]. This should be done once
86    /// box construction knows how many boxes are in this box's subtree.
87    pub(crate) fn set_subtree_size(&self, size: usize) {
88        self.subtree_size.store(size, Ordering::Relaxed);
89    }
90
91    pub(crate) fn subtree_size(&self) -> usize {
92        self.subtree_size.load(Ordering::Relaxed)
93    }
94
95    /// Get the inline content sizes of a box tree node that extends this [`LayoutBoxBase`], fetch
96    /// the result from a cache when possible.
97    pub(crate) fn inline_content_sizes(
98        &self,
99        layout_context: &LayoutContext,
100        constraint_space: &ConstraintSpace,
101        layout_box: &impl ComputeInlineContentSizes,
102    ) -> InlineContentSizesResult {
103        let mut cache = self.cached_inline_content_size.borrow_mut();
104        if let Some(cached_inline_content_size) = cache.as_ref() {
105            let (previous_cb_block_size, result) = **cached_inline_content_size;
106            if !result.depends_on_block_constraints ||
107                previous_cb_block_size == constraint_space.block_size
108            {
109                return result;
110            }
111            // TODO: Should we keep multiple caches for various block sizes?
112        }
113
114        let result =
115            layout_box.compute_inline_content_sizes_with_fixup(layout_context, constraint_space);
116        *cache = Some(Box::new((constraint_space.block_size, result)));
117        result
118    }
119
120    pub(crate) fn fragments(&self) -> AtomicRef<'_, Vec<Fragment>> {
121        self.fragments.borrow()
122    }
123
124    pub(crate) fn add_fragment(&self, fragment: Fragment) {
125        self.fragments.borrow_mut().push(fragment);
126    }
127
128    pub(crate) fn set_fragment(&self, fragment: Fragment) {
129        *self.fragments.borrow_mut() = vec![fragment];
130    }
131
132    pub(crate) fn clear_fragments(&self) {
133        self.fragments.borrow_mut().clear();
134    }
135
136    /// Clear all resulting fragments and dirty and fragment caches. Resulting fragments are
137    /// used for layout queries and fragment caches are used for incremental layout.
138    pub(crate) fn clear_fragments_and_dirty_fragment_cache(&self) {
139        self.clear_fragments();
140        self.cached_layout_result_dirty
141            .store(true, Ordering::Relaxed);
142    }
143
144    pub(crate) fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) {
145        self.style = new_style.clone();
146        for fragment in self.fragments.borrow_mut().iter_mut() {
147            if let Some(base) = fragment.base() {
148                base.repair_style(new_style);
149            }
150        }
151    }
152
153    #[expect(unused)]
154    pub(crate) fn parent_box(&self) -> Option<LayoutBox> {
155        self.parent_box.as_ref().and_then(WeakLayoutBox::upgrade)
156    }
157
158    /// Clear fragment layout caches on this base, depending on upward flowing damage, but
159    /// *do not* clear its resulting fragment. The layout cache itself is always cleared,
160    /// but the inline content size cache is cleared conditionally.
161    ///
162    /// Returns true is this [`LayoutBoxBase`] propagates `RecomputeInlineContentSizes`
163    /// and false otherwise.
164    pub(crate) fn invalidate_caches(&self, damage_set: &ElementDamageSet) -> bool {
165        self.cached_layout_result_dirty
166            .store(true, Ordering::Relaxed);
167        if !damage_set.on_element.is_empty() ||
168            damage_set
169                .from_children
170                .contains(LayoutDamage::RecomputeInlineContentSizes)
171        {
172            *self.cached_inline_content_size.borrow_mut() = None;
173        }
174
175        // When a block container has a mix of inline-level and block-level contents, the
176        // inline-level ones are wrapped inside an anonymous block associated with the
177        // block container. The anonymous block has an `auto` size, so its intrinsic
178        // contribution depends on content, but it can't affect the intrinsic size of
179        // ancestors if the block container is sized extrinsically.
180        //
181        // If the intrinsic contributions of this node depend on content, we will need to
182        // clear the cached intrinsic sizes of the parent. But if the contributions are
183        // purely extrinsic, then the intrinsic sizes of the ancestors won't be affected,
184        // and we can keep the cache.
185        !self.base_fragment_info.is_anonymous() &&
186            self.outer_inline_content_sizes_depend_on_content
187                .load(Ordering::Relaxed)
188    }
189
190    /// Clear fragment layout caches on this base, depending on upward flowing damage, and
191    /// also clear its resulting fragment. The layout cache itself is always cleared, but
192    /// the inline content size cache is cleared conditionally.
193    ///
194    /// Returns true is this [`LayoutBoxBase`] propagates `RecomputeInlineContentSizes`
195    /// and false otherwise.
196    pub(crate) fn invalidate_caches_for_fragment_tree_layout(
197        &self,
198        damage_set: &ElementDamageSet,
199    ) -> bool {
200        self.clear_fragments();
201        self.invalidate_caches(damage_set)
202    }
203
204    pub(crate) fn cached_independent_formatting_context_layout_if_applicable(
205        &self,
206        positioning_context: &mut PositioningContext,
207        containing_block_for_children: &ContainingBlock<'_>,
208    ) -> Option<IndependentFormattingContextLayoutResult> {
209        if self.cached_layout_result_dirty.load(Ordering::Relaxed) {
210            return None;
211        }
212
213        let cache = self.cached_layout_result.borrow();
214        let Some(LayoutResultAndInputs::IndependentFormattingContext(cache)) = &*cache else {
215            return None;
216        };
217
218        let cache = &**cache;
219        if cache.containing_block_for_children_size.inline !=
220            containing_block_for_children.size.inline
221        {
222            return None;
223        }
224        if cache.containing_block_for_children_size.block !=
225            containing_block_for_children.size.block &&
226            cache.result.depends_on_block_constraints
227        {
228            return None;
229        }
230
231        positioning_context.append(cache.positioning_context.clone());
232        Some(cache.result.clone())
233    }
234
235    pub(crate) fn cache_independent_formatting_context_layout(
236        &self,
237        containing_block_for_children: &ContainingBlock<'_>,
238        child_positioning_context: &PositioningContext,
239        result: &IndependentFormattingContextLayoutResult,
240    ) {
241        self.cached_layout_result_dirty
242            .store(false, Ordering::Relaxed);
243        *self.cached_layout_result.borrow_mut() =
244            Some(LayoutResultAndInputs::IndependentFormattingContext(
245                Box::new(IndependentFormattingContextLayoutResultAndInputs {
246                    result: result.clone(),
247                    positioning_context: child_positioning_context.clone(),
248                    containing_block_for_children_size: containing_block_for_children.size.clone(),
249                }),
250            ));
251    }
252
253    pub(crate) fn cached_same_formatting_context_block_if_applicable(
254        &self,
255        containing_block: &ContainingBlock,
256        collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>,
257        ignore_block_margins_for_stretch: LogicalSides1D<bool>,
258        has_inline_parent: bool,
259    ) -> Option<Arc<BoxFragment>> {
260        if self.cached_layout_result_dirty.load(Ordering::Relaxed) {
261            return None;
262        }
263
264        let mut cached_layout_result = self.cached_layout_result.borrow_mut();
265        let Some(LayoutResultAndInputs::SameFormattingContextBlock(result)) =
266            &mut *cached_layout_result
267        else {
268            return None;
269        };
270
271        if result.containing_block_size != containing_block.size ||
272            result.containing_block_writing_mode != containing_block.style.writing_mode ||
273            result.containing_block_justify_items !=
274                containing_block.style.clone_justify_items().computed.0.0 ||
275            result.collapsible_with_parent_start_margin != collapsible_with_parent_start_margin ||
276            result.ignore_block_margins_for_stretch != ignore_block_margins_for_stretch ||
277            result.has_inline_parent != has_inline_parent
278        {
279            return None;
280        }
281
282        let fragment = result.result.fragment.clone();
283        {
284            let mut origin = result.result.original_offset;
285            if self.style.clone_position() == Position::Relative {
286                origin += relative_adjustement(&self.style, containing_block)
287                    .to_physical_vector(containing_block.style.writing_mode)
288            }
289            fragment.base.set_rect_origin(origin);
290        }
291
292        Some(fragment)
293    }
294
295    pub(crate) fn cache_same_formatting_context_block_layout(
296        &self,
297        containing_block: &ContainingBlock,
298        collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>,
299        ignore_block_margins_for_stretch: LogicalSides1D<bool>,
300        has_inline_parent: bool,
301        fragment: Arc<BoxFragment>,
302    ) {
303        let mut original_offset;
304        {
305            original_offset = fragment.content_rect().origin;
306            if self.style.clone_position() == Position::Relative {
307                original_offset -= relative_adjustement(&self.style, containing_block)
308                    .to_physical_vector(containing_block.style.writing_mode)
309            }
310        }
311
312        self.cached_layout_result_dirty
313            .store(false, Ordering::Relaxed);
314        *self.cached_layout_result.borrow_mut() =
315            Some(LayoutResultAndInputs::SameFormattingContextBlock(Box::new(
316                SameFormattingContextBlockLayoutResultAndInputs {
317                    result: SameFormattingContextBlockLayoutResult {
318                        fragment,
319                        original_offset,
320                    },
321                    containing_block_size: containing_block.size.clone(),
322                    containing_block_writing_mode: containing_block.style.writing_mode,
323                    containing_block_justify_items: containing_block
324                        .style
325                        .clone_justify_items()
326                        .computed
327                        .0
328                        .0,
329                    collapsible_with_parent_start_margin,
330                    ignore_block_margins_for_stretch,
331                    has_inline_parent,
332                },
333            )));
334    }
335
336    pub(crate) fn clear_scrollable_overflow_all_on_fragments(&self) {
337        for fragment in self.fragments.borrow().iter() {
338            fragment.clear_scrollable_overflow();
339        }
340    }
341
342    pub(crate) fn mark_fragments_as_descendants_changed(&self) {
343        for fragment in self.fragments.borrow().iter() {
344            if let Some(base) = fragment.base() {
345                base.set_status(FragmentStatus::OnlyDescendantsChanged);
346            }
347        }
348    }
349}
350
351impl Debug for LayoutBoxBase {
352    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
353        f.debug_struct("LayoutBoxBase").finish()
354    }
355}
356
357#[derive(MallocSizeOf)]
358pub(crate) enum LayoutResultAndInputs {
359    IndependentFormattingContext(Box<IndependentFormattingContextLayoutResultAndInputs>),
360    SameFormattingContextBlock(Box<SameFormattingContextBlockLayoutResultAndInputs>),
361}
362
363#[derive(Clone, MallocSizeOf)]
364pub(crate) struct IndependentFormattingContextLayoutResult {
365    pub fragments: Vec<Fragment>,
366
367    /// <https://drafts.csswg.org/css2/visudet.html#root-height>
368    pub content_block_size: Au,
369
370    /// If this layout is for a block container, this tracks the collapsable size
371    /// of start and end margins and whether or not the block container collapsed through.
372    pub collapsible_margins_in_children: CollapsedBlockMargins,
373
374    /// The contents of a table may force it to become wider than what we would expect
375    /// from 'width' and 'min-width'. This is the resulting inline content size,
376    /// or None for non-table layouts.
377    pub content_inline_size_for_table: Option<Au>,
378
379    /// The offset of the last inflow baseline of this layout in the content area, if
380    /// there was one. This is used to propagate baselines to the ancestors of `display:
381    /// inline-block`.
382    pub baselines: Baselines,
383
384    /// Whether or not this layout depends on the containing block size.
385    pub depends_on_block_constraints: bool,
386
387    /// Additional information of this layout that could be used by Javascripts and devtools.
388    pub specific_layout_info: Option<SpecificLayoutInfo>,
389}
390
391/// A collection of layout inputs and a cached layout result for an IndependentFormattingContext for
392/// use in [`LayoutBoxBase`].
393#[derive(MallocSizeOf)]
394pub(crate) struct IndependentFormattingContextLayoutResultAndInputs {
395    /// The [`IndependentFormattingContextLayoutResult`] for this layout.
396    pub result: IndependentFormattingContextLayoutResult,
397
398    /// The [`ContainingBlockSize`] to use for this box's contents, but not
399    /// for the box itself.
400    pub containing_block_for_children_size: ContainingBlockSize,
401
402    /// A [`PositioningContext`] holding absolutely-positioned descendants
403    /// collected during the layout of this box.
404    pub positioning_context: PositioningContext,
405}
406
407#[derive(Clone, MallocSizeOf)]
408pub(crate) struct SameFormattingContextBlockLayoutResult {
409    #[conditional_malloc_size_of]
410    pub fragment: Arc<BoxFragment>,
411    original_offset: Point2D<Au, CSSPixel>,
412}
413
414/// A collection of layout inputs and a cached layout result for a SameFormattingContextBlock for
415/// use in [`LayoutBoxBase`].
416#[derive(MallocSizeOf)]
417pub(crate) struct SameFormattingContextBlockLayoutResultAndInputs {
418    pub result: SameFormattingContextBlockLayoutResult,
419    /// The [`ContainingBlockSize`] used when this block was laid out.
420    pub containing_block_size: ContainingBlockSize,
421    /// The containing block's [`WritingMode`]  used when this block was laid out.
422    pub containing_block_writing_mode: WritingMode,
423    /// The containing block's `justify-items` [`AlignFlags`] used when this block was laid out.
424    pub containing_block_justify_items: AlignFlags,
425    /// Whether or not the margin in this block was collapsible with the parent's start margin
426    /// when this block was laid out.
427    collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>,
428    /// Whether or not block margins were ignored for stretch when this block was laid out.
429    ignore_block_margins_for_stretch: LogicalSides1D<bool>,
430    /// Whether or not this block had an inline parent.
431    has_inline_parent: bool,
432}