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