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().iter() {
147            fragment.repair_style(new_style);
148        }
149    }
150
151    #[expect(unused)]
152    pub(crate) fn parent_box(&self) -> Option<LayoutBox> {
153        self.parent_box.as_ref().and_then(WeakLayoutBox::upgrade)
154    }
155
156    /// Clear fragment layout caches on this base, depending on upward flowing damage, but
157    /// *do not* clear its resulting fragment. The layout cache itself is always cleared,
158    /// but the inline content size cache is cleared conditionally.
159    ///
160    /// Returns true is this [`LayoutBoxBase`] propagates `RecomputeInlineContentSizes`
161    /// and false otherwise.
162    pub(crate) fn invalidate_caches(&self, damage_set: &ElementDamageSet) -> bool {
163        self.cached_layout_result_dirty
164            .store(true, Ordering::Relaxed);
165        if !damage_set.on_element.is_empty() ||
166            damage_set
167                .from_children
168                .contains(LayoutDamage::RecomputeInlineContentSizes)
169        {
170            *self.cached_inline_content_size.borrow_mut() = None;
171        }
172
173        // When a block container has a mix of inline-level and block-level contents, the
174        // inline-level ones are wrapped inside an anonymous block associated with the
175        // block container. The anonymous block has an `auto` size, so its intrinsic
176        // contribution depends on content, but it can't affect the intrinsic size of
177        // ancestors if the block container is sized extrinsically.
178        //
179        // If the intrinsic contributions of this node depend on content, we will need to
180        // clear the cached intrinsic sizes of the parent. But if the contributions are
181        // purely extrinsic, then the intrinsic sizes of the ancestors won't be affected,
182        // and we can keep the cache.
183        !self.base_fragment_info.is_anonymous() &&
184            self.outer_inline_content_sizes_depend_on_content
185                .load(Ordering::Relaxed)
186    }
187
188    /// Clear fragment layout caches on this base, depending on upward flowing damage, and
189    /// also clear its resulting fragment. The layout cache itself is always cleared, but
190    /// the inline content size cache is cleared conditionally.
191    ///
192    /// Returns true is this [`LayoutBoxBase`] propagates `RecomputeInlineContentSizes`
193    /// and false otherwise.
194    pub(crate) fn invalidate_caches_for_fragment_tree_layout(
195        &self,
196        damage_set: &ElementDamageSet,
197    ) -> bool {
198        self.clear_fragments();
199        self.invalidate_caches(damage_set)
200    }
201
202    pub(crate) fn cached_independent_formatting_context_layout_if_applicable(
203        &self,
204        positioning_context: &mut PositioningContext,
205        containing_block_for_children: &ContainingBlock<'_>,
206    ) -> Option<IndependentFormattingContextLayoutResult> {
207        if self.cached_layout_result_dirty.load(Ordering::Relaxed) {
208            return None;
209        }
210
211        let cache = self.cached_layout_result.borrow();
212        let Some(LayoutResultAndInputs::IndependentFormattingContext(cache)) = &*cache else {
213            return None;
214        };
215
216        let cache = &**cache;
217        if cache.containing_block_for_children_size.inline !=
218            containing_block_for_children.size.inline
219        {
220            return None;
221        }
222        if cache.containing_block_for_children_size.block !=
223            containing_block_for_children.size.block &&
224            cache.result.depends_on_block_constraints
225        {
226            return None;
227        }
228
229        positioning_context.append(cache.positioning_context.clone());
230        Some(cache.result.clone())
231    }
232
233    pub(crate) fn cache_independent_formatting_context_layout(
234        &self,
235        containing_block_for_children: &ContainingBlock<'_>,
236        child_positioning_context: &PositioningContext,
237        result: &IndependentFormattingContextLayoutResult,
238    ) {
239        self.cached_layout_result_dirty
240            .store(false, Ordering::Relaxed);
241        *self.cached_layout_result.borrow_mut() =
242            Some(LayoutResultAndInputs::IndependentFormattingContext(
243                Box::new(IndependentFormattingContextLayoutResultAndInputs {
244                    result: result.clone(),
245                    positioning_context: child_positioning_context.clone(),
246                    containing_block_for_children_size: containing_block_for_children.size.clone(),
247                }),
248            ));
249    }
250
251    pub(crate) fn cached_same_formatting_context_block_if_applicable(
252        &self,
253        containing_block: &ContainingBlock,
254        collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>,
255        ignore_block_margins_for_stretch: LogicalSides1D<bool>,
256        has_inline_parent: bool,
257    ) -> Option<Arc<BoxFragment>> {
258        if self.cached_layout_result_dirty.load(Ordering::Relaxed) {
259            return None;
260        }
261
262        let mut cached_layout_result = self.cached_layout_result.borrow_mut();
263        let Some(LayoutResultAndInputs::SameFormattingContextBlock(result)) =
264            &mut *cached_layout_result
265        else {
266            return None;
267        };
268
269        if result.containing_block_size != containing_block.size ||
270            result.containing_block_writing_mode != containing_block.style.writing_mode ||
271            result.containing_block_justify_items !=
272                containing_block.style.clone_justify_items().computed.0.0 ||
273            result.collapsible_with_parent_start_margin != collapsible_with_parent_start_margin ||
274            result.ignore_block_margins_for_stretch != ignore_block_margins_for_stretch ||
275            result.has_inline_parent != has_inline_parent
276        {
277            return None;
278        }
279
280        let fragment = result.result.fragment.clone();
281        {
282            let mut origin = result.result.original_offset;
283            if self.style.clone_position() == Position::Relative {
284                origin += relative_adjustement(&self.style, containing_block)
285                    .to_physical_vector(containing_block.style.writing_mode)
286            }
287            fragment.base.set_rect_origin(origin);
288        }
289
290        Some(fragment)
291    }
292
293    pub(crate) fn cache_same_formatting_context_block_layout(
294        &self,
295        containing_block: &ContainingBlock,
296        collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>,
297        ignore_block_margins_for_stretch: LogicalSides1D<bool>,
298        has_inline_parent: bool,
299        fragment: Arc<BoxFragment>,
300    ) {
301        let mut original_offset;
302        {
303            original_offset = fragment.content_rect().origin;
304            if self.style.clone_position() == Position::Relative {
305                original_offset -= relative_adjustement(&self.style, containing_block)
306                    .to_physical_vector(containing_block.style.writing_mode)
307            }
308        }
309
310        self.cached_layout_result_dirty
311            .store(false, Ordering::Relaxed);
312        *self.cached_layout_result.borrow_mut() =
313            Some(LayoutResultAndInputs::SameFormattingContextBlock(Box::new(
314                SameFormattingContextBlockLayoutResultAndInputs {
315                    result: SameFormattingContextBlockLayoutResult {
316                        fragment,
317                        original_offset,
318                    },
319                    containing_block_size: containing_block.size.clone(),
320                    containing_block_writing_mode: containing_block.style.writing_mode,
321                    containing_block_justify_items: containing_block
322                        .style
323                        .clone_justify_items()
324                        .computed
325                        .0
326                        .0,
327                    collapsible_with_parent_start_margin,
328                    ignore_block_margins_for_stretch,
329                    has_inline_parent,
330                },
331            )));
332    }
333
334    pub(crate) fn clear_scrollable_overflow_all_on_fragments(&self) {
335        for fragment in self.fragments.borrow().iter() {
336            fragment.clear_scrollable_overflow();
337        }
338    }
339
340    pub(crate) fn mark_fragments_as_descendants_changed(&self) {
341        for fragment in self.fragments.borrow().iter() {
342            if let Some(base) = fragment.base() {
343                base.set_status(FragmentStatus::OnlyDescendantsChanged);
344            }
345        }
346    }
347}
348
349impl Debug for LayoutBoxBase {
350    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
351        f.debug_struct("LayoutBoxBase").finish()
352    }
353}
354
355#[derive(MallocSizeOf)]
356pub(crate) enum LayoutResultAndInputs {
357    IndependentFormattingContext(Box<IndependentFormattingContextLayoutResultAndInputs>),
358    SameFormattingContextBlock(Box<SameFormattingContextBlockLayoutResultAndInputs>),
359}
360
361#[derive(Clone, MallocSizeOf)]
362pub(crate) struct IndependentFormattingContextLayoutResult {
363    pub fragments: Vec<Fragment>,
364
365    /// <https://drafts.csswg.org/css2/visudet.html#root-height>
366    pub content_block_size: Au,
367
368    /// If this layout is for a block container, this tracks the collapsable size
369    /// of start and end margins and whether or not the block container collapsed through.
370    pub collapsible_margins_in_children: CollapsedBlockMargins,
371
372    /// The contents of a table may force it to become wider than what we would expect
373    /// from 'width' and 'min-width'. This is the resulting inline content size,
374    /// or None for non-table layouts.
375    pub content_inline_size_for_table: Option<Au>,
376
377    /// The offset of the last inflow baseline of this layout in the content area, if
378    /// there was one. This is used to propagate baselines to the ancestors of `display:
379    /// inline-block`.
380    pub baselines: Baselines,
381
382    /// Whether or not this layout depends on the containing block size.
383    pub depends_on_block_constraints: bool,
384
385    /// Additional information of this layout that could be used by Javascripts and devtools.
386    pub specific_layout_info: Option<SpecificLayoutInfo>,
387}
388
389/// A collection of layout inputs and a cached layout result for an IndependentFormattingContext for
390/// use in [`LayoutBoxBase`].
391#[derive(MallocSizeOf)]
392pub(crate) struct IndependentFormattingContextLayoutResultAndInputs {
393    /// The [`IndependentFormattingContextLayoutResult`] for this layout.
394    pub result: IndependentFormattingContextLayoutResult,
395
396    /// The [`ContainingBlockSize`] to use for this box's contents, but not
397    /// for the box itself.
398    pub containing_block_for_children_size: ContainingBlockSize,
399
400    /// A [`PositioningContext`] holding absolutely-positioned descendants
401    /// collected during the layout of this box.
402    pub positioning_context: PositioningContext,
403}
404
405#[derive(Clone, MallocSizeOf)]
406pub(crate) struct SameFormattingContextBlockLayoutResult {
407    #[conditional_malloc_size_of]
408    pub fragment: Arc<BoxFragment>,
409    original_offset: Point2D<Au, CSSPixel>,
410}
411
412/// A collection of layout inputs and a cached layout result for a SameFormattingContextBlock for
413/// use in [`LayoutBoxBase`].
414#[derive(MallocSizeOf)]
415pub(crate) struct SameFormattingContextBlockLayoutResultAndInputs {
416    pub result: SameFormattingContextBlockLayoutResult,
417    /// The [`ContainingBlockSize`] used when this block was laid out.
418    pub containing_block_size: ContainingBlockSize,
419    /// The containing block's [`WritingMode`]  used when this block was laid out.
420    pub containing_block_writing_mode: WritingMode,
421    /// The containing block's `justify-items` [`AlignFlags`] used when this block was laid out.
422    pub containing_block_justify_items: AlignFlags,
423    /// Whether or not the margin in this block was collapsible with the parent's start margin
424    /// when this block was laid out.
425    collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>,
426    /// Whether or not block margins were ignored for stretch when this block was laid out.
427    ignore_block_margins_for_stretch: LogicalSides1D<bool>,
428    /// Whether or not this block had an inline parent.
429    has_inline_parent: bool,
430}