1use 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#[derive(Clone, MallocSizeOf)]
40pub(crate) enum BackgroundMode {
41 Extra(Vec<ExtraBackground>),
44 None,
48 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 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 pub(crate) resolved_sticky_insets: Option<Box<PhysicalSides<AuOrAuto>>>,
85
86 pub specific_layout_info: Option<SpecificLayoutInfo>,
88
89 pub generated_clip_id: Option<ClipId>,
92
93 pub generated_scroll_tree_node_id: Option<ScrollTreeNodeId>,
96}
97
98impl BoxFragmentRareData {
99 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 pub children: Vec<Fragment>,
120
121 pub cumulative_containing_block_rect: SyncPhysicalRectAu,
124
125 pub padding: PhysicalSides<Au>,
126 pub border: PhysicalSides<Au>,
127 pub margin: PhysicalSides<Au>,
128
129 baselines: Baselines,
133
134 scrollable_overflow: SyncPhysicalRectAu,
139 scrollable_overflow_is_up_to_date: AtomicBool,
140
141 pub background_mode: BackgroundMode,
142
143 pub rare_data: OnceBox<AtomicRefCell<BoxFragmentRareData>>,
145
146 pub block_level_layout_info: Option<Box<BlockLevelLayoutInfo>>,
148
149 pub spatial_tree_node: AtomicOptionScrollTreeNodeId,
154}
155
156impl BoxFragment {
157 #[expect(clippy::too_many_arguments)]
158 pub(crate) fn new(
159 base_fragment_info: BaseFragmentInfo,
160 style: ServoArc<ComputedValues>,
161 children: Vec<Fragment>,
162 content_rect: PhysicalRect<Au>,
163 padding: PhysicalSides<Au>,
164 border: PhysicalSides<Au>,
165 margin: PhysicalSides<Au>,
166 specific_layout_info: Option<SpecificLayoutInfo>,
167 ) -> Self {
168 let rare_data = BoxFragmentRareData::new(specific_layout_info);
169 Self {
170 base: BaseFragment::new(base_fragment_info, style.into(), content_rect),
171 children,
172 cumulative_containing_block_rect: Default::default(),
173 padding,
174 border,
175 margin,
176 baselines: Baselines::default(),
177 scrollable_overflow: Default::default(),
178 scrollable_overflow_is_up_to_date: AtomicBool::new(false),
179 background_mode: BackgroundMode::Normal,
180 rare_data,
181 block_level_layout_info: None,
182 spatial_tree_node: AtomicOptionScrollTreeNodeId::new(None),
183 }
184 }
185
186 pub(crate) fn with_baselines(mut self, baselines: Baselines) -> Self {
187 self.baselines = baselines;
188 self
189 }
190
191 pub(crate) fn style<'a>(&'a self) -> AtomicRef<'a, ServoArc<ComputedValues>> {
192 self.base.style()
193 }
194
195 pub(crate) fn with_style(self: &Arc<Self>) -> BoxFragmentWithStyle<'_> {
196 BoxFragmentWithStyle {
197 box_fragment: self,
198 style: self.style(),
199 }
200 }
201
202 pub(crate) fn baselines(&self, writing_mode: WritingMode) -> Baselines {
205 let style = self.style();
206 let mut baselines = if writing_mode.is_horizontal() == style.writing_mode.is_horizontal() {
207 self.baselines
208 } else {
209 Baselines::default()
213 };
214
215 if style.establishes_scroll_container(self.base.flags) {
224 let content_rect_size = self.content_rect().size.to_logical(writing_mode);
225 let padding = self.padding.to_logical(writing_mode);
226 let border = self.border.to_logical(writing_mode);
227 let margin = self.margin.to_logical(writing_mode);
228 baselines.last = Some(
229 content_rect_size.block + padding.block_end + border.block_end + margin.block_end,
230 )
231 }
232 baselines
233 }
234
235 pub(crate) fn add_extra_background(&mut self, extra_background: ExtraBackground) {
236 match self.background_mode {
237 BackgroundMode::Extra(ref mut backgrounds) => backgrounds.push(extra_background),
238 _ => self.background_mode = BackgroundMode::Extra(vec![extra_background]),
239 }
240 }
241
242 pub(crate) fn set_does_not_paint_background(&mut self) {
243 self.background_mode = BackgroundMode::None;
244 }
245
246 pub(crate) fn ensure_rare_data(&self) -> AtomicRefMut<'_, BoxFragmentRareData> {
247 self.rare_data.get_or_init(Default::default).borrow_mut()
248 }
249
250 pub(crate) fn specific_layout_info(&self) -> Option<AtomicRef<'_, SpecificLayoutInfo>> {
251 let rare_data = self.rare_data.get()?.borrow();
252
253 AtomicRef::filter_map(rare_data, |rare_data| {
254 rare_data.specific_layout_info.as_ref()
255 })
256 }
257
258 pub(crate) fn resolved_sticky_insets(
259 &self,
260 ) -> Option<AtomicRef<'_, Box<PhysicalSides<AuOrAuto>>>> {
261 let rare_data = self.rare_data.get()?.borrow();
262
263 AtomicRef::filter_map(rare_data, |rare_data| {
264 rare_data.resolved_sticky_insets.as_ref()
265 })
266 }
267
268 pub(crate) fn set_resolved_sticky_insets(&self, sticky_insets: PhysicalSides<AuOrAuto>) {
269 self.ensure_rare_data().resolved_sticky_insets = Some(sticky_insets.into());
270 }
271
272 pub(crate) fn generated_clip_id(&self) -> Option<ClipId> {
273 self.rare_data.get()?.borrow().generated_clip_id
274 }
275
276 pub(crate) fn set_generated_clip_id(&self, generated_clip_id: ClipId) {
277 self.ensure_rare_data().generated_clip_id = Some(generated_clip_id);
278 }
279
280 pub(crate) fn generated_scroll_tree_node_id(&self) -> Option<ScrollTreeNodeId> {
281 self.rare_data.get()?.borrow().generated_scroll_tree_node_id
282 }
283
284 pub(crate) fn set_generated_scroll_tree_node_id(
285 &self,
286 generated_scroll_tree_node_id: ScrollTreeNodeId,
287 ) {
288 self.ensure_rare_data().generated_scroll_tree_node_id = Some(generated_scroll_tree_node_id);
289 }
290
291 pub(crate) fn with_block_level_layout_info(
292 mut self,
293 block_margins_collapsed_with_children: CollapsedBlockMargins,
294 clearance: Option<Au>,
295 ) -> Self {
296 self.block_level_layout_info = Some(Box::new(BlockLevelLayoutInfo {
297 block_margins_collapsed_with_children,
298 clearance,
299 }));
300 self
301 }
302
303 pub(crate) fn clear_scrollable_overflow(&self) {
307 self.scrollable_overflow_is_up_to_date
308 .store(false, Ordering::Release);
309 }
310
311 #[inline]
312 pub(crate) fn set_containing_block(&self, containing_block: &PhysicalRect<Au>) {
313 self.cumulative_containing_block_rect.set(*containing_block);
314 }
315
316 pub(crate) fn offset_by_containing_block(
317 &self,
318 rect: &PhysicalRect<Au>,
319 containing_block_computation: ContainingBlockCalculation<'_>,
320 ) -> PhysicalRect<Au> {
321 containing_block_computation.ensure();
322 rect.translate(self.cumulative_containing_block_rect.origin().to_vector())
323 }
324
325 pub(crate) fn cumulative_content_box_rect(
326 &self,
327 containing_block_computation: ContainingBlockCalculation<'_>,
328 ) -> PhysicalRect<Au> {
329 self.offset_by_containing_block(&self.base.rect(), containing_block_computation)
330 }
331
332 pub(crate) fn cumulative_padding_box_rect(
333 &self,
334 containing_block_computation: ContainingBlockCalculation<'_>,
335 ) -> PhysicalRect<Au> {
336 self.offset_by_containing_block(&self.padding_rect(), containing_block_computation)
337 }
338
339 pub(crate) fn cumulative_border_box_rect(
340 &self,
341 containing_block_computation: ContainingBlockCalculation<'_>,
342 ) -> PhysicalRect<Au> {
343 self.offset_by_containing_block(&self.border_rect(), containing_block_computation)
344 }
345
346 pub(crate) fn content_rect(&self) -> PhysicalRect<Au> {
347 self.base.rect()
348 }
349
350 pub(crate) fn padding_rect(&self) -> PhysicalRect<Au> {
351 self.content_rect().outer_rect(self.padding)
352 }
353
354 pub(crate) fn border_rect(&self) -> PhysicalRect<Au> {
355 self.padding_rect().outer_rect(self.border)
356 }
357
358 pub(crate) fn margin_rect(&self) -> PhysicalRect<Au> {
359 self.border_rect().outer_rect(self.margin)
360 }
361
362 pub(crate) fn padding_border_margin(&self) -> PhysicalSides<Au> {
363 self.margin + self.border + self.padding
364 }
365
366 pub(crate) fn is_root_element(&self) -> bool {
367 self.base.flags.intersects(FragmentFlags::IS_ROOT_ELEMENT)
368 }
369
370 pub(crate) fn is_body_element_of_html_element_root(&self) -> bool {
371 self.base
372 .flags
373 .intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT)
374 }
375
376 pub(crate) fn print(self: &Arc<Self>, tree: &mut PrintTree) {
377 let with_style = self.with_style();
378 tree.new_level(format!(
379 "Box\
380 \nbase={:?}\
381 \ncontent={:?}\
382 \npadding rect={:?}\
383 \nborder rect={:?}\
384 \nmargin={:?}\
385 \nscrollable_overflow={:?}\
386 \nbaselines={:?}\
387 \noverflow={:?}",
388 self.base,
389 self.content_rect(),
390 self.padding_rect(),
391 self.border_rect(),
392 self.margin,
393 with_style.scrollable_overflow(),
394 self.baselines,
395 with_style.style().effective_overflow(self.base.flags),
396 ));
397
398 for child in &self.children {
399 child.print(tree);
400 }
401 tree.end_level();
402 }
403
404 pub(crate) fn calculate_resolved_insets_if_positioned(
405 &self,
406 containing_block_computation: ContainingBlockCalculation<'_>,
407 ) -> PhysicalSides<AuOrAuto> {
408 let style = self.style();
409 let position = style.get_box().position;
410 debug_assert_ne!(
411 position,
412 ComputedPosition::Static,
413 "Should not call this method on statically positioned box."
414 );
415
416 if let Some(resolved_sticky_insets) = self.resolved_sticky_insets() {
417 return **resolved_sticky_insets;
418 }
419
420 let convert_to_au_or_auto = |sides: PhysicalSides<Au>| {
421 PhysicalSides::new(
422 AuOrAuto::LengthPercentage(sides.top),
423 AuOrAuto::LengthPercentage(sides.right),
424 AuOrAuto::LengthPercentage(sides.bottom),
425 AuOrAuto::LengthPercentage(sides.left),
426 )
427 };
428
429 let containing_block_size = LazyCell::new(|| {
430 containing_block_computation.ensure();
431 self.cumulative_containing_block_rect.size()
432 });
433
434 let insets = style.physical_box_offsets();
441 if position == ComputedPosition::Relative {
442 let get_resolved_axis = |start: &LengthPercentageOrAuto,
443 end: &LengthPercentageOrAuto,
444 container_length: Au| {
445 let start = start.map(|value| value.to_used_value(container_length));
446 let end = end.map(|value| value.to_used_value(container_length));
447 match (start.non_auto(), end.non_auto()) {
448 (None, None) => (Au::zero(), Au::zero()),
449 (None, Some(end)) => (-end, end),
450 (Some(start), None) => (start, -start),
451 (Some(start), Some(end)) => (start, end),
454 }
455 };
456
457 let (left, right) =
458 get_resolved_axis(&insets.left, &insets.right, containing_block_size.width);
459 let (top, bottom) =
460 get_resolved_axis(&insets.top, &insets.bottom, containing_block_size.height);
461 return convert_to_au_or_auto(PhysicalSides::new(top, right, bottom, left));
462 }
463
464 debug_assert!(position.is_absolutely_positioned());
465
466 let margin_rect = self.margin_rect();
467 let (top, bottom) = match (&insets.top, &insets.bottom) {
468 (
469 LengthPercentageOrAuto::LengthPercentage(top),
470 LengthPercentageOrAuto::LengthPercentage(bottom),
471 ) => (
472 top.to_used_value(containing_block_size.height),
473 bottom.to_used_value(containing_block_size.height),
474 ),
475 _ => (
476 margin_rect.origin.y,
477 containing_block_size.height - margin_rect.max_y(),
478 ),
479 };
480 let (left, right) = match (&insets.left, &insets.right) {
481 (
482 LengthPercentageOrAuto::LengthPercentage(left),
483 LengthPercentageOrAuto::LengthPercentage(right),
484 ) => (
485 left.to_used_value(containing_block_size.width),
486 right.to_used_value(containing_block_size.width),
487 ),
488 _ => (
489 margin_rect.origin.x,
490 containing_block_size.width - margin_rect.max_x(),
491 ),
492 };
493
494 convert_to_au_or_auto(PhysicalSides::new(top, right, bottom, left))
495 }
496
497 pub(crate) fn is_flex_or_grid_item(&self) -> bool {
499 self.base
500 .flags
501 .contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM)
502 }
503
504 pub(crate) fn is_replaced(&self) -> bool {
506 self.base.flags.contains(FragmentFlags::IS_REPLACED)
507 }
508
509 pub(crate) fn is_table_wrapper(&self) -> bool {
512 matches!(
513 self.specific_layout_info().as_deref(),
514 Some(SpecificLayoutInfo::TableWrapper)
515 )
516 }
517
518 pub(crate) fn is_table_grid_with_collapsed_borders(&self) -> bool {
520 matches!(
521 self.specific_layout_info().as_deref(),
522 Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(_))
523 )
524 }
525
526 pub(crate) fn spatial_tree_node(&self) -> Option<ScrollTreeNodeId> {
527 self.spatial_tree_node.get()
528 }
529}
530
531pub(crate) struct BoxFragmentWithStyle<'a> {
538 pub(crate) box_fragment: &'a Arc<BoxFragment>,
539 pub(crate) style: AtomicRef<'a, ServoArc<ComputedValues>>,
540}
541
542impl std::ops::Deref for BoxFragmentWithStyle<'_> {
543 type Target = Arc<BoxFragment>;
544
545 fn deref(&self) -> &Self::Target {
546 self.box_fragment
547 }
548}
549
550impl<'a> BoxFragmentWithStyle<'a> {
551 pub(crate) fn style(&self) -> &ServoArc<ComputedValues> {
552 &self.style
553 }
554
555 pub(crate) fn clip_wholly_unreachable_scrollable_overflow(
560 &self,
561 scrollable_overflow_from_child: PhysicalRect<Au>,
562 clipping_rect: PhysicalRect<Au>,
563 ) -> PhysicalRect<Au> {
564 let scrolling_direction = self.style().overflow_direction();
573 let mut clipping_box = clipping_rect.to_box2d();
574 if scrolling_direction.rightward {
575 clipping_box.max.x = MAX_AU;
576 } else {
577 clipping_box.min.x = MIN_AU;
578 }
579
580 if scrolling_direction.downward {
581 clipping_box.max.y = MAX_AU;
582 } else {
583 clipping_box.min.y = MIN_AU;
584 }
585
586 let scrollable_overflow_box = scrollable_overflow_from_child
587 .to_box2d()
588 .intersection_unchecked(&clipping_box);
589
590 match scrollable_overflow_box.is_negative() {
591 false => scrollable_overflow_box.to_rect(),
592 true => Rect::zero(),
593 }
594 }
595
596 pub(crate) fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
597 let style = self.style();
598 let mut overflow = self.border_rect();
599 if !style.establishes_scroll_container(self.base.flags) {
600 let scrollable_overflow = self.scrollable_overflow();
603 let bottom_right = PhysicalPoint::new(
604 overflow.max_x().max(scrollable_overflow.max_x()),
605 overflow.max_y().max(scrollable_overflow.max_y()),
606 );
607
608 let overflow_style = style.effective_overflow(self.base.flags);
609 if overflow_style.y == ComputedOverflow::Visible {
610 overflow.origin.y = overflow.origin.y.min(scrollable_overflow.origin.y);
611 overflow.size.height = bottom_right.y - overflow.origin.y;
612 }
613
614 if overflow_style.x == ComputedOverflow::Visible {
615 overflow.origin.x = overflow.origin.x.min(scrollable_overflow.origin.x);
616 overflow.size.width = bottom_right.x - overflow.origin.x;
617 }
618 }
619
620 if !style.has_effective_transform_or_perspective(self.base.flags) {
621 return overflow;
622 }
623
624 self.calculate_transform_matrix(&self.border_rect())
632 .and_then(|transform| {
633 transform.outer_transformed_rect(&overflow.to_webrender().to_rect())
634 })
635 .map(|transformed_rect| f32_rect_to_au_rect(transformed_rect).cast_unit())
636 .unwrap_or(overflow)
637 }
638
639 pub(crate) fn scrollable_overflow(&self) -> PhysicalRect<Au> {
643 if self
644 .scrollable_overflow_is_up_to_date
645 .load(Ordering::Acquire)
646 {
647 self.scrollable_overflow.get()
648 } else {
649 let rect = self.calculate_scrollable_overflow();
650 self.scrollable_overflow.set(rect);
651 self.scrollable_overflow_is_up_to_date
652 .store(true, Ordering::Release);
653 rect
654 }
655 }
656 fn calculate_scrollable_overflow(&self) -> PhysicalRect<Au> {
660 if self.base.flags.contains(FragmentFlags::IS_COLLAPSED) {
665 return Rect::zero();
666 }
667
668 let physical_padding_rect = self.padding_rect();
669 let content_origin = self.base.rect().origin.to_vector();
670
671 let mut scrollable_overflow =
695 self.children
696 .iter()
697 .fold(physical_padding_rect, |acc, child| {
698 let scrollable_overflow_from_child = child
699 .scrollable_overflow_for_parent()
700 .translate(content_origin);
701
702 let scrollable_overflow_from_child = self
707 .clip_wholly_unreachable_scrollable_overflow(
708 scrollable_overflow_from_child,
709 physical_padding_rect,
710 );
711
712 acc.union(&scrollable_overflow_from_child)
713 });
714
715 let should_include_additional_padding =
728 self.style().establishes_scroll_container(self.base.flags) &&
729 !self.base.flags.intersects(FragmentFlags::IS_INPUT_ELEMENT);
730
731 if should_include_additional_padding {
732 scrollable_overflow = self
733 .children
734 .iter()
735 .fold(scrollable_overflow, |acc, child| {
736 let Some(padding_contribution) =
737 child.scrollable_overflow_padding_contribution_for_parent()
738 else {
739 return acc;
740 };
741
742 let padding_contribution = padding_contribution
743 .translate(content_origin)
744 .outer_rect(self.padding);
745
746 let padding_contribution = self.clip_wholly_unreachable_scrollable_overflow(
750 padding_contribution,
751 physical_padding_rect,
752 );
753
754 acc.union(&padding_contribution)
755 });
756 }
757 scrollable_overflow
758 }
759
760 pub(crate) fn is_inline_box(&self) -> bool {
763 self.style().is_inline_box(self.base.flags)
764 }
765
766 pub(crate) fn is_atomic_inline_level(&self) -> bool {
769 self.style().is_atomic_inline_level(self.base.flags)
770 }
771
772 pub(crate) fn has_collapsed_borders(&self) -> bool {
773 match self.specific_layout_info().as_deref() {
774 Some(SpecificLayoutInfo::TableCellWithCollapsedBorders) => true,
775 Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(_)) => true,
776 Some(SpecificLayoutInfo::TableWrapper) => {
777 self.style().get_inherited_table().border_collapse == BorderCollapse::Collapse
778 },
779 _ => false,
780 }
781 }
782
783 pub(crate) fn has_outline(&self) -> bool {
785 let style = self.style();
786 let outline = style.get_outline();
787 !outline.outline_style.none_or_hidden() && !outline.outline_width.0.is_zero()
788 }
789}