servo-layout 0.3.0

A component of the servo web-engine.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};

use app_units::Au;
use atomic_refcell::AtomicRefCell;
use euclid::Point2D;
use layout_api::LayoutDamage;
use malloc_size_of_derive::MallocSizeOf;
use servo_arc::Arc as ServoArc;
use style::computed_values::position::T as Position;
use style::logical_geometry::WritingMode;
use style::properties::ComputedValues;
use style::selector_parser::RestyleDamage;
use style::values::specified::align::AlignFlags;
use style_traits::CSSPixel;

use crate::context::LayoutContext;
use crate::dom::{LayoutBox, WeakLayoutBox};
use crate::flow::CollapsibleWithParentStartMargin;
use crate::formatting_contexts::Baselines;
use crate::fragment_tree::{
    BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, Fragment, FragmentStatus,
    SpecificLayoutInfo,
};
use crate::geom::LogicalSides1D;
use crate::positioned::{PositioningContext, relative_adjustement};
use crate::sizing::{ComputeInlineContentSizes, InlineContentSizesResult, SizeConstraint};
use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize};

/// A box tree node that handles containing information about style and the original DOM
/// node or pseudo-element that it is based on. This also handles caching of layout values
/// such as the inline content sizes to avoid recalculating these values during layout
/// passes.
///
/// In the future, this will hold layout results to support incremental layout.
#[derive(MallocSizeOf)]
pub(crate) struct LayoutBoxBase {
    pub base_fragment_info: BaseFragmentInfo,
    pub style: ServoArc<ComputedValues>,
    pub cached_inline_content_size:
        AtomicRefCell<Option<Box<(SizeConstraint, InlineContentSizesResult)>>>,
    pub outer_inline_content_sizes_depend_on_content: AtomicBool,

    /// The cached layout results for this [`LayoutBoxBase`]. These are either cached
    /// independent formatting context results or a cached block layout for use within
    /// a block flow.
    cached_layout_result: AtomicRefCell<Option<LayoutResultAndInputs>>,

    /// Whether or not the cached layout result for this [`LayoutBoxBase`] is dirty.
    /// This flag is used to preserve the cache when it can be used to do a faster
    /// layout, but cannot be reused directly.
    cached_layout_result_dirty: AtomicBool,

    pub fragments: AtomicRefCell<Vec<Fragment>>,
    pub parent_box: Option<WeakLayoutBox>,
    only_descendants_changed: AtomicBool,
}

impl LayoutBoxBase {
    pub(crate) fn new(
        base_fragment_info: BaseFragmentInfo,
        style: ServoArc<ComputedValues>,
    ) -> Self {
        Self {
            base_fragment_info,
            style,
            cached_inline_content_size: AtomicRefCell::default(),
            outer_inline_content_sizes_depend_on_content: AtomicBool::new(true),
            cached_layout_result: AtomicRefCell::default(),
            cached_layout_result_dirty: AtomicBool::default(),
            fragments: AtomicRefCell::default(),
            parent_box: None,
            only_descendants_changed: AtomicBool::default(),
        }
    }

    /// Get the inline content sizes of a box tree node that extends this [`LayoutBoxBase`], fetch
    /// the result from a cache when possible.
    pub(crate) fn inline_content_sizes(
        &self,
        layout_context: &LayoutContext,
        constraint_space: &ConstraintSpace,
        layout_box: &impl ComputeInlineContentSizes,
    ) -> InlineContentSizesResult {
        let mut cache = self.cached_inline_content_size.borrow_mut();
        if let Some(cached_inline_content_size) = cache.as_ref() {
            let (previous_cb_block_size, result) = **cached_inline_content_size;
            if !result.depends_on_block_constraints ||
                previous_cb_block_size == constraint_space.block_size
            {
                return result;
            }
            // TODO: Should we keep multiple caches for various block sizes?
        }

        let result =
            layout_box.compute_inline_content_sizes_with_fixup(layout_context, constraint_space);
        *cache = Some(Box::new((constraint_space.block_size, result)));
        result
    }

    pub(crate) fn fragments(&self) -> Vec<Fragment> {
        self.fragments.borrow().clone()
    }

    pub(crate) fn add_fragment(&self, fragment: Fragment) {
        if self.only_descendants_changed.load(Ordering::Relaxed) &&
            let Some(base) = fragment.base()
        {
            base.set_status(FragmentStatus::OnlyDescendantsChanged)
        }
        self.fragments.borrow_mut().push(fragment);
    }

    pub(crate) fn set_fragment(&self, fragment: Fragment) {
        if self.only_descendants_changed.load(Ordering::Relaxed) &&
            let Some(base) = fragment.base()
        {
            base.set_status(FragmentStatus::OnlyDescendantsChanged)
        }
        *self.fragments.borrow_mut() = vec![fragment];
    }

    pub(crate) fn clear_fragments(&self) {
        self.fragments.borrow_mut().clear();
    }

    /// Clear all resulting fragments and dirty and fragment caches. Resulting fragments are
    /// used for layout queries and fragment caches are used for incremental layout.
    pub(crate) fn clear_fragments_and_dirty_fragment_cache(&self) {
        self.fragments.borrow_mut().clear();
        self.cached_layout_result_dirty
            .store(true, Ordering::Relaxed);
    }

    pub(crate) fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) {
        self.style = new_style.clone();
        for fragment in self.fragments.borrow_mut().iter_mut() {
            if let Some(base) = fragment.base() {
                base.repair_style(new_style);
            }
        }
    }

    #[expect(unused)]
    pub(crate) fn parent_box(&self) -> Option<LayoutBox> {
        self.parent_box.as_ref().and_then(WeakLayoutBox::upgrade)
    }

    pub(crate) fn add_damage(
        &self,
        element_damage: LayoutDamage,
        damage_from_children: LayoutDamage,
        damage_from_parent: RestyleDamage,
    ) -> LayoutDamage {
        let only_descendants_changed = !RestyleDamage::from(element_damage)
            .contains(RestyleDamage::RELAYOUT) &&
            !damage_from_parent.contains(RestyleDamage::RELAYOUT) &&
            !damage_from_children.contains(LayoutDamage::LAYOUT_AFFECTED_BY_INFLOW_DESCENDANT) &&
            self.fragments.borrow().iter().all(|fragment| {
                fragment
                    .base()
                    .is_some_and(|base| base.status() == FragmentStatus::Clean)
            });
        self.only_descendants_changed
            .store(only_descendants_changed, Ordering::Relaxed);
        self.clear_fragments_and_dirty_fragment_cache();

        if !element_damage.is_empty() ||
            damage_from_children.contains(LayoutDamage::RECOMPUTE_INLINE_CONTENT_SIZES)
        {
            *self.cached_inline_content_size.borrow_mut() = None;
        }

        let mut damage_for_parent = element_damage | damage_from_children;

        // When a block container has a mix of inline-level and block-level contents, the
        // inline-level ones are wrapped inside an anonymous block associated with the
        // block container. The anonymous block has an `auto` size, so its intrinsic
        // contribution depends on content, but it can't affect the intrinsic size of
        // ancestors if the block container is sized extrinsically.
        //
        // If the intrinsic contributions of this node depend on content, we will need to
        // clear the cached intrinsic sizes of the parent. But if the contributions are
        // purely extrinsic, then the intrinsic sizes of the ancestors won't be affected,
        // and we can keep the cache.
        damage_for_parent.set(
            LayoutDamage::RECOMPUTE_INLINE_CONTENT_SIZES,
            !element_damage.is_empty() ||
                (!self.base_fragment_info.is_anonymous() &&
                    self.outer_inline_content_sizes_depend_on_content
                        .load(Ordering::Relaxed)),
        );

        damage_for_parent
    }

    pub(crate) fn cached_independent_formatting_context_layout_if_applicable(
        &self,
        positioning_context: &mut PositioningContext,
        containing_block_for_children: &ContainingBlock<'_>,
    ) -> Option<IndependentFormattingContextLayoutResult> {
        if self.cached_layout_result_dirty.load(Ordering::Relaxed) {
            return None;
        }

        let cache = self.cached_layout_result.borrow();
        let Some(LayoutResultAndInputs::IndependentFormattingContext(cache)) = &*cache else {
            return None;
        };

        let cache = &**cache;
        if cache.containing_block_for_children_size.inline !=
            containing_block_for_children.size.inline
        {
            return None;
        }
        if cache.containing_block_for_children_size.block !=
            containing_block_for_children.size.block &&
            cache.result.depends_on_block_constraints
        {
            return None;
        }

        positioning_context.append(cache.positioning_context.clone());
        Some(cache.result.clone())
    }

    pub(crate) fn cache_independent_formatting_context_layout(
        &self,
        containing_block_for_children: &ContainingBlock<'_>,
        child_positioning_context: &PositioningContext,
        result: &IndependentFormattingContextLayoutResult,
    ) {
        self.cached_layout_result_dirty
            .store(false, Ordering::Relaxed);
        *self.cached_layout_result.borrow_mut() =
            Some(LayoutResultAndInputs::IndependentFormattingContext(
                Box::new(IndependentFormattingContextLayoutResultAndInputs {
                    result: result.clone(),
                    positioning_context: child_positioning_context.clone(),
                    containing_block_for_children_size: containing_block_for_children.size.clone(),
                }),
            ));
    }

    pub(crate) fn cached_same_formatting_context_block_if_applicable(
        &self,
        containing_block: &ContainingBlock,
        collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>,
        ignore_block_margins_for_stretch: LogicalSides1D<bool>,
        has_inline_parent: bool,
    ) -> Option<Arc<BoxFragment>> {
        if self.cached_layout_result_dirty.load(Ordering::Relaxed) {
            return None;
        }

        let mut cached_layout_result = self.cached_layout_result.borrow_mut();
        let Some(LayoutResultAndInputs::SameFormattingContextBlock(result)) =
            &mut *cached_layout_result
        else {
            return None;
        };

        if result.containing_block_size != containing_block.size ||
            result.containing_block_writing_mode != containing_block.style.writing_mode ||
            result.containing_block_justify_items !=
                containing_block.style.clone_justify_items().computed.0.0 ||
            result.collapsible_with_parent_start_margin != collapsible_with_parent_start_margin ||
            result.ignore_block_margins_for_stretch != ignore_block_margins_for_stretch ||
            result.has_inline_parent != has_inline_parent
        {
            return None;
        }

        let fragment = result.result.fragment.clone();
        {
            let mut origin = result.result.original_offset;
            if self.style.clone_position() == Position::Relative {
                origin += relative_adjustement(&self.style, containing_block)
                    .to_physical_vector(containing_block.style.writing_mode)
            }
            fragment.base.set_rect_origin(origin);
        }

        Some(fragment)
    }

    pub(crate) fn cache_same_formatting_context_block_layout(
        &self,
        containing_block: &ContainingBlock,
        collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>,
        ignore_block_margins_for_stretch: LogicalSides1D<bool>,
        has_inline_parent: bool,
        fragment: Arc<BoxFragment>,
    ) {
        let mut original_offset;
        {
            original_offset = fragment.content_rect().origin;
            if self.style.clone_position() == Position::Relative {
                original_offset -= relative_adjustement(&self.style, containing_block)
                    .to_physical_vector(containing_block.style.writing_mode)
            }
        }

        self.cached_layout_result_dirty
            .store(false, Ordering::Relaxed);
        *self.cached_layout_result.borrow_mut() =
            Some(LayoutResultAndInputs::SameFormattingContextBlock(Box::new(
                SameFormattingContextBlockLayoutResultAndInputs {
                    result: SameFormattingContextBlockLayoutResult {
                        fragment,
                        original_offset,
                    },
                    containing_block_size: containing_block.size.clone(),
                    containing_block_writing_mode: containing_block.style.writing_mode,
                    containing_block_justify_items: containing_block
                        .style
                        .clone_justify_items()
                        .computed
                        .0
                        .0,
                    collapsible_with_parent_start_margin,
                    ignore_block_margins_for_stretch,
                    has_inline_parent,
                },
            )));
    }

    pub(crate) fn clear_scrollable_overflow_all_on_fragments(&self) {
        for fragment in self.fragments.borrow().iter() {
            fragment.clear_scrollable_overflow();
        }
    }
}

impl Debug for LayoutBoxBase {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
        f.debug_struct("LayoutBoxBase").finish()
    }
}

#[derive(MallocSizeOf)]
pub(crate) enum LayoutResultAndInputs {
    IndependentFormattingContext(Box<IndependentFormattingContextLayoutResultAndInputs>),
    SameFormattingContextBlock(Box<SameFormattingContextBlockLayoutResultAndInputs>),
}

#[derive(Clone, MallocSizeOf)]
pub(crate) struct IndependentFormattingContextLayoutResult {
    pub fragments: Vec<Fragment>,

    /// <https://drafts.csswg.org/css2/visudet.html#root-height>
    pub content_block_size: Au,

    /// If this layout is for a block container, this tracks the collapsable size
    /// of start and end margins and whether or not the block container collapsed through.
    pub collapsible_margins_in_children: CollapsedBlockMargins,

    /// The contents of a table may force it to become wider than what we would expect
    /// from 'width' and 'min-width'. This is the resulting inline content size,
    /// or None for non-table layouts.
    pub content_inline_size_for_table: Option<Au>,

    /// The offset of the last inflow baseline of this layout in the content area, if
    /// there was one. This is used to propagate baselines to the ancestors of `display:
    /// inline-block`.
    pub baselines: Baselines,

    /// Whether or not this layout depends on the containing block size.
    pub depends_on_block_constraints: bool,

    /// Additional information of this layout that could be used by Javascripts and devtools.
    pub specific_layout_info: Option<SpecificLayoutInfo>,
}

/// A collection of layout inputs and a cached layout result for an IndependentFormattingContext for
/// use in [`LayoutBoxBase`].
#[derive(MallocSizeOf)]
pub(crate) struct IndependentFormattingContextLayoutResultAndInputs {
    /// The [`IndependentFormattingContextLayoutResult`] for this layout.
    pub result: IndependentFormattingContextLayoutResult,

    /// The [`ContainingBlockSize`] to use for this box's contents, but not
    /// for the box itself.
    pub containing_block_for_children_size: ContainingBlockSize,

    /// A [`PositioningContext`] holding absolutely-positioned descendants
    /// collected during the layout of this box.
    pub positioning_context: PositioningContext,
}

#[derive(Clone, MallocSizeOf)]
pub(crate) struct SameFormattingContextBlockLayoutResult {
    #[conditional_malloc_size_of]
    pub fragment: Arc<BoxFragment>,
    original_offset: Point2D<Au, CSSPixel>,
}

/// A collection of layout inputs and a cached layout result for a SameFormattingContextBlock for
/// use in [`LayoutBoxBase`].
#[derive(MallocSizeOf)]
pub(crate) struct SameFormattingContextBlockLayoutResultAndInputs {
    pub result: SameFormattingContextBlockLayoutResult,
    /// The [`ContainingBlockSize`] used when this block was laid out.
    pub containing_block_size: ContainingBlockSize,
    /// The containing block's [`WritingMode`]  used when this block was laid out.
    pub containing_block_writing_mode: WritingMode,
    /// The containing block's `justify-items` [`AlignFlags`] used when this block was laid out.
    pub containing_block_justify_items: AlignFlags,
    /// Whether or not the margin in this block was collapsible with the parent's start margin
    /// when this block was laid out.
    collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>,
    /// Whether or not block margins were ignored for stretch when this block was laid out.
    ignore_block_margins_for_stretch: LogicalSides1D<bool>,
    /// Whether or not this block had an inline parent.
    has_inline_parent: bool,
}