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