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