Skip to main content

layout/fragment_tree/
fragment.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::ops::Range;
6use std::sync::Arc;
7use std::sync::atomic::AtomicBool;
8
9use app_units::Au;
10use atomic_refcell::AtomicRef;
11use euclid::{Point2D, Rect, Size2D};
12use fonts::{FontMetrics, ShapedTextSlice};
13use layout_api::BoxAreaType;
14use malloc_size_of_derive::MallocSizeOf;
15use servo_arc::Arc as ServoArc;
16use servo_base::id::PipelineId;
17use servo_base::print_tree::PrintTree;
18use servo_base::text::Utf32CodeUnits;
19use servo_url::ServoUrl;
20use style::Zero;
21use style::properties::ComputedValues;
22use style_traits::CSSPixel;
23use webrender_api::{FontInstanceKey, ImageKey};
24
25use super::{
26    BaseFragment, BoxFragment, ContainingBlockManager, HoistedSharedFragment, PositioningFragment,
27    Tag,
28};
29use crate::SharedStyle;
30use crate::cell::{ArcRefCell, RefOrAtomicRef};
31use crate::flow::inline::text_run::SharedTextRunData;
32use crate::fragment_tree::FragmentStatus;
33use crate::geom::{LogicalSides, PhysicalPoint, PhysicalRect};
34use crate::layout_impl::LayoutThread;
35use crate::style_ext::ComputedValuesExt;
36
37#[derive(Clone, MallocSizeOf)]
38pub(crate) enum Fragment {
39    LayoutRoot(LayoutRootFragment),
40    Box(#[conditional_malloc_size_of] Arc<BoxFragment>),
41    /// Floating content. A floated fragment is very similar to a normal
42    /// [BoxFragment] but it isn't positioned using normal in block flow
43    /// positioning rules (margin collapse, etc). Instead, they are laid
44    /// out by the [crate::flow::float::SequentialLayoutState] of their
45    /// float containing block formatting context.
46    Float(#[conditional_malloc_size_of] Arc<BoxFragment>),
47    Positioning(#[conditional_malloc_size_of] Arc<PositioningFragment>),
48    /// Absolute and fixed position fragments are hoisted up so that they are children of the
49    /// BoxFragment that establishes their containing blocks, so that they can be laid out properly.
50    /// When this happens an `AbsoluteOrFixedPositionedPlaceholder` fragment is left at the original
51    /// tree position. This allows these hoisted fragments to be painted with regard to their
52    /// original tree order during stacking context tree / display list construction.
53    AbsoluteOrFixedPositionedPlaceholder(ArcRefCell<HoistedSharedFragment>),
54    Text(#[conditional_malloc_size_of] Arc<TextFragment>),
55    Image(#[conditional_malloc_size_of] Arc<ImageFragment>),
56    IFrame(#[conditional_malloc_size_of] Arc<IFrameFragment>),
57}
58
59#[derive(Clone, MallocSizeOf)]
60pub(crate) struct CollapsedBlockMargins {
61    pub collapsed_through: bool,
62    pub start: CollapsedMargin,
63    pub end: CollapsedMargin,
64}
65
66#[derive(Clone, Copy, Debug, MallocSizeOf)]
67pub(crate) struct CollapsedMargin {
68    max_positive: Au,
69    min_negative: Au,
70}
71
72#[derive(Clone, MallocSizeOf)]
73pub(crate) struct LayoutRootFragment {
74    pub fragment: ArcRefCell<HoistedSharedFragment>,
75}
76
77impl LayoutRootFragment {
78    pub(crate) fn inner(&self) -> AtomicRef<'_, Fragment> {
79        AtomicRef::map(self.fragment.borrow(), |fragment| {
80            fragment
81                .fragment
82                .as_ref()
83                .expect("Should never create LayoutRoot without a Fragment")
84        })
85    }
86
87    pub(crate) fn inner_box_fragment(&self) -> AtomicRef<'_, Arc<BoxFragment>> {
88        AtomicRef::map(self.inner(), |fragment| match fragment {
89            Fragment::Box(box_fragment) => box_fragment,
90            _ => unreachable!("Layout root should always contain box fragment"),
91        })
92    }
93}
94
95#[derive(MallocSizeOf)]
96pub(crate) struct TextFragment {
97    pub base: BaseFragment,
98    #[conditional_malloc_size_of]
99    pub run_data: Arc<SharedTextRunData>,
100    #[conditional_malloc_size_of]
101    pub font_metrics: Arc<FontMetrics>,
102    pub font_key: FontInstanceKey,
103    #[conditional_malloc_size_of]
104    pub glyphs: Vec<Arc<ShapedTextSlice>>,
105    /// Extra space to add for each justification opportunity.
106    pub justification_adjustment: Au,
107    /// The range of characters this [`TextFragment`] represents within the text of its
108    /// original DOM node (modified by text transformation).
109    pub character_range_in_dom_node: Range<Utf32CodeUnits>,
110    /// Whether or not this [`TextFragment`] is an empty fragment added for the
111    /// benefit of placing a text cursor on an otherwise empty editable line.
112    pub is_empty_for_text_cursor: bool,
113}
114
115#[derive(MallocSizeOf)]
116pub(crate) struct ImageFragment {
117    pub base: BaseFragment,
118    pub style: SharedStyle,
119    /// The style to use for the selection overlay if this [`ImageFragment`] is
120    /// selected.
121    pub selected_style: SharedStyle,
122    pub clip: PhysicalRect<Au>,
123    pub image_key: Option<ImageKey>,
124    pub showing_broken_image_icon: bool,
125    pub url: Option<ServoUrl>,
126    /// The intrinsic (natural) width of the image, if known.
127    pub natural_width: Option<Au>,
128    /// The intrinsic (natural) height of the image, if known.
129    pub natural_height: Option<Au>,
130    /// Whether or not this image is selected.
131    #[conditional_malloc_size_of]
132    pub selected: Arc<AtomicBool>,
133}
134
135#[derive(MallocSizeOf)]
136pub(crate) struct IFrameFragment {
137    pub base: BaseFragment,
138    pub style: SharedStyle,
139    pub pipeline_id: PipelineId,
140}
141
142impl Fragment {
143    pub fn base(&self) -> Option<RefOrAtomicRef<'_, BaseFragment>> {
144        Some(match self {
145            Fragment::LayoutRoot(fragment) => RefOrAtomicRef::AtomicRef(AtomicRef::map(
146                fragment.inner_box_fragment(),
147                |box_fragment| &box_fragment.base,
148            )),
149            Fragment::Box(fragment) => RefOrAtomicRef::Ref(&fragment.base),
150            Fragment::Text(fragment) => RefOrAtomicRef::Ref(&fragment.base),
151            Fragment::AbsoluteOrFixedPositionedPlaceholder(_) => return None,
152            Fragment::Positioning(fragment) => RefOrAtomicRef::Ref(&fragment.base),
153            Fragment::Image(fragment) => RefOrAtomicRef::Ref(&fragment.base),
154            Fragment::IFrame(fragment) => RefOrAtomicRef::Ref(&fragment.base),
155            Fragment::Float(fragment) => RefOrAtomicRef::Ref(&fragment.base),
156        })
157    }
158
159    pub(crate) fn set_containing_block(&self, containing_block: &PhysicalRect<Au>) {
160        match self {
161            Fragment::LayoutRoot(layout_root_fragment) => layout_root_fragment
162                .inner()
163                .set_containing_block(containing_block),
164            Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
165                box_fragment.set_containing_block(containing_block)
166            },
167            Fragment::Positioning(positioning_fragment) => {
168                positioning_fragment.set_containing_block(containing_block)
169            },
170            Fragment::AbsoluteOrFixedPositionedPlaceholder(..) |
171            Fragment::Text(..) |
172            Fragment::Image(..) |
173            Fragment::IFrame(..) => {},
174        }
175    }
176
177    pub fn tag(&self) -> Option<Tag> {
178        self.base().and_then(|base| base.tag)
179    }
180
181    pub fn print(&self, tree: &mut PrintTree) {
182        match self {
183            Fragment::LayoutRoot(layout_root_fragment) => layout_root_fragment.inner().print(tree),
184            Fragment::Box(fragment) => fragment.print(tree),
185            Fragment::Float(fragment) => {
186                tree.new_level("Float".to_string());
187                fragment.print(tree);
188                tree.end_level();
189            },
190            Fragment::AbsoluteOrFixedPositionedPlaceholder(_) => {
191                tree.add_item("AbsoluteOrFixedPositioned".to_string());
192            },
193            Fragment::Positioning(fragment) => fragment.print(tree),
194            Fragment::Text(fragment) => fragment.print(tree),
195            Fragment::Image(fragment) => fragment.print(tree),
196            Fragment::IFrame(fragment) => fragment.print(tree),
197        }
198    }
199
200    pub(crate) fn scrolling_area(&self, layout_thread: &LayoutThread) -> PhysicalRect<Au> {
201        self.retrieve_box_fragment().map_or_else(
202            || self.scrollable_overflow_for_parent(),
203            |box_fragment| {
204                box_fragment.offset_by_containing_block(
205                    &box_fragment.with_style().scrollable_overflow(),
206                    layout_thread.into(),
207                )
208            },
209        )
210    }
211
212    /// Clear the scrollable overflow on this [`Fragment`]. This is called during damage
213    /// propagation when a fragment is preserved, itself or one of its descendants has
214    /// scrollable overflow damage.
215    pub(crate) fn clear_scrollable_overflow(&self) {
216        match self {
217            Fragment::LayoutRoot(fragment) => {
218                fragment.inner_box_fragment().clear_scrollable_overflow()
219            },
220            Fragment::Box(fragment) | Fragment::Float(fragment) => {
221                fragment.clear_scrollable_overflow()
222            },
223            Fragment::Positioning(fragment) => fragment.clear_scrollable_overflow(),
224            _ => {},
225        }
226    }
227
228    pub(crate) fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
229        match self {
230            Fragment::LayoutRoot(layout_root) => {
231                layout_root.inner().scrollable_overflow_for_parent()
232            },
233            Fragment::Box(fragment) | Fragment::Float(fragment) => {
234                fragment.with_style().scrollable_overflow_for_parent()
235            },
236            Fragment::Positioning(fragment) => fragment.scrollable_overflow_for_parent(),
237            Fragment::Text(fragment) => fragment.scrollable_overflow_for_parent(),
238            Fragment::AbsoluteOrFixedPositionedPlaceholder(_) |
239            Fragment::Image(..) |
240            Fragment::IFrame(..) => self.base().map(|base| base.rect()).unwrap_or_default(),
241        }
242    }
243
244    /// From <https://drafts.csswg.org/css-overflow-3/#scrollable>:
245    /// > This padding represents, within the scrollable overflow rectangle, the box’s own padding
246    /// > so that when its content is scrolled to its end, there is padding between the edge of its
247    /// > in-flow (or floated) content and the border edge of the box. It typically ends up being
248    /// > exactly the same size as the box’s own padding, except in a few cases—​such as when an
249    /// > out-of-flow positioned element, or the visible overflow of a descendent, has already
250    /// > increased the size of the scrollable overflow rectangle outside the conceptual “content
251    /// > edge” of the scroll container’s content.
252    pub(crate) fn scrollable_overflow_padding_contribution_for_parent(
253        &self,
254    ) -> Option<PhysicalRect<Au>> {
255        match self {
256            // TODO: This should consider the box in pre-relative-adjusted position state.
257            Fragment::Box(fragment) | Fragment::Float(fragment)
258                if !fragment
259                    .style()
260                    .get_box()
261                    .position
262                    .is_absolutely_positioned() =>
263            {
264                Some(fragment.margin_rect())
265            },
266            // Layout roots and absolutely positioned elements do not affect scrollable overflow
267            // for parents.
268            Fragment::Box(..) | Fragment::Float(..) | Fragment::LayoutRoot(..) => None,
269            // TODO: This rectangle does not include extra size from overflowing inline items. As
270            // this measurement is concerned with the actual fragments, it's quite likely that this
271            // rectangle should include that.
272            Fragment::Positioning(fragment) => Some(fragment.base.rect()),
273            Fragment::AbsoluteOrFixedPositionedPlaceholder(_) => None,
274            Fragment::Text(..) | Fragment::Image(..) | Fragment::IFrame(..) => {
275                Some(self.base()?.rect())
276            },
277        }
278    }
279
280    pub(crate) fn cumulative_box_area_rect(
281        &self,
282        area: BoxAreaType,
283        containing_block_computation: ContainingBlockCalculation<'_>,
284    ) -> Option<PhysicalRect<Au>> {
285        match self {
286            Fragment::LayoutRoot(layout_root_fragment) => layout_root_fragment
287                .inner()
288                .cumulative_box_area_rect(area, containing_block_computation),
289            Fragment::Box(fragment) | Fragment::Float(fragment) => Some(match area {
290                BoxAreaType::Content => {
291                    fragment.cumulative_content_box_rect(containing_block_computation)
292                },
293                BoxAreaType::Padding => {
294                    fragment.cumulative_padding_box_rect(containing_block_computation)
295                },
296                BoxAreaType::Border => {
297                    fragment.cumulative_border_box_rect(containing_block_computation)
298                },
299            }),
300            Fragment::Positioning(fragment) => {
301                Some(fragment.offset_by_containing_block(
302                    &fragment.base.rect(),
303                    containing_block_computation,
304                ))
305            },
306            Fragment::Text(_) |
307            Fragment::AbsoluteOrFixedPositionedPlaceholder(_) |
308            Fragment::Image(_) |
309            Fragment::IFrame(_) => None,
310        }
311    }
312
313    pub(crate) fn client_rect(&self) -> Rect<i32, CSSPixel> {
314        let Some(fragment) = self.retrieve_box_fragment() else {
315            return Rect::zero();
316        };
317        let fragment = fragment.with_style();
318
319        // https://drafts.csswg.org/cssom-view/#dom-element-clienttop
320        // " If the element has no associated CSS layout box or if the
321        //   CSS layout box is inline, return zero." For this check we
322        // also explicitly ignore the list item portion of the display
323        // style.
324        if fragment.is_inline_box() {
325            return Rect::zero();
326        }
327
328        let rect = if fragment.is_table_wrapper() {
329            // For tables the border actually belongs to the table grid box,
330            // so we need to include it in the dimension of the table wrapper box.
331            let mut rect = fragment.border_rect();
332            rect.origin = PhysicalPoint::zero();
333            rect
334        } else {
335            let mut rect = fragment.padding_rect();
336            rect.origin = PhysicalPoint::new(fragment.border.left, fragment.border.top);
337            rect
338        };
339
340        let rect = Rect::new(
341            Point2D::new(rect.origin.x.to_f32_px(), rect.origin.y.to_f32_px()),
342            Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()),
343        );
344        rect.round().to_i32()
345    }
346
347    pub(crate) fn children(&self) -> Option<RefOrAtomicRef<'_, Vec<Fragment>>> {
348        match self {
349            Fragment::LayoutRoot(fragment) => Some(RefOrAtomicRef::AtomicRef(AtomicRef::map(
350                fragment.inner_box_fragment(),
351                |fragment| &fragment.children,
352            ))),
353            Fragment::Box(fragment) | Fragment::Float(fragment) => {
354                Some(RefOrAtomicRef::Ref(&fragment.children))
355            },
356            Fragment::Positioning(fragment) => Some(RefOrAtomicRef::Ref(&fragment.children)),
357            _ => None,
358        }
359    }
360
361    pub(crate) fn find<T>(
362        &self,
363        manager: &ContainingBlockManager<PhysicalRect<Au>>,
364        level: usize,
365        process_func: &mut impl FnMut(&Fragment, usize, &PhysicalRect<Au>) -> Option<T>,
366    ) -> Option<T> {
367        let containing_block = manager.get_containing_block_for_fragment(self);
368        if let Some(result) = process_func(self, level, containing_block) {
369            return Some(result);
370        }
371
372        match self {
373            Fragment::LayoutRoot(layout_root_fragment) => {
374                layout_root_fragment
375                    .inner()
376                    .find(manager, level, process_func)
377            },
378            Fragment::Box(fragment) | Fragment::Float(fragment) => {
379                let style = fragment.style();
380                let content_rect = fragment
381                    .content_rect()
382                    .translate(containing_block.origin.to_vector());
383                let padding_rect = fragment
384                    .padding_rect()
385                    .translate(containing_block.origin.to_vector());
386                let new_manager = if style
387                    .establishes_containing_block_for_all_descendants(fragment.base.flags)
388                {
389                    manager.new_for_absolute_and_fixed_descendants(&content_rect, &padding_rect)
390                } else if style
391                    .establishes_containing_block_for_absolute_descendants(fragment.base.flags)
392                {
393                    manager.new_for_absolute_descendants(&content_rect, &padding_rect)
394                } else {
395                    manager.new_for_non_absolute_descendants(&content_rect)
396                };
397
398                fragment
399                    .children
400                    .iter()
401                    .find_map(|child| child.find(&new_manager, level + 1, process_func))
402            },
403            Fragment::Positioning(fragment) => {
404                let content_rect = fragment
405                    .base
406                    .rect()
407                    .translate(containing_block.origin.to_vector());
408                let new_manager = manager.new_for_non_absolute_descendants(&content_rect);
409                fragment
410                    .children
411                    .iter()
412                    .find_map(|child| child.find(&new_manager, level + 1, process_func))
413            },
414            _ => None,
415        }
416    }
417
418    pub(crate) fn repair_style(&self, new_style: &ServoArc<ComputedValues>) {
419        if let Some(base) = self.base() {
420            base.set_status(FragmentStatus::StyleChanged);
421        }
422
423        let inner_box_fragment;
424        let shared_style = match self {
425            Fragment::LayoutRoot(fragment) => {
426                inner_box_fragment = fragment.inner_box_fragment();
427                &inner_box_fragment.style
428            },
429            Fragment::Box(fragment) => &fragment.style,
430            Fragment::Text(fragment) => &fragment.run_data.inline_styles.style,
431            Fragment::AbsoluteOrFixedPositionedPlaceholder(_) => return,
432            Fragment::Positioning(fragment) => &fragment.style,
433            Fragment::Image(fragment) => &fragment.style,
434            Fragment::IFrame(fragment) => &fragment.style,
435            Fragment::Float(fragment) => &fragment.style,
436        };
437        *shared_style.borrow_mut() = new_style.clone();
438    }
439
440    pub(crate) fn retrieve_box_fragment(&self) -> Option<RefOrAtomicRef<'_, Arc<BoxFragment>>> {
441        match self {
442            Fragment::LayoutRoot(layout_root_fragment) => Some(RefOrAtomicRef::AtomicRef(
443                layout_root_fragment.inner_box_fragment(),
444            )),
445            Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
446                Some(RefOrAtomicRef::Ref(box_fragment))
447            },
448            _ => None,
449        }
450    }
451}
452
453impl TextFragment {
454    pub(crate) fn style<'a>(&'a self) -> AtomicRef<'a, ServoArc<ComputedValues>> {
455        self.run_data.inline_styles.style.borrow()
456    }
457
458    pub(crate) fn selected_style<'a>(&'a self) -> AtomicRef<'a, ServoArc<ComputedValues>> {
459        self.run_data.inline_styles.selected.borrow()
460    }
461
462    pub fn print(&self, tree: &mut PrintTree) {
463        tree.add_item(format!(
464            "Text num_glyphs={} box={:?}",
465            self.glyphs
466                .iter()
467                .map(|shaped_text_slice| shaped_text_slice.glyph_count())
468                .sum::<usize>(),
469            self.base.rect()
470        ));
471    }
472
473    /// Whether or not the given point is within the vertical boundaries of this
474    /// [`TextFragment`].
475    pub(crate) fn point_is_within_vertical_boundaries(
476        &self,
477        point_in_fragment: Point2D<Au, CSSPixel>,
478    ) -> bool {
479        let rect = &self.base.rect();
480        rect.min_y() <= point_in_fragment.y && rect.max_y() >= point_in_fragment.y
481    }
482
483    /// Find the distance between for point relative to a [`TextFragment`] for the
484    /// purposes of finding a glyph offset. This is used to identify the most relevant
485    /// fragment for glyph offset queries during click handling.
486    pub(crate) fn distance_to_point_for_glyph_offset(
487        &self,
488        point_in_fragment: Point2D<Au, CSSPixel>,
489    ) -> Au {
490        // point_in_fragment is alerady in a coordinate space where the fragment origin is (0, 0)
491        let rect = Rect::new(Point2D::origin(), self.base.rect().size);
492
493        // This is the distance between the closest point on the edge of the rectangle and
494        // the point. From <https://stackoverflow.com/a/18157551>.
495        let dx = (rect.min_x() - point_in_fragment.x)
496            .max(Au::zero())
497            .max(point_in_fragment.x - rect.max_x());
498        let dy = (rect.min_y() - point_in_fragment.y)
499            .max(Au::zero())
500            .max(point_in_fragment.y - rect.max_y());
501        Au::from_f64_px(dx.to_f64_px().hypot(dy.to_f64_px()))
502    }
503
504    /// Given a point relative to this [`TextFragment`], find the most appropriate
505    /// character offset.
506    ///
507    /// Note that the given point may be outside the [`TextFragment`]'s content rect:
508    ///
509    ///  - If the point is vertically above the [`TextFragment`] the first offset will be returned.
510    ///  - If the point is vertically below the [`TextFragment`], `None` will be returned.
511    pub(crate) fn character_offset(
512        &self,
513        point_in_fragment: Point2D<Au, CSSPixel>,
514    ) -> Option<Utf32CodeUnits> {
515        self.unmapped_character_offset(point_in_fragment)
516            .map(|character_offset| {
517                self.run_data
518                    .map_transformed_offset_to_dom_offset(character_offset)
519            })
520    }
521
522    /// Like [`Self::character_offset`], but returning the character offset in layout text (after
523    /// applying white space collapse and the `text-transform` property), instead of the original DOM
524    /// text.
525    fn unmapped_character_offset(
526        &self,
527        point_in_fragment: Point2D<Au, CSSPixel>,
528    ) -> Option<Utf32CodeUnits> {
529        // If the click was far enough above the top of the fragment, then pick the first index.
530        let max_vertical_offset = self.base.rect().height().scale_by(0.25);
531        if point_in_fragment.y < -max_vertical_offset {
532            return Some(self.character_range_in_dom_node.start);
533        }
534
535        // If the click was below the fragment, return `None`, which will cause the
536        // caller to move the cursor to the end.
537        //
538        // TODO: It would be nice to just return the last offset here, but <textarea>
539        // does not currently make a fragment for all selection indices.
540        if point_in_fragment.y > self.base.rect().max_y() + max_vertical_offset {
541            return None;
542        }
543
544        let mut current_character = self.character_range_in_dom_node.start;
545        let mut current_offset = Au::zero();
546        for glyph_store in &self.glyphs {
547            for glyph in glyph_store.glyphs() {
548                let mut advance = glyph.advance();
549                if glyph.char_is_word_separator() {
550                    advance += self.justification_adjustment;
551                }
552                if current_offset + advance.scale_by(0.5) >= point_in_fragment.x {
553                    return Some(current_character);
554                }
555                current_offset += advance;
556                current_character += glyph.character_count();
557            }
558        }
559
560        Some(current_character)
561    }
562
563    fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
564        let mut rect = self.base.rect();
565        let mut hanging_advance = Au::zero();
566        for slice in self.glyphs.iter().rev() {
567            hanging_advance += slice.hangable_advance();
568            if slice.has_non_hangable_non_removable_content() {
569                break;
570            }
571        }
572
573        // TODO: This is not correct for vertical and right-to-left writing modes.
574        rect.size.width -= hanging_advance;
575
576        rect
577    }
578}
579
580impl ImageFragment {
581    pub fn print(&self, tree: &mut PrintTree) {
582        tree.add_item(format!(
583            "Image\
584                \nrect={:?}",
585            self.base.rect()
586        ));
587    }
588}
589
590impl IFrameFragment {
591    pub fn print(&self, tree: &mut PrintTree) {
592        tree.add_item(format!(
593            "IFrame\
594                \npipeline={:?} rect={:?}",
595            self.pipeline_id,
596            self.base.rect()
597        ));
598    }
599}
600
601impl CollapsedBlockMargins {
602    pub fn from_margin(margin: &LogicalSides<Au>) -> Self {
603        Self {
604            collapsed_through: false,
605            start: CollapsedMargin::new(margin.block_start),
606            end: CollapsedMargin::new(margin.block_end),
607        }
608    }
609
610    pub fn zero() -> Self {
611        Self {
612            collapsed_through: false,
613            start: CollapsedMargin::zero(),
614            end: CollapsedMargin::zero(),
615        }
616    }
617}
618
619impl CollapsedMargin {
620    pub fn zero() -> Self {
621        Self {
622            max_positive: Au::zero(),
623            min_negative: Au::zero(),
624        }
625    }
626
627    pub fn new(margin: Au) -> Self {
628        Self {
629            max_positive: margin.max(Au::zero()),
630            min_negative: margin.min(Au::zero()),
631        }
632    }
633
634    pub fn adjoin(&self, other: &Self) -> Self {
635        Self {
636            max_positive: self.max_positive.max(other.max_positive),
637            min_negative: self.min_negative.min(other.min_negative),
638        }
639    }
640
641    pub fn adjoin_assign(&mut self, other: &Self) {
642        *self = self.adjoin(other);
643    }
644
645    pub fn solve(&self) -> Au {
646        self.max_positive + self.min_negative
647    }
648}
649
650/// A token which ensures the calculation and assignment of cumulative containing
651/// blocks to fragments in the fragment tree. This is used because these cumulative
652/// containing block offsets are set during stacking context tree construction, but
653/// some queries might need them beforehand. If the query is executed before stacking
654/// context tree construction, a quick traversal is performed to calculate them for
655/// the purpose of the query.
656pub(crate) enum ContainingBlockCalculation<'a> {
657    /// This token variant is for the purpose of a layout query. In this case, if stacking
658    /// context tree construction has not yet taken place, a cumulative containing block
659    /// calculation traversal will be performed.
660    Lazy { layout_thread: &'a LayoutThread },
661    /// This token variant is used when the code can guarantee that stacking context
662    /// tree construction has already taken place.
663    ///
664    /// Note: Using this before stacking context tree construction can lead
665    /// to incorrect layout or layout query results!
666    AlreadyDoneWithStackingContextTree,
667}
668
669impl ContainingBlockCalculation<'_> {
670    pub(crate) fn ensure(&self) {
671        match self {
672            Self::Lazy { layout_thread } => layout_thread.ensure_containing_block_calculation(),
673            Self::AlreadyDoneWithStackingContextTree => {},
674        }
675    }
676}
677
678impl<'a> From<&'a LayoutThread> for ContainingBlockCalculation<'a> {
679    fn from(layout_thread: &'a LayoutThread) -> Self {
680        Self::Lazy { layout_thread }
681    }
682}