1use app_units::{Au, MAX_AU, MIN_AU};
6use atomic_refcell::AtomicRefCell;
7use base::id::ScrollTreeNodeId;
8use base::print_tree::PrintTree;
9use euclid::Rect;
10use malloc_size_of_derive::MallocSizeOf;
11use servo_arc::Arc as ServoArc;
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;
19use style::values::specified::box_::DisplayOutside;
20
21use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment, FragmentFlags};
22use crate::SharedStyle;
23use crate::display_list::ToWebRender;
24use crate::formatting_contexts::Baselines;
25use crate::fragment_tree::BaseFragmentStyleRef;
26use crate::geom::{
27 AuOrAuto, LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, ToLogical,
28};
29use crate::style_ext::ComputedValuesExt;
30use crate::table::SpecificTableGridInfo;
31use crate::taffy::SpecificTaffyGridInfo;
32
33#[derive(MallocSizeOf)]
35pub(crate) enum BackgroundMode {
36 Extra(Vec<ExtraBackground>),
39 None,
43 Normal,
45}
46#[derive(MallocSizeOf)]
47pub(crate) struct ExtraBackground {
48 pub style: SharedStyle,
49 pub rect: PhysicalRect<Au>,
50}
51
52#[derive(Clone, Debug, MallocSizeOf)]
53pub(crate) enum SpecificLayoutInfo {
54 Grid(Box<SpecificTaffyGridInfo>),
55 TableCellWithCollapsedBorders,
56 TableGridWithCollapsedBorders(Box<SpecificTableGridInfo>),
57 TableWrapper,
58}
59
60#[derive(MallocSizeOf)]
61pub(crate) struct BlockLevelLayoutInfo {
62 pub clearance: Option<Au>,
69
70 pub block_margins_collapsed_with_children: CollapsedBlockMargins,
71}
72
73#[derive(MallocSizeOf)]
74pub(crate) struct BoxFragmentRareData {
75 pub specific_layout_info: Option<SpecificLayoutInfo>,
77}
78
79impl BoxFragmentRareData {
80 fn try_boxed_from(specific_layout_info: Option<SpecificLayoutInfo>) -> Option<Box<Self>> {
83 specific_layout_info.map(|info| {
84 Box::new(BoxFragmentRareData {
85 specific_layout_info: Some(info),
86 })
87 })
88 }
89}
90
91#[derive(MallocSizeOf)]
92pub(crate) struct BoxFragment {
93 pub base: BaseFragment,
94
95 pub children: Vec<Fragment>,
96
97 pub cumulative_containing_block_rect: PhysicalRect<Au>,
100
101 pub padding: PhysicalSides<Au>,
102 pub border: PhysicalSides<Au>,
103 pub margin: PhysicalSides<Au>,
104
105 baselines: Baselines,
109
110 scrollable_overflow: Option<PhysicalRect<Au>>,
115
116 pub(crate) resolved_sticky_insets: AtomicRefCell<Option<PhysicalSides<AuOrAuto>>>,
120
121 pub background_mode: BackgroundMode,
122
123 pub rare_data: Option<Box<BoxFragmentRareData>>,
125
126 pub block_level_layout_info: Option<Box<BlockLevelLayoutInfo>>,
128
129 pub spatial_tree_node: AtomicRefCell<Option<ScrollTreeNodeId>>,
134}
135
136impl BoxFragment {
137 #[allow(clippy::too_many_arguments)]
138 pub fn new(
139 base_fragment_info: BaseFragmentInfo,
140 style: ServoArc<ComputedValues>,
141 children: Vec<Fragment>,
142 content_rect: PhysicalRect<Au>,
143 padding: PhysicalSides<Au>,
144 border: PhysicalSides<Au>,
145 margin: PhysicalSides<Au>,
146 specific_layout_info: Option<SpecificLayoutInfo>,
147 ) -> BoxFragment {
148 let rare_data = BoxFragmentRareData::try_boxed_from(specific_layout_info);
149
150 BoxFragment {
151 base: BaseFragment::new(base_fragment_info, style.into(), content_rect),
152 children,
153 cumulative_containing_block_rect: Default::default(),
154 padding,
155 border,
156 margin,
157 baselines: Baselines::default(),
158 scrollable_overflow: None,
159 resolved_sticky_insets: AtomicRefCell::default(),
160 background_mode: BackgroundMode::Normal,
161 rare_data,
162 block_level_layout_info: None,
163 spatial_tree_node: AtomicRefCell::default(),
164 }
165 }
166
167 pub fn with_baselines(mut self, baselines: Baselines) -> Self {
168 self.baselines = baselines;
169 self
170 }
171
172 pub(crate) fn style<'a>(&'a self) -> BaseFragmentStyleRef<'a> {
173 self.base.style()
174 }
175
176 pub fn baselines(&self, writing_mode: WritingMode) -> Baselines {
179 let style = self.style();
180 let mut baselines = if writing_mode.is_horizontal() == style.writing_mode.is_horizontal() {
181 self.baselines
182 } else {
183 Baselines::default()
187 };
188
189 if style.establishes_scroll_container(self.base.flags) {
198 let content_rect_size = self.content_rect().size.to_logical(writing_mode);
199 let padding = self.padding.to_logical(writing_mode);
200 let border = self.border.to_logical(writing_mode);
201 let margin = self.margin.to_logical(writing_mode);
202 baselines.last = Some(
203 content_rect_size.block + padding.block_end + border.block_end + margin.block_end,
204 )
205 }
206 baselines
207 }
208
209 pub fn add_extra_background(&mut self, extra_background: ExtraBackground) {
210 match self.background_mode {
211 BackgroundMode::Extra(ref mut backgrounds) => backgrounds.push(extra_background),
212 _ => self.background_mode = BackgroundMode::Extra(vec![extra_background]),
213 }
214 }
215
216 pub fn set_does_not_paint_background(&mut self) {
217 self.background_mode = BackgroundMode::None;
218 }
219
220 pub fn specific_layout_info(&self) -> Option<&SpecificLayoutInfo> {
221 self.rare_data.as_ref()?.specific_layout_info.as_ref()
222 }
223
224 pub fn with_block_level_layout_info(
225 mut self,
226 block_margins_collapsed_with_children: CollapsedBlockMargins,
227 clearance: Option<Au>,
228 ) -> Self {
229 self.block_level_layout_info = Some(Box::new(BlockLevelLayoutInfo {
230 block_margins_collapsed_with_children,
231 clearance,
232 }));
233 self
234 }
235
236 pub fn scrollable_overflow(&self) -> PhysicalRect<Au> {
239 self.scrollable_overflow
240 .expect("Should only call `scrollable_overflow()` after calculating overflow")
241 }
242
243 pub(crate) fn calculate_scrollable_overflow(&mut self) {
247 let physical_padding_rect = self.padding_rect();
248 let content_origin = self.base.rect.origin.to_vector();
249
250 let scrollable_overflow = self
274 .children
275 .iter()
276 .fold(physical_padding_rect, |acc, child| {
277 let scrollable_overflow_from_child = child
278 .calculate_scrollable_overflow_for_parent()
279 .translate(content_origin);
280
281 let scrollable_overflow_from_child = self
286 .clip_wholly_unreachable_scrollable_overflow(
287 scrollable_overflow_from_child,
288 physical_padding_rect,
289 );
290 acc.union(&scrollable_overflow_from_child)
291 });
292
293 if self.base.flags.contains(FragmentFlags::IS_COLLAPSED) {
298 self.scrollable_overflow = Some(Rect::zero());
299 return;
300 }
301
302 self.scrollable_overflow = Some(scrollable_overflow)
303 }
304
305 pub(crate) fn set_containing_block(&mut self, containing_block: &PhysicalRect<Au>) {
306 self.cumulative_containing_block_rect = *containing_block;
307 }
308
309 pub fn offset_by_containing_block(&self, rect: &PhysicalRect<Au>) -> PhysicalRect<Au> {
310 rect.translate(self.cumulative_containing_block_rect.origin.to_vector())
311 }
312
313 pub(crate) fn cumulative_content_box_rect(&self) -> PhysicalRect<Au> {
314 self.offset_by_containing_block(&self.base.rect)
315 }
316
317 pub(crate) fn cumulative_padding_box_rect(&self) -> PhysicalRect<Au> {
318 self.offset_by_containing_block(&self.padding_rect())
319 }
320
321 pub(crate) fn cumulative_border_box_rect(&self) -> PhysicalRect<Au> {
322 self.offset_by_containing_block(&self.border_rect())
323 }
324
325 pub(crate) fn content_rect(&self) -> PhysicalRect<Au> {
326 self.base.rect
327 }
328
329 pub(crate) fn padding_rect(&self) -> PhysicalRect<Au> {
330 self.content_rect().outer_rect(self.padding)
331 }
332
333 pub(crate) fn border_rect(&self) -> PhysicalRect<Au> {
334 self.padding_rect().outer_rect(self.border)
335 }
336
337 pub(crate) fn margin_rect(&self) -> PhysicalRect<Au> {
338 self.border_rect().outer_rect(self.margin)
339 }
340
341 pub(crate) fn padding_border_margin(&self) -> PhysicalSides<Au> {
342 self.margin + self.border + self.padding
343 }
344
345 pub(crate) fn is_root_element(&self) -> bool {
346 self.base.flags.intersects(FragmentFlags::IS_ROOT_ELEMENT)
347 }
348
349 pub(crate) fn is_body_element_of_html_element_root(&self) -> bool {
350 self.base
351 .flags
352 .intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT)
353 }
354
355 pub fn print(&self, tree: &mut PrintTree) {
356 tree.new_level(format!(
357 "Box\
358 \nbase={:?}\
359 \ncontent={:?}\
360 \npadding rect={:?}\
361 \nborder rect={:?}\
362 \nmargin={:?}\
363 \nscrollable_overflow={:?}\
364 \nbaselines={:?}\
365 \noverflow={:?}",
366 self.base,
367 self.content_rect(),
368 self.padding_rect(),
369 self.border_rect(),
370 self.margin,
371 self.scrollable_overflow(),
372 self.baselines,
373 self.style().effective_overflow(self.base.flags),
374 ));
375
376 for child in &self.children {
377 child.print(tree);
378 }
379 tree.end_level();
380 }
381
382 pub(crate) fn scrollable_overflow_for_parent(&self) -> PhysicalRect<Au> {
383 let style = self.style();
384 let mut overflow = self.border_rect();
385 if !style.establishes_scroll_container(self.base.flags) {
386 let scrollable_overflow = self.scrollable_overflow();
389 let bottom_right = PhysicalPoint::new(
390 overflow.max_x().max(scrollable_overflow.max_x()),
391 overflow.max_y().max(scrollable_overflow.max_y()),
392 );
393
394 let overflow_style = style.effective_overflow(self.base.flags);
395 if overflow_style.y == ComputedOverflow::Visible {
396 overflow.origin.y = overflow.origin.y.min(scrollable_overflow.origin.y);
397 overflow.size.height = bottom_right.y - overflow.origin.y;
398 }
399
400 if overflow_style.x == ComputedOverflow::Visible {
401 overflow.origin.x = overflow.origin.x.min(scrollable_overflow.origin.x);
402 overflow.size.width = bottom_right.x - overflow.origin.x;
403 }
404 }
405
406 if !style.has_effective_transform_or_perspective(self.base.flags) {
407 return overflow;
408 }
409
410 self.calculate_transform_matrix(&self.border_rect())
418 .and_then(|transform| {
419 transform.outer_transformed_rect(&overflow.to_webrender().to_rect())
420 })
421 .map(|transformed_rect| f32_rect_to_au_rect(transformed_rect).cast_unit())
422 .unwrap_or(overflow)
423 }
424
425 pub(crate) fn clip_wholly_unreachable_scrollable_overflow(
429 &self,
430 scrollable_overflow: PhysicalRect<Au>,
431 clipping_rect: PhysicalRect<Au>,
432 ) -> PhysicalRect<Au> {
433 let scrolling_direction = self.style().overflow_direction();
442 let mut clipping_box = clipping_rect.to_box2d();
443 if scrolling_direction.rightward {
444 clipping_box.max.x = MAX_AU;
445 } else {
446 clipping_box.min.x = MIN_AU;
447 }
448
449 if scrolling_direction.downward {
450 clipping_box.max.y = MAX_AU;
451 } else {
452 clipping_box.min.y = MIN_AU;
453 }
454
455 let scrollable_overflow_box = scrollable_overflow
456 .to_box2d()
457 .intersection_unchecked(&clipping_box);
458
459 match scrollable_overflow_box.is_negative() {
460 true => PhysicalRect::zero(),
461 false => scrollable_overflow_box.to_rect(),
462 }
463 }
464
465 pub(crate) fn calculate_resolved_insets_if_positioned(&self) -> PhysicalSides<AuOrAuto> {
466 let style = self.style();
467 let position = style.get_box().position;
468 debug_assert_ne!(
469 position,
470 ComputedPosition::Static,
471 "Should not call this method on statically positioned box."
472 );
473
474 if let Some(resolved_sticky_insets) = *self.resolved_sticky_insets.borrow() {
475 return resolved_sticky_insets;
476 }
477
478 let convert_to_au_or_auto = |sides: PhysicalSides<Au>| {
479 PhysicalSides::new(
480 AuOrAuto::LengthPercentage(sides.top),
481 AuOrAuto::LengthPercentage(sides.right),
482 AuOrAuto::LengthPercentage(sides.bottom),
483 AuOrAuto::LengthPercentage(sides.left),
484 )
485 };
486
487 let insets = style.physical_box_offsets();
494 let (cb_width, cb_height) = (
495 self.cumulative_containing_block_rect.width(),
496 self.cumulative_containing_block_rect.height(),
497 );
498 if position == ComputedPosition::Relative {
499 let get_resolved_axis = |start: &LengthPercentageOrAuto,
500 end: &LengthPercentageOrAuto,
501 container_length: Au| {
502 let start = start.map(|value| value.to_used_value(container_length));
503 let end = end.map(|value| value.to_used_value(container_length));
504 match (start.non_auto(), end.non_auto()) {
505 (None, None) => (Au::zero(), Au::zero()),
506 (None, Some(end)) => (-end, end),
507 (Some(start), None) => (start, -start),
508 (Some(start), Some(end)) => (start, end),
511 }
512 };
513 let (left, right) = get_resolved_axis(&insets.left, &insets.right, cb_width);
514 let (top, bottom) = get_resolved_axis(&insets.top, &insets.bottom, cb_height);
515 return convert_to_au_or_auto(PhysicalSides::new(top, right, bottom, left));
516 }
517
518 debug_assert!(position.is_absolutely_positioned());
519
520 let margin_rect = self.margin_rect();
521 let (top, bottom) = match (&insets.top, &insets.bottom) {
522 (
523 LengthPercentageOrAuto::LengthPercentage(top),
524 LengthPercentageOrAuto::LengthPercentage(bottom),
525 ) => (
526 top.to_used_value(cb_height),
527 bottom.to_used_value(cb_height),
528 ),
529 _ => (margin_rect.origin.y, cb_height - margin_rect.max_y()),
530 };
531 let (left, right) = match (&insets.left, &insets.right) {
532 (
533 LengthPercentageOrAuto::LengthPercentage(left),
534 LengthPercentageOrAuto::LengthPercentage(right),
535 ) => (left.to_used_value(cb_width), right.to_used_value(cb_width)),
536 _ => (margin_rect.origin.x, cb_width - margin_rect.max_x()),
537 };
538
539 convert_to_au_or_auto(PhysicalSides::new(top, right, bottom, left))
540 }
541
542 pub(crate) fn is_inline_box(&self) -> bool {
545 self.style().is_inline_box(self.base.flags)
546 }
547
548 pub(crate) fn is_atomic_inline_level(&self) -> bool {
551 self.style().get_box().display.outside() == DisplayOutside::Inline && !self.is_inline_box()
552 }
553
554 pub(crate) fn is_table_wrapper(&self) -> bool {
557 matches!(
558 self.specific_layout_info(),
559 Some(SpecificLayoutInfo::TableWrapper)
560 )
561 }
562
563 pub(crate) fn has_collapsed_borders(&self) -> bool {
564 match self.specific_layout_info() {
565 Some(SpecificLayoutInfo::TableCellWithCollapsedBorders) => true,
566 Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(_)) => true,
567 Some(SpecificLayoutInfo::TableWrapper) => {
568 self.style().get_inherited_table().border_collapse == BorderCollapse::Collapse
569 },
570 _ => false,
571 }
572 }
573
574 pub(crate) fn spatial_tree_node(&self) -> Option<ScrollTreeNodeId> {
575 *self.spatial_tree_node.borrow()
576 }
577}