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