servo-layout 0.5.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
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
/* 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::sync::Arc;

use layout_api::{
    DangerousStyleElement, DangerousStyleNode, LayoutDamage, LayoutElement, LayoutNode,
};
use script::layout_dom::ServoLayoutNode;
use style::context::{SharedStyleContext, StyleContext};
use style::dom::{NodeInfo, TElement, TNode};
use style::selector_parser::RestyleDamage;
use style::traversal::{DomTraversal, PerLevelTraversalData, recalc_style_at};

use crate::BoxTree;
use crate::context::LayoutContext;
use crate::dom::{DOMLayoutData, NodeExt};
use crate::layout_root::LayoutRoot;

pub struct RecalcStyle<'a> {
    context: &'a LayoutContext<'a>,
}

impl<'a> RecalcStyle<'a> {
    pub(crate) fn new(context: &'a LayoutContext<'a>) -> Self {
        RecalcStyle { context }
    }

    pub(crate) fn context(&self) -> &LayoutContext<'a> {
        self.context
    }
}

impl<'dom, E> DomTraversal<E> for RecalcStyle<'_>
where
    E: DangerousStyleElement<'dom> + TElement,
    E::ConcreteNode: 'dom + DangerousStyleNode<'dom>,
{
    fn process_preorder<F>(
        &self,
        traversal_data: &PerLevelTraversalData,
        context: &mut StyleContext<E>,
        node: E::ConcreteNode,
        note_child: F,
    ) where
        F: FnMut(E::ConcreteNode),
    {
        let Some(dangerous_style_element) = node.as_element() else {
            return;
        };

        let layout_element = dangerous_style_element.layout_element();
        let had_style_data = layout_element.style_data().is_some();
        layout_element.initialize_style_and_layout_data::<DOMLayoutData>();

        let mut element_data = dangerous_style_element.mutate_data().unwrap();
        if !had_style_data {
            element_data.damage = RestyleDamage::reconstruct();
        }

        recalc_style_at(
            self,
            traversal_data,
            context,
            dangerous_style_element,
            &mut element_data,
            note_child,
        );
    }

    #[inline]
    fn needs_postorder_traversal() -> bool {
        false
    }

    fn process_postorder(&self, _style_context: &mut StyleContext<E>, _node: E::ConcreteNode) {
        panic!("this should never be called")
    }

    fn shared_context(&self) -> &SharedStyleContext<'_> {
        &self.context.style_context
    }
}

#[servo_tracing::instrument(skip_all)]
pub(crate) fn compute_damage_and_rebuild_box_tree<'dom>(
    box_tree: &mut Option<Arc<BoxTree>>,
    layout_context: &LayoutContext,
    dirty_root: ServoLayoutNode<'dom>,
    root_node: ServoLayoutNode<'dom>,
    damage_from_environment: LayoutDamage,
    layout_roots: &mut Vec<LayoutRoot<'dom>>,
) -> LayoutDamage {
    // First process damage below the dirty root, returning the damage that
    // should be propagated upward into the clean part of the tree.
    let layout_damage = compute_damage_and_rebuild_box_tree_below_dirty_root(
        layout_context,
        dirty_root,
        damage_from_environment,
        layout_roots,
    );

    // If there was no box tree at all at this point, a full box tree / fragment
    // tree layout is necessary and there is no point processing any other damage.
    if box_tree.is_none() {
        *box_tree = Some(Arc::new(BoxTree::construct(layout_context, root_node)));
        return layout_damage;
    }

    // Propagate the damage from the dirty part of the tree upward. In this part of
    // the traversal no elements can add damage, but they might isolate damage being
    // propagated upward between the dirty root and the root of the DOM.
    let layout_damage = compute_damage_and_rebuild_box_tree_above_dirty_root(
        layout_context,
        dirty_root,
        layout_damage,
        layout_roots,
    );

    // We could not find a place in the middle of the tree to run box tree reconstruction,
    // so just rebuild the whole tree.
    if layout_damage.contains(LayoutDamage::DescendantHasBoxDamage) {
        *box_tree = Some(Arc::new(BoxTree::construct(layout_context, root_node)));
    }

    layout_damage
}

#[expect(unsafe_code)]
#[servo_tracing::instrument(skip_all)]
pub(crate) fn compute_damage_and_rebuild_box_tree_above_dirty_root<'dom>(
    layout_context: &LayoutContext,
    dirty_root: ServoLayoutNode<'dom>,
    layout_damage: LayoutDamage,
    layout_roots: &mut Vec<LayoutRoot<'dom>>,
) -> LayoutDamage {
    // Cases where propagating damage up the tree is necessary:
    //
    // 1. Box tree layout of the dirty root is necessary, in which case we
    //    search for a place to re-run box tree layout and also invalidate
    //    all fragments and fragment caches to the root.
    // 2. Fragment tree layout needs to run again, in which case fragments
    //    and fragment caches need to be invalidated.
    // 3. Overflow is dirty, in which case overflow needs to be cleared.
    //
    // In every other case, just return early.
    let needs_fragment_tree_rebuild = layout_damage.contains(LayoutDamage::Relayout);
    let needs_overflow_recalculation = layout_damage.contains(LayoutDamage::RecalculateOverflow);
    if !needs_fragment_tree_rebuild && !needs_overflow_recalculation {
        assert!(!layout_damage.contains(LayoutDamage::DescendantCollectedAsLayoutRoot));
        return layout_damage;
    }

    let mut damage_for_parent = layout_damage;
    let mut maybe_parent_node = unsafe { dirty_root.dangerous_flat_tree_parent() };
    while let Some(parent_node) = maybe_parent_node {
        let damage_set = ElementDamageSet {
            node: parent_node,
            from_parent: LayoutDamage::empty(),
            on_element: LayoutDamage::empty(),
            from_children: damage_for_parent,
            // Ancestors above the dirty root do not have damage, so will never subsume
            // any existing layout roots, but they may isolate upward flowing fragment
            // tree damage.
            incoming_layout_root_count: layout_roots.len(),
        };

        damage_for_parent = damage_set.apply_damage(layout_context, layout_roots);
        maybe_parent_node = unsafe { parent_node.dangerous_flat_tree_parent() };
    }

    damage_for_parent
}

pub(crate) fn compute_damage_and_rebuild_box_tree_below_dirty_root<'dom>(
    layout_context: &LayoutContext,
    node: ServoLayoutNode<'dom>,
    damage_from_parent: LayoutDamage,
    layout_roots: &mut Vec<LayoutRoot<'dom>>,
) -> LayoutDamage {
    // Don't do any kind of damage propagation or box tree construction for non-Element
    // nodes, such as text and comments.
    let Some(element) = node.as_element() else {
        return damage_from_parent;
    };

    let (element_damage, is_display_none) = {
        let mut element_data = element.element_data_mut();
        (
            LayoutDamage::from(std::mem::take(&mut element_data.damage)),
            element_data.styles.is_display_none(),
        )
    };

    let has_dirty_descendants;
    #[expect(unsafe_code)]
    unsafe {
        let dangerous_style_element = element.dangerous_style_element();
        has_dirty_descendants = dangerous_style_element.has_dirty_descendants();
        dangerous_style_element.unset_dirty_descendants();
    };

    if is_display_none {
        node.unset_all_boxes();
        return element_damage | damage_from_parent;
    }

    let mut damage_set = ElementDamageSet {
        node,
        from_parent: damage_from_parent,
        on_element: element_damage,
        from_children: LayoutDamage::empty(),
        incoming_layout_root_count: layout_roots.len(),
    };

    // Depending on the incoming damage, it can be isolated, meaning that some damage
    // doesn't get passed down to children.
    let damage_for_children = damage_set.isolate_incoming_damage();

    // Propagate damage to children and gather the resulting damage into `from_children`.
    damage_set.propagate_damage_to_children(
        layout_context,
        has_dirty_descendants,
        damage_for_children,
        layout_roots,
    );

    // Apply the calculated damage to this element (perhaps triggering box tree layout),
    // and propagate resulting damage to ancestors.
    damage_set.apply_damage(layout_context, layout_roots)
}

enum BoxDamageAction<'a> {
    RebuildAncestor,
    TryRebuild,
    InvalidateFragmentTreeBelowLayoutRoot,
    CollectLayoutRoot(LayoutRoot<'a>),
    InvalidateFragmentTreeAboveLayoutRoot,
    InvalidateScrollableOverflow,
    None,
}

impl BoxDamageAction<'_> {
    fn rebuilds_box(&self) -> bool {
        matches!(self, Self::RebuildAncestor | Self::TryRebuild)
    }
}

pub(crate) struct ElementDamageSet<'a> {
    node: ServoLayoutNode<'a>,
    pub from_parent: LayoutDamage,
    pub on_element: LayoutDamage,
    pub from_children: LayoutDamage,
    pub incoming_layout_root_count: usize,
}

impl<'a> ElementDamageSet<'a> {
    /// Given the damage on the element and damage from parents, determine which damage
    /// should be passed to children, returning that value.
    fn isolate_incoming_damage(&mut self) -> LayoutDamage {
        // Children only receive layout mode damage from their parents, except when an ancestor
        // needs to be completely rebuilt. In that case, descendants are rebuilt down to the
        // first independent formatting context, which should isolate that tree from further
        // box damage.
        let mut damage_for_children = (self.on_element | self.from_parent).only_layout_modes();
        let rebuild_children = self.on_element.contains(LayoutDamage::BoxDamage) ||
            (self.from_parent.contains(LayoutDamage::BoxDamage) &&
                !self.node.isolates_damage_for_damage_propagation());

        if rebuild_children {
            damage_for_children.insert(LayoutDamage::BoxDamage);
        } else if self.from_parent.contains(LayoutDamage::Relayout) &&
            !self.on_element.contains(LayoutDamage::Relayout) &&
            self.node.isolates_damage_for_damage_propagation()
        {
            // If not rebuilding the boxes for this node, but fragments need to be laid out
            // only because of an ancestor, fragment layout caches should still be valid when
            // crossing down into new independent formatting contexts.
            damage_for_children.remove(LayoutDamage::Relayout);
            self.from_parent.remove(LayoutDamage::Relayout);
        }

        damage_for_children
    }

    /// Given the damage the damage to children and whether or not this element had any
    /// dirty descendants, conditionally propagated damage to children and set the resulting
    /// damage from children on this [`ElementDamageSet`].
    fn propagate_damage_to_children(
        &mut self,
        layout_context: &LayoutContext<'_>,
        has_dirty_descendants: bool,
        damage_for_children: LayoutDamage,
        layout_roots: &mut Vec<LayoutRoot<'a>>,
    ) {
        // Propagate damage into children, but only if:
        //  1. There is a descendant that was dirty / possibly restyled.
        //  2. We detected that we need to rebuild child boxes.
        //  3. An ancestor will be laid out and children need to have their fragment caches cleared.
        //
        // In other situations, such as when layout will not run at all or when we are
        // guaranteed that children are undamaged, we can skip traversing children entirely.
        if has_dirty_descendants ||
            damage_for_children.intersects(LayoutDamage::BoxDamage | LayoutDamage::Relayout)
        {
            for child in self.node.flat_tree_children() {
                if child.is_element() {
                    self.from_children |= compute_damage_and_rebuild_box_tree_below_dirty_root(
                        layout_context,
                        child,
                        damage_for_children,
                        layout_roots,
                    );
                }
            }
        }
    }

    /// Given the damage from this element, the parent, and children, determine what action to
    /// take for this element's boxes and return the damage that should be propagated to parents.
    fn apply_damage(
        self,
        layout_context: &LayoutContext<'_>,
        layout_roots: &mut Vec<LayoutRoot<'a>>,
    ) -> LayoutDamage {
        let only_layout_mode_damage =
            (self.from_parent | self.on_element | self.from_children).only_layout_modes();

        let invalidate_for_rebuild = || {
            self.node.unset_all_boxes();
            LayoutDamage::DescendantHasBoxDamage | LayoutDamage::Relayout
        };

        // This removes any dirty layout roots from descendants.
        let discard_any_descendant_layout_roots = |layout_roots: &mut Vec<LayoutRoot>| {
            layout_roots.truncate(self.incoming_layout_root_count);
        };

        let action = self.box_damage_action();
        let will_rebuild_box = action.rebuilds_box();
        let damage_for_parent = match action {
            BoxDamageAction::TryRebuild => {
                discard_any_descendant_layout_roots(layout_roots);

                if self
                    .node
                    .rebuild_box_tree_from_independent_formatting_context(layout_context)
                {
                    // In this case, we have rebuilt the box tree from this point and we do not
                    // have to propagate rebuild box tree damage up the tree any further.
                    LayoutDamage::Relayout | LayoutDamage::RecomputeInlineContentSizes
                } else {
                    // A descendant needs to be rebuilt, but couldn't be rebuilt here,
                    // because this node was an not a rebuild-compatible independent
                    // formatting context. In this case do the same thing as if we needed
                    // to rebuild an ancestor.
                    invalidate_for_rebuild()
                }
            },
            BoxDamageAction::RebuildAncestor => {
                // In this case an ancestor needs to be completely rebuilt.
                //
                // This means that this box is no longer valid and also needs to be rebuilt
                // (perhaps some of its descendants do not though). In this case, unset all existing
                // boxes for the node and ensure that the appropriate rebuild-type damage
                // propagates up the tree.
                discard_any_descendant_layout_roots(layout_roots);
                invalidate_for_rebuild()
            },
            BoxDamageAction::InvalidateFragmentTreeBelowLayoutRoot => {
                // In this case, this node's boxes are preserved! It's possible that we still need
                // to run fragment tree layout in this subtree due to an ancestor, this node, or a
                // descendant changing style. In that case, we ask the `LayoutBoxBase` to clear
                // any cached information that cannot be used.
                discard_any_descendant_layout_roots(layout_roots);

                let mut damage_for_parent =
                    (self.on_element | self.from_children) | self.from_parent.only_layout_modes();

                // This node also needed new fragment tree layout, so if any descendant
                // was collected as a layout root, it's now discarded. This means we
                // should also clear the damage (though harmless as Relayout takes
                // precedence) indicating that there was a collected layout root.
                damage_for_parent.remove(LayoutDamage::DescendantCollectedAsLayoutRoot);

                let mut inline_size_depends_on_content = false;
                self.node.with_layout_box_base_including_pseudos(|base| {
                    inline_size_depends_on_content |=
                        base.invalidate_caches_for_fragment_tree_layout(&self);
                });

                self.adjust_inline_content_size_damage(
                    &mut damage_for_parent,
                    inline_size_depends_on_content,
                );

                damage_for_parent
            },
            BoxDamageAction::CollectLayoutRoot(layout_root) => {
                // A layout root should only be collected if a parent node does not
                // produce damage requiring a fragment tree layout. This is essential
                // to ensure the invariant that layout roots are only collected when
                // they isolate damage from ancestors. If an ancestor has damage, a
                // layout root's final position depends on that ancestor's layout
                // and should never be a collected layout root.
                debug_assert!(!self.from_parent.contains(LayoutDamage::Relayout));

                // This removes any dirty layout roots from descendants and then adds this
                // node as a dirty layout root. As this node itself as a dirty layout
                // root, it subsumes all dirty descendant layout roots.
                discard_any_descendant_layout_roots(layout_roots);
                layout_roots.push(layout_root);

                self.node.with_layout_box_base_including_pseudos(|base| {
                    base.invalidate_caches(&self);
                    base.mark_fragments_as_descendants_changed();
                });
                LayoutDamage::RecalculateOverflow |
                    LayoutDamage::DescendantCollectedAsLayoutRoot |
                    LayoutDamage::RecomputeInlineContentSizes
            },
            BoxDamageAction::InvalidateFragmentTreeAboveLayoutRoot => {
                // Damage propagation works exactly the same at the point the layout root is collected
                // and above it. Layout caches are invalidated and damage is adjusted, maybe limited
                // inline content size recalculation.
                let mut damage_for_parent = LayoutDamage::RecalculateOverflow |
                    LayoutDamage::DescendantCollectedAsLayoutRoot;

                let mut inline_size_depends_on_content = false;
                self.node.with_layout_box_base_including_pseudos(|base| {
                    inline_size_depends_on_content |= base.invalidate_caches(&self);
                    base.mark_fragments_as_descendants_changed();
                });
                self.adjust_inline_content_size_damage(
                    &mut damage_for_parent,
                    inline_size_depends_on_content,
                );

                damage_for_parent
            },
            BoxDamageAction::InvalidateScrollableOverflow => {
                // In this case the node's fragments are preserved, but it or one of its descendants
                // had scrollable overflow damage, which means that scrollable overflow should be
                // cleared. This causes it to be recalculated the next time it's queried.
                self.node.with_layout_box_base_including_pseudos(|base| {
                    base.clear_scrollable_overflow_all_on_fragments();
                });
                only_layout_mode_damage
            },
            BoxDamageAction::None => only_layout_mode_damage,
        };

        // If this element's boxes are preserved and its style has changed, whether or not
        // we run fragment tree layout, we need to update any preserved layout data
        // structures' style references.
        if !self.on_element.is_empty() && !will_rebuild_box {
            self.node.repair_style(&layout_context.style_context);
        }

        damage_for_parent
    }

    fn box_damage_action(&self) -> BoxDamageAction<'a> {
        // When a parent box is going to be reconstructed, that overrides everything else.
        if self
            .from_parent
            .contains(LayoutDamage::DescendantHasBoxDamage)
        {
            return BoxDamageAction::RebuildAncestor;
        }

        // When this element or one of its descendants needs to be reconstructed, try to
        // rebuild it here. If that fails, an ancestor box will be reconstructed instead.
        let element_and_children_damage = self.on_element | self.from_children;
        if element_and_children_damage.contains(LayoutDamage::DescendantHasBoxDamage) {
            return BoxDamageAction::TryRebuild;
        }

        if element_and_children_damage.contains(LayoutDamage::Relayout) &&
            !self.from_parent.contains(LayoutDamage::Relayout) &&
            let Ok(layout_root) = LayoutRoot::try_from(self.node)
        {
            return BoxDamageAction::CollectLayoutRoot(layout_root);
        }

        // If this element needs a new fragment layout, then invalidate fragment caches
        // clear the resulting fragments, and clear scrollable overflow.
        if (self.from_parent | element_and_children_damage).contains(LayoutDamage::Relayout) {
            return BoxDamageAction::InvalidateFragmentTreeBelowLayoutRoot;
        }

        // If one of this element's descendants was collected as a layout root, then
        // invalidate fragment caches and clear scrollable overflow.
        if self
            .from_children
            .contains(LayoutDamage::DescendantCollectedAsLayoutRoot)
        {
            return BoxDamageAction::InvalidateFragmentTreeAboveLayoutRoot;
        }

        // If the scrollable overflow of this element has changed, invalidate the
        // scrollable overflow.
        if element_and_children_damage.contains(LayoutDamage::RecalculateOverflow) {
            return BoxDamageAction::InvalidateScrollableOverflow;
        }

        BoxDamageAction::None
    }

    fn adjust_inline_content_size_damage(
        &self,
        damage_for_parent: &mut LayoutDamage,
        inline_size_depends_on_content: bool,
    ) {
        let children_need_inline_content_size_recalculation = self
            .from_children
            .contains(LayoutDamage::RecomputeInlineContentSizes) &&
            inline_size_depends_on_content;
        damage_for_parent.set(
            LayoutDamage::RecomputeInlineContentSizes,
            !self.on_element.is_empty() || children_need_inline_content_size_recalculation,
        );
    }
}