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