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