Skip to main content

layout/fragment_tree/
box_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::cell::LazyCell;
6
7use app_units::{Au, MAX_AU, MIN_AU};
8use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
9use euclid::Rect;
10use malloc_size_of_derive::MallocSizeOf;
11use servo_arc::Arc as ServoArc;
12use servo_base::id::ScrollTreeNodeId;
13use servo_base::print_tree::PrintTree;
14use servo_geometry::f32_rect_to_au_rect;
15use style::Zero;
16use style::computed_values::border_collapse::T as BorderCollapse;
17use style::computed_values::overflow_x::T as ComputedOverflow;
18use style::computed_values::position::T as ComputedPosition;
19use style::logical_geometry::WritingMode;
20use style::properties::ComputedValues;
21
22use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment, FragmentFlags};
23use crate::SharedStyle;
24use crate::display_list::ToWebRender;
25use crate::formatting_contexts::Baselines;
26use crate::fragment_tree::ContainingBlockCalculation;
27use crate::geom::{
28    AuOrAuto, LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, ToLogical,
29};
30use crate::style_ext::ComputedValuesExt;
31use crate::table::SpecificTableGridInfo;
32use crate::taffy::SpecificTaffyGridInfo;
33
34/// Describes how a [`BoxFragment`] paints its background.
35#[derive(Clone, MallocSizeOf)]
36pub(crate) enum BackgroundMode {
37    /// Draw the normal [`BoxFragment`] background as well as the extra backgrounds
38    /// based on the style and positioning rectangles in this data structure.
39    Extra(Vec<ExtraBackground>),
40    /// Do not draw a background for this Fragment. This is used for elements like
41    /// table tracks and table track groups, which rely on cells to paint their
42    /// backgrounds.
43    None,
44    /// Draw the background normally, getting information from the Fragment style.
45    Normal,
46}
47
48#[derive(Clone, MallocSizeOf)]
49pub(crate) struct ExtraBackground {
50    pub style: SharedStyle,
51    pub rect: PhysicalRect<Au>,
52}
53
54#[derive(Clone, Debug, MallocSizeOf)]
55pub(crate) enum SpecificLayoutInfo {
56    Grid(Box<SpecificTaffyGridInfo>),
57    TableCellWithCollapsedBorders,
58    TableGridWithCollapsedBorders(Box<SpecificTableGridInfo>),
59    TableWrapper,
60}
61
62#[derive(Clone, MallocSizeOf)]
63pub(crate) struct BlockLevelLayoutInfo {
64    /// When the `clear` property is not set to `none`, it may introduce clearance.
65    /// Clearance is some extra spacing that is added above the top margin,
66    /// so that the element doesn't overlap earlier floats in the same BFC.
67    /// The presence of clearance prevents the top margin from collapsing with
68    /// earlier margins or with the bottom margin of the parent block.
69    /// <https://drafts.csswg.org/css2/#clearance>
70    pub clearance: Option<Au>,
71
72    pub block_margins_collapsed_with_children: CollapsedBlockMargins,
73}
74
75#[derive(Clone, Default, MallocSizeOf)]
76pub(crate) struct BoxFragmentRareData {
77    /// The resolved box insets if this box is `position: sticky`. These are calculated
78    /// during `StackingContextTree` construction because they rely on the size of the
79    /// scroll container.
80    pub(crate) resolved_sticky_insets: Option<Box<PhysicalSides<AuOrAuto>>>,
81
82    /// Information that is specific to a layout system (e.g., grid, table, etc.).
83    pub specific_layout_info: Option<SpecificLayoutInfo>,
84}
85
86impl BoxFragmentRareData {
87    /// Create a new rare data based on information given to the fragment. Ideally, We should
88    /// avoid creating rare data as much as possible to reduce the memory cost.
89    fn try_boxed_from(
90        specific_layout_info: Option<SpecificLayoutInfo>,
91    ) -> AtomicRefCell<Option<Box<Self>>> {
92        AtomicRefCell::new(specific_layout_info.map(|info| {
93            Box::new(BoxFragmentRareData {
94                resolved_sticky_insets: None,
95                specific_layout_info: Some(info),
96            })
97        }))
98    }
99}
100
101#[derive(MallocSizeOf)]
102pub(crate) struct BoxFragment {
103    pub base: BaseFragment,
104
105    pub children: Vec<Fragment>,
106
107    /// This [`BoxFragment`]'s containing block rectangle in coordinates relative to
108    /// the initial containing block, but not taking into account any transforms.
109    pub cumulative_containing_block_rect: AtomicRefCell<PhysicalRect<Au>>,
110
111    pub padding: PhysicalSides<Au>,
112    pub border: PhysicalSides<Au>,
113    pub margin: PhysicalSides<Au>,
114
115    /// When this [`BoxFragment`] is for content that has a baseline, this tracks
116    /// the first and last baselines of that content. This is used to propagate baselines
117    /// to things such as tables and inline formatting contexts.
118    baselines: Baselines,
119
120    /// The scrollable overflow of this box fragment in the same coordiante system as
121    /// [`Self::content_rect`] ie a rectangle within the parent fragment's content
122    /// rectangle. This does not take into account any transforms this fragment applies.
123    /// This is handled when calling [`Self::scrollable_overflow_for_parent`].
124    scrollable_overflow: AtomicRefCell<Option<PhysicalRect<Au>>>,
125
126    pub background_mode: BackgroundMode,
127
128    /// Rare data that not all kinds of [`BoxFragment`] would have.
129    pub rare_data: AtomicRefCell<Option<Box<BoxFragmentRareData>>>,
130
131    /// Additional information for block-level boxes.
132    pub block_level_layout_info: Option<Box<BlockLevelLayoutInfo>>,
133
134    /// The containing spatial tree node of this [`BoxFragment`]. This is assigned during
135    /// `StackingContextTree` construction, so isn't available before that time. This is
136    /// used to for determining final viewport size and position of this node and will
137    /// also be used in the future for hit testing.
138    pub spatial_tree_node: AtomicRefCell<Option<ScrollTreeNodeId>>,
139}
140
141impl BoxFragment {
142    #[expect(clippy::too_many_arguments)]
143    pub(crate) fn new(
144        base_fragment_info: BaseFragmentInfo,
145        style: ServoArc<ComputedValues>,
146        children: Vec<Fragment>,
147        content_rect: PhysicalRect<Au>,
148        padding: PhysicalSides<Au>,
149        border: PhysicalSides<Au>,
150        margin: PhysicalSides<Au>,
151        specific_layout_info: Option<SpecificLayoutInfo>,
152    ) -> Self {
153        let rare_data = BoxFragmentRareData::try_boxed_from(specific_layout_info);
154        Self {
155            base: BaseFragment::new(base_fragment_info, style.into(), content_rect),
156            children,
157            cumulative_containing_block_rect: Default::default(),
158            padding,
159            border,
160            margin,
161            baselines: Baselines::default(),
162            scrollable_overflow: Default::default(),
163            background_mode: BackgroundMode::Normal,
164            rare_data,
165            block_level_layout_info: None,
166            spatial_tree_node: AtomicRefCell::default(),
167        }
168    }
169
170    pub(crate) fn with_baselines(mut self, baselines: Baselines) -> Self {
171        self.baselines = baselines;
172        self
173    }
174
175    pub(crate) fn style<'a>(&'a self) -> AtomicRef<'a, ServoArc<ComputedValues>> {
176        self.base.style()
177    }
178
179    /// Get the baselines for this [`BoxFragment`] if they are compatible with the given [`WritingMode`].
180    /// If they are not compatible, [`Baselines::default()`] is returned.
181    pub(crate) fn baselines(&self, writing_mode: WritingMode) -> Baselines {
182        let style = self.style();
183        let mut baselines = if writing_mode.is_horizontal() == style.writing_mode.is_horizontal() {
184            self.baselines
185        } else {
186            // If the writing mode of the container requesting baselines is not
187            // compatible, ensure that the baselines established by this fragment are
188            // not used.
189            Baselines::default()
190        };
191
192        // From the https://drafts.csswg.org/css-align-3/#baseline-export section on "block containers":
193        // > However, for legacy reasons if its baseline-source is auto (the initial
194        // > value) a block-level or inline-level block container that is a scroll container
195        // > always has a last baseline set, whose baselines all correspond to its block-end
196        // > margin edge.
197        //
198        // This applies even if there is no baseline set, so we unconditionally set the value here
199        // and ignore anything that is set via [`Self::with_baselines`].
200        if style.establishes_scroll_container(self.base.flags) {
201            let content_rect_size = self.content_rect().size.to_logical(writing_mode);
202            let padding = self.padding.to_logical(writing_mode);
203            let border = self.border.to_logical(writing_mode);
204            let margin = self.margin.to_logical(writing_mode);
205            baselines.last = Some(
206                content_rect_size.block + padding.block_end + border.block_end + margin.block_end,
207            )
208        }
209        baselines
210    }
211
212    pub(crate) fn add_extra_background(&mut self, extra_background: ExtraBackground) {
213        match self.background_mode {
214            BackgroundMode::Extra(ref mut backgrounds) => backgrounds.push(extra_background),
215            _ => self.background_mode = BackgroundMode::Extra(vec![extra_background]),
216        }
217    }
218
219    pub(crate) fn set_does_not_paint_background(&mut self) {
220        self.background_mode = BackgroundMode::None;
221    }
222
223    pub(crate) fn ensure_rare_data(&self) -> AtomicRefMut<'_, Box<BoxFragmentRareData>> {
224        let mut rare_data = self.rare_data.borrow_mut();
225        if rare_data.is_none() {
226            *rare_data = Some(Default::default());
227        }
228
229        AtomicRefMut::map(rare_data, |rare_data| {
230            rare_data
231                .as_mut()
232                .expect("This data should have just been set")
233        })
234    }
235
236    pub(crate) fn specific_layout_info(&self) -> Option<AtomicRef<'_, SpecificLayoutInfo>> {
237        let rare_data = self.rare_data.borrow();
238
239        AtomicRef::filter_map(rare_data, |rare_data| {
240            rare_data.as_ref()?.specific_layout_info.as_ref()
241        })
242    }
243
244    pub(crate) fn resolved_sticky_insets(
245        &self,
246    ) -> Option<AtomicRef<'_, Box<PhysicalSides<AuOrAuto>>>> {
247        let rare_data = self.rare_data.borrow();
248
249        AtomicRef::filter_map(rare_data, |rare_data| {
250            rare_data.as_ref()?.resolved_sticky_insets.as_ref()
251        })
252    }
253
254    pub(crate) fn with_block_level_layout_info(
255        mut self,
256        block_margins_collapsed_with_children: CollapsedBlockMargins,
257        clearance: Option<Au>,
258    ) -> Self {
259        self.block_level_layout_info = Some(Box::new(BlockLevelLayoutInfo {
260            block_margins_collapsed_with_children,
261            clearance,
262        }));
263        self
264    }
265
266    /// Get the scrollable overflow for this [`BoxFragment`] relative to its containing
267    /// block, recalculating scrollable overflow when necessary, for instance after a
268    /// style change.
269    pub(crate) fn scrollable_overflow(&self) -> PhysicalRect<Au> {
270        *self
271            .scrollable_overflow
272            .borrow_mut()
273            .get_or_insert_with(|| self.calculate_scrollable_overflow())
274    }
275
276    /// Clear the scrollable overflow on this [`BoxFragment`]. This is called
277    /// during damage propagation when a fragment is preserved, itself or one of its
278    /// descendants has scrollable overflow damage.
279    pub(crate) fn clear_scrollable_overflow(&self) {
280        *self.scrollable_overflow.borrow_mut() = None;
281    }
282
283    /// This is an implementation of:
284    /// - <https://drafts.csswg.org/css-overflow-3/#scrollable>.
285    /// - <https://drafts.csswg.org/cssom-view/#scrolling-area>
286    fn calculate_scrollable_overflow(&self) -> PhysicalRect<Au> {
287        // Fragments with `IS_COLLAPSED` (currently only table cells that are part of
288        // table tracks with `visibility: collapse`) should not contribute to scrollable
289        // overflow. This behavior matches Chrome, but not Firefox.
290        // See https://github.com/w3c/csswg-drafts/issues/12689
291        if self.base.flags.contains(FragmentFlags::IS_COLLAPSED) {
292            return Rect::zero();
293        }
294
295        let physical_padding_rect = self.padding_rect();
296        let content_origin = self.base.rect().origin.to_vector();
297
298        // > The scrollable overflow area is the union of:
299        // > * The scroll container’s own padding box.
300        // > * All line boxes directly contained by the scroll container.
301        // > * The border boxes of all boxes for which it is the containing block and
302        // >   whose border boxes are positioned not wholly in the unreachable
303        // >   scrollable overflow region, accounting for transforms by projecting
304        // >   each box onto the plane of the element that establishes its 3D
305        // >   rendering context.
306        // > * The margin areas of grid item and flex item boxes for which the box
307        // >   establishes a containing block.
308        // > * The scrollable overflow areas of all of the above boxes (including zero-area
309        // >   boxes and accounting for transforms as described above), provided they
310        // >   themselves have overflow: visible (i.e. do not themselves trap the overflow)
311        // >   and that scrollable overflow is not already clipped (e.g. by the clip property
312        // >   or the contain property).
313        // > * Additional padding added to the scrollable overflow rectangle as necessary
314        //     to enable scroll positions that satisfy the requirements of both place-content:
315        //     start and place-content: end alignment.
316        //
317        // TODO(mrobinson): Below we are handling the border box and the scrollable
318        // overflow together, but from the specification it seems that if the border
319        // box of an item is in the "wholly unreachable scrollable overflow region", but
320        // its scrollable overflow is not, it should also be excluded.
321        let mut scrollable_overflow =
322            self.children
323                .iter()
324                .fold(physical_padding_rect, |acc, child| {
325                    let scrollable_overflow_from_child = child
326                        .scrollable_overflow_for_parent()
327                        .translate(content_origin);
328
329                    // Note that this doesn't just exclude the wholly unreachable
330                    // scrollable overflow area from the rectangle, but also clips it.
331                    // This makes the resulting value more like the "scroll area" rather
332                    // than the "scrollable overflow."
333                    let scrollable_overflow_from_child = self
334                        .clip_wholly_unreachable_scrollable_overflow(
335                            scrollable_overflow_from_child,
336                            physical_padding_rect,
337                        );
338
339                    acc.union(&scrollable_overflow_from_child)
340                });
341
342        // From <https://drafts.csswg.org/css-overflow-3/#scrollable>:
343        // > Additional padding added to the scrollable overflow rectangle as necessary to
344        // > enable scroll positions that satisfy the requirements of both place-content:
345        // > start and place-content: end alignment.
346        //
347        // Whether we should include additional padding to the scrollable overflow to satisfy
348        // the requirements of `place-content: start` and `place-content: end` for boxes that
349        // establish scroll containers. Note: This padding is different from the CSS concept
350        // of padding. See <https://github.com/w3c/csswg-drafts/issues/129>.
351        //
352        // TODO: For input elements, we also disable the padding in the inline direction, as we
353        // do not have a way to scroll the textual input element in inline direction yet.
354        let should_include_additional_padding =
355            self.style().establishes_scroll_container(self.base.flags) &&
356                !self.base.flags.intersects(FragmentFlags::IS_INPUT_ELEMENT);
357
358        if should_include_additional_padding {
359            scrollable_overflow = self
360                .children
361                .iter()
362                .fold(scrollable_overflow, |acc, child| {
363                    let Some(padding_contribution) =
364                        child.scrollable_overflow_padding_contribution_for_parent()
365                    else {
366                        return acc;
367                    };
368
369                    let padding_contribution = padding_contribution
370                        .translate(content_origin)
371                        .outer_rect(self.padding);
372
373                    // Applying padding could also cause the rectangle to overflow to
374                    // the wholly unreachable scrollable overflow, clipping the overflow
375                    // here prevents this.
376                    let padding_contribution = self.clip_wholly_unreachable_scrollable_overflow(
377                        padding_contribution,
378                        physical_padding_rect,
379                    );
380
381                    acc.union(&padding_contribution)
382                });
383        }
384        scrollable_overflow
385    }
386
387    pub(crate) fn set_containing_block(&self, containing_block: &PhysicalRect<Au>) {
388        *self.cumulative_containing_block_rect.borrow_mut() = *containing_block;
389    }
390
391    pub(crate) fn offset_by_containing_block(
392        &self,
393        rect: &PhysicalRect<Au>,
394        containing_block_computation: ContainingBlockCalculation<'_>,
395    ) -> PhysicalRect<Au> {
396        containing_block_computation.ensure();
397        rect.translate(
398            self.cumulative_containing_block_rect
399                .borrow()
400                .origin
401                .to_vector(),
402        )
403    }
404
405    pub(crate) fn cumulative_content_box_rect(
406        &self,
407        containing_block_computation: ContainingBlockCalculation<'_>,
408    ) -> PhysicalRect<Au> {
409        self.offset_by_containing_block(&self.base.rect(), containing_block_computation)
410    }
411
412    pub(crate) fn cumulative_padding_box_rect(
413        &self,
414        containing_block_computation: ContainingBlockCalculation<'_>,
415    ) -> PhysicalRect<Au> {
416        self.offset_by_containing_block(&self.padding_rect(), containing_block_computation)
417    }
418
419    pub(crate) fn cumulative_border_box_rect(
420        &self,
421        containing_block_computation: ContainingBlockCalculation<'_>,
422    ) -> PhysicalRect<Au> {
423        self.offset_by_containing_block(&self.border_rect(), containing_block_computation)
424    }
425
426    pub(crate) fn content_rect(&self) -> PhysicalRect<Au> {
427        self.base.rect()
428    }
429
430    pub(crate) fn padding_rect(&self) -> PhysicalRect<Au> {
431        self.content_rect().outer_rect(self.padding)
432    }
433
434    pub(crate) fn border_rect(&self) -> PhysicalRect<Au> {
435        self.padding_rect().outer_rect(self.border)
436    }
437
438    pub(crate) fn margin_rect(&self) -> PhysicalRect<Au> {
439        self.border_rect().outer_rect(self.margin)
440    }
441
442    pub(crate) fn padding_border_margin(&self) -> PhysicalSides<Au> {
443        self.margin + self.border + self.padding
444    }
445
446    pub(crate) fn is_root_element(&self) -> bool {
447        self.base.flags.intersects(FragmentFlags::IS_ROOT_ELEMENT)
448    }
449
450    pub(crate) fn is_body_element_of_html_element_root(&self) -> bool {
451        self.base
452            .flags
453            .intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT)
454    }
455
456    pub(crate) fn print(&self, tree: &mut PrintTree) {
457        tree.new_level(format!(
458            "Box\
459                \nbase={:?}\
460                \ncontent={:?}\
461                \npadding rect={:?}\
462                \nborder rect={:?}\
463                \nmargin={:?}\
464                \nscrollable_overflow={:?}\
465                \nbaselines={:?}\
466                \noverflow={:?}",
467            self.base,
468            self.content_rect(),
469            self.padding_rect(),
470            self.border_rect(),
471            self.margin,
472            self.scrollable_overflow(),
473            self.baselines,
474            self.style().effective_overflow(self.base.flags),
475        ));
476
477        for child in &self.children {
478            child.print(tree);
479        }
480        tree.end_level();
481    }
482
483    pub(crate) fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
484        let style = self.style();
485        let mut overflow = self.border_rect();
486        if !style.establishes_scroll_container(self.base.flags) {
487            // https://www.w3.org/TR/css-overflow-3/#scrollable
488            // Only include the scrollable overflow of a child box if it has overflow: visible.
489            let scrollable_overflow = self.scrollable_overflow();
490            let bottom_right = PhysicalPoint::new(
491                overflow.max_x().max(scrollable_overflow.max_x()),
492                overflow.max_y().max(scrollable_overflow.max_y()),
493            );
494
495            let overflow_style = style.effective_overflow(self.base.flags);
496            if overflow_style.y == ComputedOverflow::Visible {
497                overflow.origin.y = overflow.origin.y.min(scrollable_overflow.origin.y);
498                overflow.size.height = bottom_right.y - overflow.origin.y;
499            }
500
501            if overflow_style.x == ComputedOverflow::Visible {
502                overflow.origin.x = overflow.origin.x.min(scrollable_overflow.origin.x);
503                overflow.size.width = bottom_right.x - overflow.origin.x;
504            }
505        }
506
507        if !style.has_effective_transform_or_perspective(self.base.flags) {
508            return overflow;
509        }
510
511        // <https://drafts.csswg.org/css-overflow-3/#scrollable-overflow-region>
512        // > ...accounting for transforms by projecting each box onto the plane of
513        // > the element that establishes its 3D rendering context. [CSS3-TRANSFORMS]
514        // Both boxes and its scrollable overflow (if it is included) should be transformed accordingly.
515        //
516        // TODO(stevennovaryo): We are supposed to handle perspective transform and 3d
517        // contexts, but it is yet to happen.
518        self.calculate_transform_matrix(&self.border_rect())
519            .and_then(|transform| {
520                transform.outer_transformed_rect(&overflow.to_webrender().to_rect())
521            })
522            .map(|transformed_rect| f32_rect_to_au_rect(transformed_rect).cast_unit())
523            .unwrap_or(overflow)
524    }
525
526    /// Return the clipped scrollable overflow based on its scroll origin, determined by
527    /// overflow direction. Return [`None`] if the scrollable overflow from child is wholly
528    /// unreachable. For an element, the clip rect is the padding rect and for viewport,
529    /// it is the initial containing block.
530    pub(crate) fn clip_wholly_unreachable_scrollable_overflow(
531        &self,
532        scrollable_overflow_from_child: PhysicalRect<Au>,
533        clipping_rect: PhysicalRect<Au>,
534    ) -> PhysicalRect<Au> {
535        // From <https://drafts.csswg.org/css-overflow/#unreachable-scrollable-overflow-region>:
536        // > Unless otherwise adjusted (e.g. by content alignment [css-align-3]), the area
537        // > beyond the scroll origin in either axis is considered the unreachable scrollable
538        // > overflow region: content rendered here is not accessible to the reader, see § 2.2
539        // > Scrollable Overflow. A scroll container is said to be scrolled to its scroll
540        // > origin when its scroll origin coincides with the corresponding corner of its
541        // > scrollport. This scroll position, the scroll origin position, usually, but not
542        // > always, coincides with the initial scroll position.
543        let scrolling_direction = self.style().overflow_direction();
544        let mut clipping_box = clipping_rect.to_box2d();
545        if scrolling_direction.rightward {
546            clipping_box.max.x = MAX_AU;
547        } else {
548            clipping_box.min.x = MIN_AU;
549        }
550
551        if scrolling_direction.downward {
552            clipping_box.max.y = MAX_AU;
553        } else {
554            clipping_box.min.y = MIN_AU;
555        }
556
557        let scrollable_overflow_box = scrollable_overflow_from_child
558            .to_box2d()
559            .intersection_unchecked(&clipping_box);
560
561        match scrollable_overflow_box.is_negative() {
562            false => scrollable_overflow_box.to_rect(),
563            true => Rect::zero(),
564        }
565    }
566
567    pub(crate) fn calculate_resolved_insets_if_positioned(
568        &self,
569        containing_block_computation: ContainingBlockCalculation<'_>,
570    ) -> PhysicalSides<AuOrAuto> {
571        let style = self.style();
572        let position = style.get_box().position;
573        debug_assert_ne!(
574            position,
575            ComputedPosition::Static,
576            "Should not call this method on statically positioned box."
577        );
578
579        if let Some(resolved_sticky_insets) = self.resolved_sticky_insets() {
580            return **resolved_sticky_insets;
581        }
582
583        let convert_to_au_or_auto = |sides: PhysicalSides<Au>| {
584            PhysicalSides::new(
585                AuOrAuto::LengthPercentage(sides.top),
586                AuOrAuto::LengthPercentage(sides.right),
587                AuOrAuto::LengthPercentage(sides.bottom),
588                AuOrAuto::LengthPercentage(sides.left),
589            )
590        };
591
592        let containing_block_size = LazyCell::new(|| {
593            containing_block_computation.ensure();
594            self.cumulative_containing_block_rect.borrow().size
595        });
596
597        // "A resolved value special case property like top defined in another
598        // specification If the property applies to a positioned element and the
599        // resolved value of the display property is not none or contents, and
600        // the property is not over-constrained, then the resolved value is the
601        // used value. Otherwise the resolved value is the computed value."
602        // https://drafts.csswg.org/cssom/#resolved-values
603        let insets = style.physical_box_offsets();
604        if position == ComputedPosition::Relative {
605            let get_resolved_axis = |start: &LengthPercentageOrAuto,
606                                     end: &LengthPercentageOrAuto,
607                                     container_length: Au| {
608                let start = start.map(|value| value.to_used_value(container_length));
609                let end = end.map(|value| value.to_used_value(container_length));
610                match (start.non_auto(), end.non_auto()) {
611                    (None, None) => (Au::zero(), Au::zero()),
612                    (None, Some(end)) => (-end, end),
613                    (Some(start), None) => (start, -start),
614                    // This is the overconstrained case, for which the resolved insets will
615                    // simply be the computed insets.
616                    (Some(start), Some(end)) => (start, end),
617                }
618            };
619
620            let (left, right) =
621                get_resolved_axis(&insets.left, &insets.right, containing_block_size.width);
622            let (top, bottom) =
623                get_resolved_axis(&insets.top, &insets.bottom, containing_block_size.height);
624            return convert_to_au_or_auto(PhysicalSides::new(top, right, bottom, left));
625        }
626
627        debug_assert!(position.is_absolutely_positioned());
628
629        let margin_rect = self.margin_rect();
630        let (top, bottom) = match (&insets.top, &insets.bottom) {
631            (
632                LengthPercentageOrAuto::LengthPercentage(top),
633                LengthPercentageOrAuto::LengthPercentage(bottom),
634            ) => (
635                top.to_used_value(containing_block_size.height),
636                bottom.to_used_value(containing_block_size.height),
637            ),
638            _ => (
639                margin_rect.origin.y,
640                containing_block_size.height - margin_rect.max_y(),
641            ),
642        };
643        let (left, right) = match (&insets.left, &insets.right) {
644            (
645                LengthPercentageOrAuto::LengthPercentage(left),
646                LengthPercentageOrAuto::LengthPercentage(right),
647            ) => (
648                left.to_used_value(containing_block_size.width),
649                right.to_used_value(containing_block_size.width),
650            ),
651            _ => (
652                margin_rect.origin.x,
653                containing_block_size.width - margin_rect.max_x(),
654            ),
655        };
656
657        convert_to_au_or_auto(PhysicalSides::new(top, right, bottom, left))
658    }
659
660    /// Whether this is a non-replaced inline-level box whose inner display type is `flow`.
661    /// <https://drafts.csswg.org/css-display-3/#inline-box>
662    pub(crate) fn is_inline_box(&self) -> bool {
663        self.style().is_inline_box(self.base.flags)
664    }
665
666    /// Whether this is an atomic inline-level box.
667    /// <https://drafts.csswg.org/css-display-3/#atomic-inline>
668    pub(crate) fn is_atomic_inline_level(&self) -> bool {
669        self.style().is_atomic_inline_level(self.base.flags)
670    }
671
672    /// Whether this is a table wrapper box.
673    /// <https://www.w3.org/TR/css-tables-3/#table-wrapper-box>
674    pub(crate) fn is_table_wrapper(&self) -> bool {
675        matches!(
676            self.specific_layout_info().as_deref(),
677            Some(SpecificLayoutInfo::TableWrapper)
678        )
679    }
680
681    pub(crate) fn has_collapsed_borders(&self) -> bool {
682        match self.specific_layout_info().as_deref() {
683            Some(SpecificLayoutInfo::TableCellWithCollapsedBorders) => true,
684            Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(_)) => true,
685            Some(SpecificLayoutInfo::TableWrapper) => {
686                self.style().get_inherited_table().border_collapse == BorderCollapse::Collapse
687            },
688            _ => false,
689        }
690    }
691
692    pub(crate) fn spatial_tree_node(&self) -> Option<ScrollTreeNodeId> {
693        *self.spatial_tree_node.borrow()
694    }
695}