1use 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#[derive(Clone, MallocSizeOf)]
36pub(crate) enum BackgroundMode {
37 Extra(Vec<ExtraBackground>),
40 None,
44 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 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 pub(crate) resolved_sticky_insets: Option<Box<PhysicalSides<AuOrAuto>>>,
81
82 pub specific_layout_info: Option<SpecificLayoutInfo>,
84}
85
86impl BoxFragmentRareData {
87 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 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 baselines: Baselines,
119
120 scrollable_overflow: AtomicRefCell<Option<PhysicalRect<Au>>>,
125
126 pub background_mode: BackgroundMode,
127
128 pub rare_data: AtomicRefCell<Option<Box<BoxFragmentRareData>>>,
130
131 pub block_level_layout_info: Option<Box<BlockLevelLayoutInfo>>,
133
134 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 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 Baselines::default()
190 };
191
192 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 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 pub(crate) fn clear_scrollable_overflow(&self) {
280 *self.scrollable_overflow.borrow_mut() = None;
281 }
282
283 fn calculate_scrollable_overflow(&self) -> PhysicalRect<Au> {
287 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 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 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 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 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 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 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 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 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 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 (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 pub(crate) fn is_inline_box(&self) -> bool {
663 self.style().is_inline_box(self.base.flags)
664 }
665
666 pub(crate) fn is_atomic_inline_level(&self) -> bool {
669 self.style().is_atomic_inline_level(self.base.flags)
670 }
671
672 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}