1use app_units::{Au, MAX_AU, MIN_AU};
6use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
7use euclid::Rect;
8use malloc_size_of_derive::MallocSizeOf;
9use servo_arc::Arc as ServoArc;
10use servo_base::id::ScrollTreeNodeId;
11use servo_base::print_tree::PrintTree;
12use servo_geometry::f32_rect_to_au_rect;
13use style::Zero;
14use style::computed_values::border_collapse::T as BorderCollapse;
15use style::computed_values::overflow_x::T as ComputedOverflow;
16use style::computed_values::position::T as ComputedPosition;
17use style::logical_geometry::WritingMode;
18use style::properties::ComputedValues;
19
20use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment, FragmentFlags};
21use crate::SharedStyle;
22use crate::display_list::ToWebRender;
23use crate::formatting_contexts::Baselines;
24use crate::fragment_tree::BaseFragmentStyleRef;
25use crate::geom::{
26 AuOrAuto, LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, ToLogical,
27};
28use crate::style_ext::ComputedValuesExt;
29use crate::table::SpecificTableGridInfo;
30use crate::taffy::SpecificTaffyGridInfo;
31
32#[derive(MallocSizeOf)]
34pub(crate) enum BackgroundMode {
35 Extra(Vec<ExtraBackground>),
38 None,
42 Normal,
44}
45#[derive(MallocSizeOf)]
46pub(crate) struct ExtraBackground {
47 pub style: SharedStyle,
48 pub rect: PhysicalRect<Au>,
49}
50
51#[derive(Clone, Debug, MallocSizeOf)]
52pub(crate) enum SpecificLayoutInfo {
53 Grid(Box<SpecificTaffyGridInfo>),
54 TableCellWithCollapsedBorders,
55 TableGridWithCollapsedBorders(Box<SpecificTableGridInfo>),
56 TableWrapper,
57}
58
59#[derive(MallocSizeOf)]
60pub(crate) struct BlockLevelLayoutInfo {
61 pub clearance: Option<Au>,
68
69 pub block_margins_collapsed_with_children: CollapsedBlockMargins,
70}
71
72#[derive(Default, MallocSizeOf)]
73pub(crate) struct BoxFragmentRareData {
74 pub(crate) resolved_sticky_insets: Option<Box<PhysicalSides<AuOrAuto>>>,
78
79 pub specific_layout_info: Option<SpecificLayoutInfo>,
81}
82
83impl BoxFragmentRareData {
84 fn try_boxed_from(
87 specific_layout_info: Option<SpecificLayoutInfo>,
88 ) -> AtomicRefCell<Option<Box<Self>>> {
89 AtomicRefCell::new(specific_layout_info.map(|info| {
90 Box::new(BoxFragmentRareData {
91 resolved_sticky_insets: None,
92 specific_layout_info: Some(info),
93 })
94 }))
95 }
96}
97
98#[derive(MallocSizeOf)]
99pub(crate) struct BoxFragment {
100 pub base: BaseFragment,
101
102 pub children: Vec<Fragment>,
103
104 pub cumulative_containing_block_rect: PhysicalRect<Au>,
107
108 pub padding: PhysicalSides<Au>,
109 pub border: PhysicalSides<Au>,
110 pub margin: PhysicalSides<Au>,
111
112 baselines: Baselines,
116
117 scrollable_overflow: Option<PhysicalRect<Au>>,
122
123 pub background_mode: BackgroundMode,
124
125 pub rare_data: AtomicRefCell<Option<Box<BoxFragmentRareData>>>,
127
128 pub block_level_layout_info: Option<Box<BlockLevelLayoutInfo>>,
130
131 pub spatial_tree_node: AtomicRefCell<Option<ScrollTreeNodeId>>,
136}
137
138impl BoxFragment {
139 #[allow(clippy::too_many_arguments)]
140 pub fn new(
141 base_fragment_info: BaseFragmentInfo,
142 style: ServoArc<ComputedValues>,
143 children: Vec<Fragment>,
144 content_rect: PhysicalRect<Au>,
145 padding: PhysicalSides<Au>,
146 border: PhysicalSides<Au>,
147 margin: PhysicalSides<Au>,
148 specific_layout_info: Option<SpecificLayoutInfo>,
149 ) -> BoxFragment {
150 let rare_data = BoxFragmentRareData::try_boxed_from(specific_layout_info);
151
152 BoxFragment {
153 base: BaseFragment::new(base_fragment_info, style.into(), content_rect),
154 children,
155 cumulative_containing_block_rect: Default::default(),
156 padding,
157 border,
158 margin,
159 baselines: Baselines::default(),
160 scrollable_overflow: None,
161 background_mode: BackgroundMode::Normal,
162 rare_data,
163 block_level_layout_info: None,
164 spatial_tree_node: AtomicRefCell::default(),
165 }
166 }
167
168 pub fn with_baselines(mut self, baselines: Baselines) -> Self {
169 self.baselines = baselines;
170 self
171 }
172
173 pub(crate) fn style<'a>(&'a self) -> BaseFragmentStyleRef<'a> {
174 self.base.style()
175 }
176
177 pub fn baselines(&self, writing_mode: WritingMode) -> Baselines {
180 let style = self.style();
181 let mut baselines = if writing_mode.is_horizontal() == style.writing_mode.is_horizontal() {
182 self.baselines
183 } else {
184 Baselines::default()
188 };
189
190 if style.establishes_scroll_container(self.base.flags) {
199 let content_rect_size = self.content_rect().size.to_logical(writing_mode);
200 let padding = self.padding.to_logical(writing_mode);
201 let border = self.border.to_logical(writing_mode);
202 let margin = self.margin.to_logical(writing_mode);
203 baselines.last = Some(
204 content_rect_size.block + padding.block_end + border.block_end + margin.block_end,
205 )
206 }
207 baselines
208 }
209
210 pub fn add_extra_background(&mut self, extra_background: ExtraBackground) {
211 match self.background_mode {
212 BackgroundMode::Extra(ref mut backgrounds) => backgrounds.push(extra_background),
213 _ => self.background_mode = BackgroundMode::Extra(vec![extra_background]),
214 }
215 }
216
217 pub fn set_does_not_paint_background(&mut self) {
218 self.background_mode = BackgroundMode::None;
219 }
220
221 pub fn ensure_rare_data(&self) -> AtomicRefMut<'_, Box<BoxFragmentRareData>> {
222 let mut rare_data = self.rare_data.borrow_mut();
223 if rare_data.is_none() {
224 *rare_data = Some(Default::default());
225 }
226
227 AtomicRefMut::map(rare_data, |rare_data| {
228 rare_data
229 .as_mut()
230 .expect("This data should have just been set")
231 })
232 }
233
234 pub fn specific_layout_info(&self) -> Option<AtomicRef<'_, SpecificLayoutInfo>> {
235 let rare_data = self.rare_data.borrow();
236
237 AtomicRef::filter_map(rare_data, |rare_data| {
238 rare_data.as_ref()?.specific_layout_info.as_ref()
239 })
240 }
241
242 pub fn resolved_sticky_insets(&self) -> Option<AtomicRef<'_, Box<PhysicalSides<AuOrAuto>>>> {
243 let rare_data = self.rare_data.borrow();
244
245 AtomicRef::filter_map(rare_data, |rare_data| {
246 rare_data.as_ref()?.resolved_sticky_insets.as_ref()
247 })
248 }
249
250 pub fn with_block_level_layout_info(
251 mut self,
252 block_margins_collapsed_with_children: CollapsedBlockMargins,
253 clearance: Option<Au>,
254 ) -> Self {
255 self.block_level_layout_info = Some(Box::new(BlockLevelLayoutInfo {
256 block_margins_collapsed_with_children,
257 clearance,
258 }));
259 self
260 }
261
262 pub fn scrollable_overflow(&self) -> PhysicalRect<Au> {
265 self.scrollable_overflow
266 .expect("Should only call `scrollable_overflow()` after calculating overflow")
267 }
268
269 pub(crate) fn calculate_scrollable_overflow(&mut self) {
273 let physical_padding_rect = self.padding_rect();
274 let content_origin = self.base.rect.origin.to_vector();
275
276 let scrollable_overflow = self
300 .children
301 .iter()
302 .fold(physical_padding_rect, |acc, child| {
303 let scrollable_overflow_from_child = child
304 .calculate_scrollable_overflow_for_parent()
305 .translate(content_origin);
306
307 let scrollable_overflow_from_child = self
312 .clip_wholly_unreachable_scrollable_overflow(
313 scrollable_overflow_from_child,
314 physical_padding_rect,
315 );
316 acc.union(&scrollable_overflow_from_child)
317 });
318
319 if self.base.flags.contains(FragmentFlags::IS_COLLAPSED) {
324 self.scrollable_overflow = Some(Rect::zero());
325 return;
326 }
327
328 self.scrollable_overflow = Some(scrollable_overflow)
329 }
330
331 pub(crate) fn set_containing_block(&mut self, containing_block: &PhysicalRect<Au>) {
332 self.cumulative_containing_block_rect = *containing_block;
333 }
334
335 pub fn offset_by_containing_block(&self, rect: &PhysicalRect<Au>) -> PhysicalRect<Au> {
336 rect.translate(self.cumulative_containing_block_rect.origin.to_vector())
337 }
338
339 pub(crate) fn cumulative_content_box_rect(&self) -> PhysicalRect<Au> {
340 self.offset_by_containing_block(&self.base.rect)
341 }
342
343 pub(crate) fn cumulative_padding_box_rect(&self) -> PhysicalRect<Au> {
344 self.offset_by_containing_block(&self.padding_rect())
345 }
346
347 pub(crate) fn cumulative_border_box_rect(&self) -> PhysicalRect<Au> {
348 self.offset_by_containing_block(&self.border_rect())
349 }
350
351 pub(crate) fn content_rect(&self) -> PhysicalRect<Au> {
352 self.base.rect
353 }
354
355 pub(crate) fn padding_rect(&self) -> PhysicalRect<Au> {
356 self.content_rect().outer_rect(self.padding)
357 }
358
359 pub(crate) fn border_rect(&self) -> PhysicalRect<Au> {
360 self.padding_rect().outer_rect(self.border)
361 }
362
363 pub(crate) fn margin_rect(&self) -> PhysicalRect<Au> {
364 self.border_rect().outer_rect(self.margin)
365 }
366
367 pub(crate) fn padding_border_margin(&self) -> PhysicalSides<Au> {
368 self.margin + self.border + self.padding
369 }
370
371 pub(crate) fn is_root_element(&self) -> bool {
372 self.base.flags.intersects(FragmentFlags::IS_ROOT_ELEMENT)
373 }
374
375 pub(crate) fn is_body_element_of_html_element_root(&self) -> bool {
376 self.base
377 .flags
378 .intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT)
379 }
380
381 pub fn print(&self, tree: &mut PrintTree) {
382 tree.new_level(format!(
383 "Box\
384 \nbase={:?}\
385 \ncontent={:?}\
386 \npadding rect={:?}\
387 \nborder rect={:?}\
388 \nmargin={:?}\
389 \nscrollable_overflow={:?}\
390 \nbaselines={:?}\
391 \noverflow={:?}",
392 self.base,
393 self.content_rect(),
394 self.padding_rect(),
395 self.border_rect(),
396 self.margin,
397 self.scrollable_overflow(),
398 self.baselines,
399 self.style().effective_overflow(self.base.flags),
400 ));
401
402 for child in &self.children {
403 child.print(tree);
404 }
405 tree.end_level();
406 }
407
408 pub(crate) fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
409 let style = self.style();
410 let mut overflow = self.border_rect();
411 if !style.establishes_scroll_container(self.base.flags) {
412 let scrollable_overflow = self.scrollable_overflow();
415 let bottom_right = PhysicalPoint::new(
416 overflow.max_x().max(scrollable_overflow.max_x()),
417 overflow.max_y().max(scrollable_overflow.max_y()),
418 );
419
420 let overflow_style = style.effective_overflow(self.base.flags);
421 if overflow_style.y == ComputedOverflow::Visible {
422 overflow.origin.y = overflow.origin.y.min(scrollable_overflow.origin.y);
423 overflow.size.height = bottom_right.y - overflow.origin.y;
424 }
425
426 if overflow_style.x == ComputedOverflow::Visible {
427 overflow.origin.x = overflow.origin.x.min(scrollable_overflow.origin.x);
428 overflow.size.width = bottom_right.x - overflow.origin.x;
429 }
430 }
431
432 if !style.has_effective_transform_or_perspective(self.base.flags) {
433 return overflow;
434 }
435
436 self.calculate_transform_matrix(&self.border_rect())
444 .and_then(|transform| {
445 transform.outer_transformed_rect(&overflow.to_webrender().to_rect())
446 })
447 .map(|transformed_rect| f32_rect_to_au_rect(transformed_rect).cast_unit())
448 .unwrap_or(overflow)
449 }
450
451 pub(crate) fn clip_wholly_unreachable_scrollable_overflow(
455 &self,
456 scrollable_overflow: PhysicalRect<Au>,
457 clipping_rect: PhysicalRect<Au>,
458 ) -> PhysicalRect<Au> {
459 let scrolling_direction = self.style().overflow_direction();
468 let mut clipping_box = clipping_rect.to_box2d();
469 if scrolling_direction.rightward {
470 clipping_box.max.x = MAX_AU;
471 } else {
472 clipping_box.min.x = MIN_AU;
473 }
474
475 if scrolling_direction.downward {
476 clipping_box.max.y = MAX_AU;
477 } else {
478 clipping_box.min.y = MIN_AU;
479 }
480
481 let scrollable_overflow_box = scrollable_overflow
482 .to_box2d()
483 .intersection_unchecked(&clipping_box);
484
485 match scrollable_overflow_box.is_negative() {
486 true => PhysicalRect::zero(),
487 false => scrollable_overflow_box.to_rect(),
488 }
489 }
490
491 pub(crate) fn calculate_resolved_insets_if_positioned(&self) -> PhysicalSides<AuOrAuto> {
492 let style = self.style();
493 let position = style.get_box().position;
494 debug_assert_ne!(
495 position,
496 ComputedPosition::Static,
497 "Should not call this method on statically positioned box."
498 );
499
500 if let Some(resolved_sticky_insets) = self.resolved_sticky_insets() {
501 return **resolved_sticky_insets;
502 }
503
504 let convert_to_au_or_auto = |sides: PhysicalSides<Au>| {
505 PhysicalSides::new(
506 AuOrAuto::LengthPercentage(sides.top),
507 AuOrAuto::LengthPercentage(sides.right),
508 AuOrAuto::LengthPercentage(sides.bottom),
509 AuOrAuto::LengthPercentage(sides.left),
510 )
511 };
512
513 let insets = style.physical_box_offsets();
520 let (cb_width, cb_height) = (
521 self.cumulative_containing_block_rect.width(),
522 self.cumulative_containing_block_rect.height(),
523 );
524 if position == ComputedPosition::Relative {
525 let get_resolved_axis = |start: &LengthPercentageOrAuto,
526 end: &LengthPercentageOrAuto,
527 container_length: Au| {
528 let start = start.map(|value| value.to_used_value(container_length));
529 let end = end.map(|value| value.to_used_value(container_length));
530 match (start.non_auto(), end.non_auto()) {
531 (None, None) => (Au::zero(), Au::zero()),
532 (None, Some(end)) => (-end, end),
533 (Some(start), None) => (start, -start),
534 (Some(start), Some(end)) => (start, end),
537 }
538 };
539 let (left, right) = get_resolved_axis(&insets.left, &insets.right, cb_width);
540 let (top, bottom) = get_resolved_axis(&insets.top, &insets.bottom, cb_height);
541 return convert_to_au_or_auto(PhysicalSides::new(top, right, bottom, left));
542 }
543
544 debug_assert!(position.is_absolutely_positioned());
545
546 let margin_rect = self.margin_rect();
547 let (top, bottom) = match (&insets.top, &insets.bottom) {
548 (
549 LengthPercentageOrAuto::LengthPercentage(top),
550 LengthPercentageOrAuto::LengthPercentage(bottom),
551 ) => (
552 top.to_used_value(cb_height),
553 bottom.to_used_value(cb_height),
554 ),
555 _ => (margin_rect.origin.y, cb_height - margin_rect.max_y()),
556 };
557 let (left, right) = match (&insets.left, &insets.right) {
558 (
559 LengthPercentageOrAuto::LengthPercentage(left),
560 LengthPercentageOrAuto::LengthPercentage(right),
561 ) => (left.to_used_value(cb_width), right.to_used_value(cb_width)),
562 _ => (margin_rect.origin.x, cb_width - margin_rect.max_x()),
563 };
564
565 convert_to_au_or_auto(PhysicalSides::new(top, right, bottom, left))
566 }
567
568 pub(crate) fn is_inline_box(&self) -> bool {
571 self.style().is_inline_box(self.base.flags)
572 }
573
574 pub(crate) fn is_atomic_inline_level(&self) -> bool {
577 self.style().is_atomic_inline_level(self.base.flags)
578 }
579
580 pub(crate) fn is_table_wrapper(&self) -> bool {
583 matches!(
584 self.specific_layout_info().as_deref(),
585 Some(SpecificLayoutInfo::TableWrapper)
586 )
587 }
588
589 pub(crate) fn has_collapsed_borders(&self) -> bool {
590 match self.specific_layout_info().as_deref() {
591 Some(SpecificLayoutInfo::TableCellWithCollapsedBorders) => true,
592 Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(_)) => true,
593 Some(SpecificLayoutInfo::TableWrapper) => {
594 self.style().get_inherited_table().border_collapse == BorderCollapse::Collapse
595 },
596 _ => false,
597 }
598 }
599
600 pub(crate) fn spatial_tree_node(&self) -> Option<ScrollTreeNodeId> {
601 *self.spatial_tree_node.borrow()
602 }
603}