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 style: SharedStyle,
121
122 pub children: Vec<Fragment>,
123
124 pub cumulative_containing_block_rect: SyncPhysicalRectAu,
127
128 pub padding: PhysicalSides<Au>,
129 pub border: PhysicalSides<Au>,
130 pub margin: PhysicalSides<Au>,
131
132 baselines: Baselines,
136
137 scrollable_overflow: SyncPhysicalRectAu,
142 scrollable_overflow_is_up_to_date: AtomicBool,
143
144 pub background_mode: BackgroundMode,
145
146 pub rare_data: OnceBox<AtomicRefCell<BoxFragmentRareData>>,
148
149 pub block_level_layout_info: Option<Box<BlockLevelLayoutInfo>>,
151
152 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 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 Baselines::default()
217 };
218
219 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 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 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 (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 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 pub(crate) fn is_replaced(&self) -> bool {
519 self.base.flags.contains(FragmentFlags::IS_REPLACED)
520 }
521
522 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 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
544pub(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 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 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 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 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 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 fn calculate_scrollable_overflow(&self) -> PhysicalRect<Au> {
673 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 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 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 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 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 pub(crate) fn is_inline_box(&self) -> bool {
776 self.style().is_inline_box(self.base.flags)
777 }
778
779 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 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}