1use app_units::Au;
6use layout_api::{AxesOverflow, LayoutElementType, LayoutNode, LayoutNodeType};
7use malloc_size_of_derive::MallocSizeOf;
8use style::Zero;
9use style::color::AbsoluteColor;
10use style::computed_values::direction::T as Direction;
11use style::computed_values::isolation::T as ComputedIsolation;
12use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
13use style::computed_values::position::T as ComputedPosition;
14use style::computed_values::transform_style::T as ComputedTransformStyle;
15use style::computed_values::unicode_bidi::T as UnicodeBidi;
16use style::logical_geometry::{Direction as AxisDirection, PhysicalSide, WritingMode};
17use style::properties::ComputedValues;
18use style::properties::longhands::backface_visibility::computed_value::T as BackfaceVisiblity;
19use style::properties::longhands::box_sizing::computed_value::T as BoxSizing;
20use style::properties::longhands::column_span::computed_value::T as ColumnSpan;
21use style::properties::style_structs::Border;
22use style::servo::selector_parser::PseudoElement;
23use style::values::CSSFloat;
24use style::values::computed::basic_shape::ClipPath;
25use style::values::computed::image::Image as ComputedImageLayer;
26use style::values::computed::{
27 BorderSideWidth, BorderStyle, Color, Inset, ItemPlacement, LengthPercentage, Margin,
28 SelfAlignment,
29};
30use style::values::generics::box_::Perspective;
31use style::values::generics::position::{GenericAspectRatio, PreferredRatio};
32use style::values::generics::transform::{GenericRotate, GenericScale, GenericTranslate};
33use style::values::specified::align::AlignFlags;
34use style::values::specified::{Overflow, WillChangeBits, box_ as stylo};
35use unicode_bidi::Level;
36use webrender_api as wr;
37use webrender_api::units::LayoutTransform;
38
39use crate::dom_traversal::{Contents, NodeAndStyleInfo};
40use crate::fragment_tree::FragmentFlags;
41use crate::geom::{
42 AuOrAuto, LengthPercentageOrAuto, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalSides,
43 PhysicalSize,
44};
45use crate::sizing::{Size, Sizes};
46use crate::table::TableLayoutStyle;
47use crate::{ContainingBlock, IndefiniteContainingBlock};
48
49#[derive(Clone, Copy, Eq, PartialEq)]
50pub(crate) enum Display {
51 None,
52 Contents,
53 GeneratingBox(DisplayGeneratingBox),
54}
55
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub(crate) enum DisplayGeneratingBox {
58 OutsideInside {
59 outside: DisplayOutside,
60 inside: DisplayInside,
61 },
62 LayoutInternal(DisplayLayoutInternal),
64}
65impl DisplayGeneratingBox {
66 pub(crate) fn display_inside(&self) -> DisplayInside {
67 match *self {
68 DisplayGeneratingBox::OutsideInside { inside, .. } => inside,
69 DisplayGeneratingBox::LayoutInternal(layout_internal) => {
70 layout_internal.display_inside()
71 },
72 }
73 }
74
75 pub(crate) fn used_value_for_contents(
76 &self,
77 contents: &Contents,
78 info: &NodeAndStyleInfo,
79 ) -> Self {
80 if matches!(self, Self::LayoutInternal(_)) && contents.is_replaced() {
85 Self::OutsideInside {
86 outside: DisplayOutside::Inline,
87 inside: DisplayInside::Flow {
88 is_list_item: false,
89 },
90 }
91 } else if matches!(contents, Contents::Widget(_)) {
92 if let DisplayGeneratingBox::OutsideInside { outside, inside } = self &&
99 (matches!(
100 inside,
101 DisplayInside::Flow { .. } | DisplayInside::FlowRoot { .. }
102 ) || info.node.type_id() !=
103 Some(LayoutNodeType::Element(
104 LayoutElementType::HTMLButtonElement,
105 )))
106 {
107 DisplayGeneratingBox::OutsideInside {
108 outside: *outside,
109 inside: DisplayInside::FlowRoot {
110 is_list_item: false,
111 },
112 }
113 } else {
114 *self
115 }
116 } else {
117 *self
118 }
119 }
120}
121
122#[derive(Clone, Copy, Debug, Eq, PartialEq)]
123pub(crate) enum DisplayOutside {
124 Block,
125 Inline,
126}
127
128#[derive(Clone, Copy, Debug, Eq, PartialEq)]
129pub(crate) enum DisplayInside {
130 Flow { is_list_item: bool },
133 FlowRoot { is_list_item: bool },
134 Flex,
135 Grid,
136 Table,
137}
138
139#[derive(Clone, Copy, Debug, Eq, PartialEq)]
140#[expect(clippy::enum_variant_names)]
141pub(crate) enum DisplayLayoutInternal {
143 TableCaption,
144 TableCell,
145 TableColumn,
146 TableColumnGroup,
147 TableFooterGroup,
148 TableHeaderGroup,
149 TableRow,
150 TableRowGroup,
151}
152
153impl DisplayLayoutInternal {
154 pub(crate) fn display_inside(&self) -> DisplayInside {
156 DisplayInside::FlowRoot {
160 is_list_item: false,
161 }
162 }
163}
164
165#[derive(Clone, Debug)]
167pub(crate) struct PaddingBorderMargin {
168 pub padding: LogicalSides<Au>,
169 pub border: LogicalSides<Au>,
170 pub margin: LogicalSides<AuOrAuto>,
171
172 pub padding_border_sums: LogicalVec2<Au>,
174}
175
176impl PaddingBorderMargin {
177 pub(crate) fn zero() -> Self {
178 Self {
179 padding: LogicalSides::zero(),
180 border: LogicalSides::zero(),
181 margin: LogicalSides::zero(),
182 padding_border_sums: LogicalVec2::zero(),
183 }
184 }
185
186 pub(crate) fn sums_auto_is_zero(
187 &self,
188 ignore_block_margins: LogicalSides1D<bool>,
189 ) -> LogicalVec2<Au> {
190 let margin = self.margin.auto_is(Au::zero);
191 let mut sums = self.padding_border_sums;
192 sums.inline += margin.inline_sum();
193 if !ignore_block_margins.start {
194 sums.block += margin.block_start;
195 }
196 if !ignore_block_margins.end {
197 sums.block += margin.block_end;
198 }
199 sums
200 }
201}
202
203#[derive(Clone, Copy, Debug)]
207pub(crate) struct AspectRatio {
208 box_sizing_adjustment: LogicalVec2<Au>,
213 i_over_b: CSSFloat,
215}
216
217impl AspectRatio {
218 pub(crate) fn compute_dependent_size(
220 &self,
221 ratio_dependent_axis: AxisDirection,
222 ratio_determining_size: Au,
223 ) -> Au {
224 match ratio_dependent_axis {
225 AxisDirection::Inline => {
227 (ratio_determining_size + self.box_sizing_adjustment.block).scale_by(self.i_over_b) -
228 self.box_sizing_adjustment.inline
229 },
230 AxisDirection::Block => {
232 (ratio_determining_size + self.box_sizing_adjustment.inline)
233 .scale_by(1.0 / self.i_over_b) -
234 self.box_sizing_adjustment.block
235 },
236 }
237 }
238
239 pub(crate) fn from_logical_content_ratio(i_over_b: CSSFloat) -> Self {
240 Self {
241 box_sizing_adjustment: LogicalVec2::zero(),
242 i_over_b,
243 }
244 }
245}
246
247#[derive(Clone)]
248pub(crate) struct ContentBoxSizesAndPBM {
249 pub content_box_sizes: LogicalVec2<Sizes>,
250 pub pbm: PaddingBorderMargin,
251 pub depends_on_block_constraints: bool,
252 pub preferred_size_computes_to_auto: LogicalVec2<bool>,
253}
254
255#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
256pub(crate) struct BorderStyleColor {
257 pub style: BorderStyle,
258 pub color: AbsoluteColor,
259}
260
261impl BorderStyleColor {
262 pub(crate) fn new(style: BorderStyle, color: AbsoluteColor) -> Self {
263 Self { style, color }
264 }
265
266 pub(crate) fn from_border(
267 border: &Border,
268 current_color: &AbsoluteColor,
269 ) -> PhysicalSides<Self> {
270 let resolve = |color: &Color| color.resolve_to_absolute(current_color);
271 PhysicalSides::<Self>::new(
272 Self::new(border.border_top_style, resolve(&border.border_top_color)),
273 Self::new(
274 border.border_right_style,
275 resolve(&border.border_right_color),
276 ),
277 Self::new(
278 border.border_bottom_style,
279 resolve(&border.border_bottom_color),
280 ),
281 Self::new(border.border_left_style, resolve(&border.border_left_color)),
282 )
283 }
284
285 pub(crate) fn hidden() -> Self {
286 Self::new(BorderStyle::Hidden, AbsoluteColor::TRANSPARENT_BLACK)
287 }
288}
289
290impl Default for BorderStyleColor {
291 fn default() -> Self {
292 Self::new(BorderStyle::None, AbsoluteColor::TRANSPARENT_BLACK)
293 }
294}
295
296pub(crate) struct OverflowDirection {
300 pub rightward: bool,
302 pub downward: bool,
304}
305
306pub(crate) trait ComputedValuesExt {
307 fn physical_box_offsets(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>>;
308 fn box_offsets(&self, writing_mode: WritingMode) -> LogicalSides<LengthPercentageOrAuto<'_>>;
309 fn box_size(
310 &self,
311 containing_block_writing_mode: WritingMode,
312 ) -> LogicalVec2<Size<LengthPercentage>>;
313 fn min_box_size(
314 &self,
315 containing_block_writing_mode: WritingMode,
316 ) -> LogicalVec2<Size<LengthPercentage>>;
317 fn max_box_size(
318 &self,
319 containing_block_writing_mode: WritingMode,
320 ) -> LogicalVec2<Size<LengthPercentage>>;
321 fn content_box_size_for_box_size(
322 &self,
323 box_size: LogicalVec2<Size<Au>>,
324 pbm: &PaddingBorderMargin,
325 ) -> LogicalVec2<Size<Au>>;
326 fn content_min_box_size_for_min_size(
327 &self,
328 box_size: LogicalVec2<Size<Au>>,
329 pbm: &PaddingBorderMargin,
330 ) -> LogicalVec2<Size<Au>>;
331 fn content_max_box_size_for_max_size(
332 &self,
333 box_size: LogicalVec2<Size<Au>>,
334 pbm: &PaddingBorderMargin,
335 ) -> LogicalVec2<Size<Au>>;
336 fn border_style_color(
337 &self,
338 containing_block_writing_mode: WritingMode,
339 ) -> LogicalSides<BorderStyleColor>;
340 fn physical_margin(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>>;
341 fn margin(
342 &self,
343 containing_block_writing_mode: WritingMode,
344 ) -> LogicalSides<LengthPercentageOrAuto<'_>>;
345 fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool;
346 fn has_transform_or_perspective_style(&self) -> bool;
347 fn has_effective_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool;
348 fn z_index_applies(&self, fragment_flags: FragmentFlags) -> bool;
349 fn effective_z_index(&self, fragment_flags: FragmentFlags) -> i32;
350 fn effective_overflow(&self, fragment_flags: FragmentFlags) -> AxesOverflow;
351 fn used_transform_style(&self, fragment_flags: FragmentFlags) -> ComputedTransformStyle;
352 fn establishes_block_formatting_context(&self, fragment_flags: FragmentFlags) -> bool;
353 fn establishes_stacking_context(&self, fragment_flags: FragmentFlags) -> bool;
354 fn establishes_scroll_container(&self, fragment_flags: FragmentFlags) -> bool;
355 fn establishes_or_extends_3d_rendering_context(&self, fragment_flags: FragmentFlags) -> bool;
356 fn establishes_containing_block_for_absolute_descendants(
357 &self,
358 fragment_flags: FragmentFlags,
359 ) -> bool;
360 fn establishes_containing_block_for_all_descendants(
361 &self,
362 fragment_flags: FragmentFlags,
363 ) -> bool;
364 fn preferred_aspect_ratio(
365 &self,
366 natural_aspect_ratio: Option<CSSFloat>,
367 padding_border_sums: &LogicalVec2<Au>,
368 ) -> Option<AspectRatio>;
369 fn background_is_transparent(&self) -> bool;
370 fn webrender_primitive_flags(&self) -> wr::PrimitiveFlags;
371 fn bidi_control_chars(&self) -> (&'static str, &'static str);
372 fn resolve_align_self(
373 &self,
374 resolved_auto_value: ItemPlacement,
375 resolved_normal_value: AlignFlags,
376 ) -> SelfAlignment;
377 fn depends_on_block_constraints_due_to_relative_positioning(
378 &self,
379 writing_mode: WritingMode,
380 ) -> bool;
381 fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool;
382 fn is_atomic_inline_level(&self, fragment_flags: FragmentFlags) -> bool;
383 fn overflow_direction(&self) -> OverflowDirection;
384 fn to_bidi_level(&self) -> Level;
385}
386
387impl ComputedValuesExt for ComputedValues {
388 fn physical_box_offsets(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>> {
389 fn convert(inset: &Inset) -> LengthPercentageOrAuto<'_> {
390 match inset {
391 Inset::LengthPercentage(v) => LengthPercentageOrAuto::LengthPercentage(v),
392 Inset::Auto => LengthPercentageOrAuto::Auto,
393 Inset::AnchorFunction(_) => unreachable!("anchor() should be disabled"),
394 Inset::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"),
395 Inset::AnchorContainingCalcFunction(_) => {
396 unreachable!("anchor() and anchor-size() should be disabled")
397 },
398 }
399 }
400 let position = self.get_position();
401 PhysicalSides::new(
402 convert(&position.top),
403 convert(&position.right),
404 convert(&position.bottom),
405 convert(&position.left),
406 )
407 }
408
409 fn box_offsets(&self, writing_mode: WritingMode) -> LogicalSides<LengthPercentageOrAuto<'_>> {
410 LogicalSides::from_physical(&self.physical_box_offsets(), writing_mode)
411 }
412
413 fn box_size(
414 &self,
415 containing_block_writing_mode: WritingMode,
416 ) -> LogicalVec2<Size<LengthPercentage>> {
417 let position = self.get_position();
418 LogicalVec2::from_physical_size(
419 &PhysicalSize::new(position.get_width().into(), position.get_height().into()),
420 containing_block_writing_mode,
421 )
422 }
423
424 fn min_box_size(
425 &self,
426 containing_block_writing_mode: WritingMode,
427 ) -> LogicalVec2<Size<LengthPercentage>> {
428 let position = self.get_position();
429 LogicalVec2::from_physical_size(
430 &PhysicalSize::new(
431 position.get_min_width().into(),
432 position.get_min_height().into(),
433 ),
434 containing_block_writing_mode,
435 )
436 }
437
438 fn max_box_size(
439 &self,
440 containing_block_writing_mode: WritingMode,
441 ) -> LogicalVec2<Size<LengthPercentage>> {
442 let position = self.get_position();
443 LogicalVec2::from_physical_size(
444 &PhysicalSize::new(
445 position.get_max_width().into(),
446 position.get_max_height().into(),
447 ),
448 containing_block_writing_mode,
449 )
450 }
451
452 fn content_box_size_for_box_size(
453 &self,
454 box_size: LogicalVec2<Size<Au>>,
455 pbm: &PaddingBorderMargin,
456 ) -> LogicalVec2<Size<Au>> {
457 match self.get_position().box_sizing {
458 BoxSizing::ContentBox => box_size,
459 BoxSizing::BorderBox => box_size.map_inline_and_block_sizes(
462 |value| value - pbm.padding_border_sums.inline,
463 |value| value - pbm.padding_border_sums.block,
464 ),
465 }
466 }
467
468 fn content_min_box_size_for_min_size(
469 &self,
470 min_box_size: LogicalVec2<Size<Au>>,
471 pbm: &PaddingBorderMargin,
472 ) -> LogicalVec2<Size<Au>> {
473 match self.get_position().box_sizing {
474 BoxSizing::ContentBox => min_box_size,
475 BoxSizing::BorderBox => min_box_size.map_inline_and_block_sizes(
477 |value| Au::zero().max(value - pbm.padding_border_sums.inline),
478 |value| Au::zero().max(value - pbm.padding_border_sums.block),
479 ),
480 }
481 }
482
483 fn content_max_box_size_for_max_size(
484 &self,
485 max_box_size: LogicalVec2<Size<Au>>,
486 pbm: &PaddingBorderMargin,
487 ) -> LogicalVec2<Size<Au>> {
488 match self.get_position().box_sizing {
489 BoxSizing::ContentBox => max_box_size,
490 BoxSizing::BorderBox => max_box_size.map_inline_and_block_sizes(
493 |value| value - pbm.padding_border_sums.inline,
494 |value| value - pbm.padding_border_sums.block,
495 ),
496 }
497 }
498
499 fn border_style_color(
500 &self,
501 containing_block_writing_mode: WritingMode,
502 ) -> LogicalSides<BorderStyleColor> {
503 let current_color = self.get_color();
504 LogicalSides::from_physical(
505 &BorderStyleColor::from_border(self.get_border(), current_color),
506 containing_block_writing_mode,
507 )
508 }
509
510 fn physical_margin(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>> {
511 fn convert(inset: &Margin) -> LengthPercentageOrAuto<'_> {
512 match inset {
513 Margin::LengthPercentage(v) => LengthPercentageOrAuto::LengthPercentage(v),
514 Margin::Auto => LengthPercentageOrAuto::Auto,
515 Margin::AnchorSizeFunction(_) | Margin::AnchorContainingCalcFunction(_) => {
516 unreachable!("anchor-size() should be disabled")
517 },
518 }
519 }
520 let margin = self.get_margin();
521 PhysicalSides::new(
522 convert(&margin.margin_top),
523 convert(&margin.margin_right),
524 convert(&margin.margin_bottom),
525 convert(&margin.margin_left),
526 )
527 }
528
529 fn margin(
530 &self,
531 containing_block_writing_mode: WritingMode,
532 ) -> LogicalSides<LengthPercentageOrAuto<'_>> {
533 LogicalSides::from_physical(&self.physical_margin(), containing_block_writing_mode)
534 }
535
536 fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool {
537 (self.get_box().display.is_inline_flow() &&
538 !fragment_flags.intersects(
539 FragmentFlags::IS_REPLACED |
540 FragmentFlags::IS_WIDGET |
541 FragmentFlags::IS_FLEX_OR_GRID_ITEM,
542 )) ||
543 matches!(self.pseudo(), Some(PseudoElement::FirstLetter))
544 }
545
546 fn is_atomic_inline_level(&self, fragment_flags: FragmentFlags) -> bool {
547 self.get_box().display.outside() == stylo::DisplayOutside::Inline &&
548 !self.is_inline_box(fragment_flags) &&
549 !fragment_flags.intersects(FragmentFlags::IS_FLEX_OR_GRID_ITEM)
550 }
551
552 fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool {
554 !self.is_inline_box(fragment_flags)
564 }
565
566 fn has_transform_or_perspective_style(&self) -> bool {
568 !self.get_box().transform.0.is_empty() ||
569 self.get_box().scale != GenericScale::None ||
570 self.get_box().rotate != GenericRotate::None ||
571 self.get_box().translate != GenericTranslate::None ||
572 self.get_box().perspective != Perspective::None
573 }
574
575 #[inline]
578 fn has_effective_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool {
579 self.is_transformable(fragment_flags) && self.has_transform_or_perspective_style()
580 }
581
582 fn z_index_applies(&self, fragment_flags: FragmentFlags) -> bool {
584 if self.get_box().position != ComputedPosition::Static {
587 return true;
588 }
589 fragment_flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM)
601 }
602
603 fn effective_z_index(&self, fragment_flags: FragmentFlags) -> i32 {
607 if self.z_index_applies(fragment_flags) {
608 self.get_position().z_index.integer_or(0)
609 } else {
610 0
611 }
612 }
613
614 fn effective_overflow(&self, fragment_flags: FragmentFlags) -> AxesOverflow {
618 if fragment_flags.contains(FragmentFlags::PROPAGATED_OVERFLOW_TO_VIEWPORT) {
621 return AxesOverflow::default();
622 }
623
624 let mut overflow = AxesOverflow::from(self);
625
626 if fragment_flags.contains(FragmentFlags::IS_REPLACED) {
629 if overflow.x != Overflow::Visible {
630 overflow.x = Overflow::Clip;
631 }
632 if overflow.y != Overflow::Visible {
633 overflow.y = Overflow::Clip;
634 }
635 return overflow;
636 }
637
638 let ignores_overflow = match self.get_box().display.inside() {
639 stylo::DisplayInside::Flow => self.is_inline_box(fragment_flags),
642
643 stylo::DisplayInside::Table => {
651 !matches!(self.pseudo(), Some(PseudoElement::ServoTableGrid)) ||
652 matches!(overflow.x, Overflow::Auto | Overflow::Scroll) ||
653 matches!(overflow.y, Overflow::Auto | Overflow::Scroll)
654 },
655
656 stylo::DisplayInside::TableColumn |
659 stylo::DisplayInside::TableColumnGroup |
660 stylo::DisplayInside::TableRow |
661 stylo::DisplayInside::TableRowGroup |
662 stylo::DisplayInside::TableHeaderGroup |
663 stylo::DisplayInside::TableFooterGroup => true,
664
665 _ => false,
666 };
667 if ignores_overflow {
668 return AxesOverflow::default();
669 }
670
671 overflow
672 }
673
674 fn used_transform_style(&self, fragment_flags: FragmentFlags) -> ComputedTransformStyle {
677 let box_style = self.get_box();
680 if box_style.transform_style == ComputedTransformStyle::Flat {
681 return ComputedTransformStyle::Flat;
682 }
683
684 let effects = self.get_effects();
701 let overflow = self.effective_overflow(fragment_flags);
702 if !matches!(overflow.x, Overflow::Visible | Overflow::Clip) ||
703 !matches!(overflow.y, Overflow::Visible | Overflow::Clip) ||
704 effects.opacity < 1.0 ||
705 !effects.filter.0.is_empty() ||
706 !effects.clip.is_auto() ||
707 self.get_svg().clip_path != ClipPath::None ||
708 self.get_box().isolation == ComputedIsolation::Isolate ||
709 effects.mix_blend_mode != ComputedMixBlendMode::Normal
710 {
711 return ComputedTransformStyle::Flat;
712 }
713
714 box_style.transform_style
716 }
717
718 fn establishes_block_formatting_context(&self, fragment_flags: FragmentFlags) -> bool {
725 if self.establishes_scroll_container(fragment_flags) {
726 return true;
727 }
728
729 if self.get_column().is_multicol() {
730 return true;
731 }
732
733 if self.get_column().column_span == ColumnSpan::All {
734 return true;
735 }
736
737 if self.get_position().align_content.primary() != AlignFlags::NORMAL {
743 return true;
744 }
745
746 false
748 }
749
750 fn establishes_scroll_container(&self, fragment_flags: FragmentFlags) -> bool {
752 self.effective_overflow(fragment_flags)
753 .establishes_scroll_container()
754 }
755
756 fn establishes_or_extends_3d_rendering_context(&self, fragment_flags: FragmentFlags) -> bool {
758 self.is_transformable(fragment_flags) &&
763 self.used_transform_style(fragment_flags) == ComputedTransformStyle::Preserve3d
764 }
765
766 fn establishes_stacking_context(&self, fragment_flags: FragmentFlags) -> bool {
768 let will_change_bits = self.get_will_change().bits;
772 if will_change_bits
773 .intersects(WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL | WillChangeBits::OPACITY)
774 {
775 return true;
776 }
777
778 if self.z_index_applies(fragment_flags) &&
781 (!self.get_position().z_index.is_auto() ||
782 will_change_bits.intersects(WillChangeBits::Z_INDEX))
783 {
784 return true;
785 }
786
787 if matches!(
790 self.get_box().position,
791 ComputedPosition::Fixed | ComputedPosition::Sticky
792 ) {
793 return true;
794 }
795
796 if self.is_transformable(fragment_flags) &&
811 (self.has_transform_or_perspective_style() ||
812 self.used_transform_style(fragment_flags) ==
813 ComputedTransformStyle::Preserve3d ||
814 will_change_bits
815 .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE))
816 {
817 return true;
818 }
819
820 let effects = self.get_effects();
824 if effects.opacity != 1.0 {
825 return true;
826 }
827
828 if !effects.filter.0.is_empty() {
832 return true;
833 }
834
835 if effects.mix_blend_mode != ComputedMixBlendMode::Normal {
839 return true;
840 }
841
842 if self.get_svg().clip_path != ClipPath::None {
846 return true;
847 }
848
849 if self.get_box().isolation == ComputedIsolation::Isolate {
853 return true;
854 }
855
856 if fragment_flags.contains(FragmentFlags::IS_ROOT_ELEMENT) {
859 return true;
860 }
861
862 false
864 }
865
866 fn establishes_containing_block_for_absolute_descendants(
873 &self,
874 fragment_flags: FragmentFlags,
875 ) -> bool {
876 if self.establishes_containing_block_for_all_descendants(fragment_flags) {
877 return true;
878 }
879
880 if self
885 .get_will_change()
886 .bits
887 .intersects(WillChangeBits::POSITION)
888 {
889 return true;
890 }
891
892 self.get_box().position != ComputedPosition::Static
893 }
894
895 fn establishes_containing_block_for_all_descendants(
900 &self,
901 fragment_flags: FragmentFlags,
902 ) -> bool {
903 let will_change_bits = self.get_will_change().bits;
908
909 if self.is_transformable(fragment_flags) &&
925 (self.has_transform_or_perspective_style() ||
926 self.used_transform_style(fragment_flags) ==
927 ComputedTransformStyle::Preserve3d ||
928 will_change_bits
929 .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE))
930 {
931 return true;
932 }
933
934 if !fragment_flags.contains(FragmentFlags::IS_ROOT_ELEMENT) &&
939 (!self.get_effects().filter.0.is_empty() ||
940 will_change_bits.intersects(WillChangeBits::FIXPOS_CB_NON_SVG))
941 {
942 return true;
943 }
944
945 false
947 }
948
949 fn preferred_aspect_ratio(
953 &self,
954 natural_aspect_ratio: Option<CSSFloat>,
955 padding_border_sums: &LogicalVec2<Au>,
956 ) -> Option<AspectRatio> {
957 let &GenericAspectRatio {
958 ref auto,
959 ratio: mut preferred_ratio,
960 } = self.get_aspect_ratio();
961
962 if matches!(preferred_ratio, PreferredRatio::Ratio(ratio) if ratio.is_degenerate()) {
965 preferred_ratio = PreferredRatio::None;
966 }
967
968 let to_logical_ratio = |physical_ratio| {
969 if self.writing_mode.is_horizontal() {
970 physical_ratio
971 } else {
972 1.0 / physical_ratio
973 }
974 };
975
976 match (auto, preferred_ratio) {
977 (_, PreferredRatio::None) => natural_aspect_ratio
985 .map(to_logical_ratio)
986 .map(AspectRatio::from_logical_content_ratio),
987 (true, PreferredRatio::Ratio(preferred_ratio)) => Some({
994 let physical_ratio = natural_aspect_ratio
995 .unwrap_or_else(|| (preferred_ratio.0).0 / (preferred_ratio.1).0);
996 AspectRatio::from_logical_content_ratio(to_logical_ratio(physical_ratio))
997 }),
998
999 (false, PreferredRatio::Ratio(preferred_ratio)) => {
1003 let box_sizing_adjustment = match self.get_box_sizing() {
1006 BoxSizing::ContentBox => LogicalVec2::zero(),
1007 BoxSizing::BorderBox => *padding_border_sums,
1008 };
1009 Some(AspectRatio {
1010 i_over_b: to_logical_ratio((preferred_ratio.0).0 / (preferred_ratio.1).0),
1011 box_sizing_adjustment,
1012 })
1013 },
1014 }
1015 }
1016
1017 fn background_is_transparent(&self) -> bool {
1019 let background = self.get_background();
1020 let color = self.resolve_color(&background.background_color);
1021 color.alpha == 0.0 &&
1022 background
1023 .background_image
1024 .0
1025 .iter()
1026 .all(|layer| matches!(layer, ComputedImageLayer::None))
1027 }
1028
1029 fn webrender_primitive_flags(&self) -> wr::PrimitiveFlags {
1032 match self.get_box().backface_visibility {
1033 BackfaceVisiblity::Visible => wr::PrimitiveFlags::default(),
1034 BackfaceVisiblity::Hidden => wr::PrimitiveFlags::empty(),
1035 }
1036 }
1037
1038 fn bidi_control_chars(&self) -> (&'static str, &'static str) {
1042 match (
1043 self.get_text().unicode_bidi,
1044 self.get_inherited_box().direction,
1045 ) {
1046 (UnicodeBidi::Normal, _) => ("", ""),
1047 (UnicodeBidi::Embed, Direction::Ltr) => ("\u{202a}", "\u{202c}"),
1048 (UnicodeBidi::Embed, Direction::Rtl) => ("\u{202b}", "\u{202c}"),
1049 (UnicodeBidi::Isolate, Direction::Ltr) => ("\u{2066}", "\u{2069}"),
1050 (UnicodeBidi::Isolate, Direction::Rtl) => ("\u{2067}", "\u{2069}"),
1051 (UnicodeBidi::BidiOverride, Direction::Ltr) => ("\u{202d}", "\u{202c}"),
1052 (UnicodeBidi::BidiOverride, Direction::Rtl) => ("\u{202e}", "\u{202c}"),
1053 (UnicodeBidi::IsolateOverride, Direction::Ltr) => {
1054 ("\u{2068}\u{202d}", "\u{202c}\u{2069}")
1055 },
1056 (UnicodeBidi::IsolateOverride, Direction::Rtl) => {
1057 ("\u{2068}\u{202e}", "\u{202c}\u{2069}")
1058 },
1059 (UnicodeBidi::Plaintext, _) => ("\u{2068}", "\u{2069}"),
1060 }
1061 }
1062
1063 fn resolve_align_self(
1064 &self,
1065 resolved_auto_value: ItemPlacement,
1066 resolved_normal_value: AlignFlags,
1067 ) -> SelfAlignment {
1068 SelfAlignment(match self.get_align_self().0 {
1069 AlignFlags::AUTO => resolved_auto_value.0,
1070 AlignFlags::NORMAL => resolved_normal_value,
1071 value => value,
1072 })
1073 }
1074
1075 fn depends_on_block_constraints_due_to_relative_positioning(
1076 &self,
1077 writing_mode: WritingMode,
1078 ) -> bool {
1079 if !matches!(
1080 self.get_box().position,
1081 ComputedPosition::Relative | ComputedPosition::Sticky
1082 ) {
1083 return false;
1084 }
1085 let box_offsets = self.box_offsets(writing_mode);
1086 let has_percentage = |offset: LengthPercentageOrAuto<'_>| {
1087 offset
1088 .non_auto()
1089 .is_some_and(LengthPercentage::has_percentage)
1090 };
1091 has_percentage(box_offsets.block_start) || has_percentage(box_offsets.block_end)
1092 }
1093
1094 fn overflow_direction(&self) -> OverflowDirection {
1096 let inline_end_direction = self.writing_mode.inline_end_physical_side();
1097 let block_end_direction = self.writing_mode.block_end_physical_side();
1098
1099 let rightward = inline_end_direction == PhysicalSide::Right ||
1100 block_end_direction == PhysicalSide::Right;
1101 let downward = inline_end_direction == PhysicalSide::Bottom ||
1102 block_end_direction == PhysicalSide::Bottom;
1103
1104 OverflowDirection {
1106 rightward,
1107 downward,
1108 }
1109 }
1110
1111 fn to_bidi_level(&self) -> Level {
1115 if self.writing_mode.is_bidi_ltr() {
1116 Level::ltr()
1117 } else {
1118 Level::rtl()
1119 }
1120 }
1121}
1122
1123pub(crate) enum LayoutStyle<'a> {
1124 Default(&'a ComputedValues),
1125 Table(TableLayoutStyle<'a>),
1126}
1127
1128impl LayoutStyle<'_> {
1129 #[inline]
1130 pub(crate) fn style(&self) -> &ComputedValues {
1131 match self {
1132 Self::Default(style) => style,
1133 Self::Table(table) => table.style(),
1134 }
1135 }
1136
1137 #[inline]
1138 pub(crate) fn is_table(&self) -> bool {
1139 matches!(self, Self::Table(_))
1140 }
1141
1142 pub(crate) fn content_box_sizes_and_padding_border_margin(
1143 &self,
1144 containing_block: &IndefiniteContainingBlock,
1145 ) -> ContentBoxSizesAndPBM {
1146 let containing_block_size_or_zero =
1152 containing_block.size.map(|value| value.unwrap_or_default());
1153 let writing_mode = containing_block.style.writing_mode;
1154 let pbm = self.padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1155 writing_mode,
1156 containing_block_size_or_zero.inline,
1157 );
1158 let style = self.style();
1159 let box_size = style.box_size(writing_mode);
1160 let min_size = style.min_box_size(writing_mode);
1161 let max_size = style.max_box_size(writing_mode);
1162 let preferred_size_computes_to_auto = box_size.map(|size| size.is_initial());
1163
1164 let depends_on_block_constraints = |size: &Size<LengthPercentage>| {
1165 match size {
1166 Size::Stretch => true,
1174 Size::Numeric(length_percentage) => length_percentage.has_percentage(),
1175 _ => false,
1176 }
1177 };
1178 let depends_on_block_constraints = depends_on_block_constraints(&box_size.block) ||
1179 depends_on_block_constraints(&min_size.block) ||
1180 depends_on_block_constraints(&max_size.block) ||
1181 style.depends_on_block_constraints_due_to_relative_positioning(writing_mode);
1182
1183 let box_size = box_size.map_with(&containing_block.size, |size, basis| {
1184 size.resolve_percentages_for_preferred(*basis)
1185 });
1186 let content_box_size = style.content_box_size_for_box_size(box_size, &pbm);
1187 let min_size = min_size.percentages_relative_to_basis(&containing_block_size_or_zero);
1188 let content_min_box_size = style.content_min_box_size_for_min_size(min_size, &pbm);
1189 let max_size = max_size.map_with(&containing_block.size, |size, basis| {
1190 size.resolve_percentages_for_max(*basis)
1191 });
1192 let content_max_box_size = style.content_max_box_size_for_max_size(max_size, &pbm);
1193 ContentBoxSizesAndPBM {
1194 content_box_sizes: LogicalVec2 {
1195 block: Sizes::new(
1196 content_box_size.block,
1197 content_min_box_size.block,
1198 content_max_box_size.block,
1199 ),
1200 inline: Sizes::new(
1201 content_box_size.inline,
1202 content_min_box_size.inline,
1203 content_max_box_size.inline,
1204 ),
1205 },
1206 pbm,
1207 depends_on_block_constraints,
1208 preferred_size_computes_to_auto,
1209 }
1210 }
1211
1212 pub(crate) fn padding_border_margin(
1213 &self,
1214 containing_block: &ContainingBlock,
1215 ) -> PaddingBorderMargin {
1216 self.padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1217 containing_block.style.writing_mode,
1218 containing_block.size.inline,
1219 )
1220 }
1221
1222 pub(crate) fn padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1223 &self,
1224 writing_mode: WritingMode,
1225 containing_block_inline_size: Au,
1226 ) -> PaddingBorderMargin {
1227 let padding = self
1228 .padding(writing_mode)
1229 .percentages_relative_to(containing_block_inline_size);
1230 let style = self.style();
1231 let border = self.border_width(writing_mode);
1232 let margin = style
1233 .margin(writing_mode)
1234 .percentages_relative_to(containing_block_inline_size);
1235 PaddingBorderMargin {
1236 padding_border_sums: LogicalVec2 {
1237 inline: padding.inline_sum() + border.inline_sum(),
1238 block: padding.block_sum() + border.block_sum(),
1239 },
1240 padding,
1241 border,
1242 margin,
1243 }
1244 }
1245
1246 pub(crate) fn padding(
1247 &self,
1248 containing_block_writing_mode: WritingMode,
1249 ) -> LogicalSides<LengthPercentage> {
1250 if matches!(self, Self::Table(table) if table.collapses_borders()) {
1251 return LogicalSides::zero();
1254 }
1255 let padding = self.style().get_padding().clone();
1256 LogicalSides::from_physical(
1257 &PhysicalSides::new(
1258 padding.padding_top.0,
1259 padding.padding_right.0,
1260 padding.padding_bottom.0,
1261 padding.padding_left.0,
1262 ),
1263 containing_block_writing_mode,
1264 )
1265 }
1266
1267 pub(crate) fn border_width(
1268 &self,
1269 containing_block_writing_mode: WritingMode,
1270 ) -> LogicalSides<Au> {
1271 let border_width = match self {
1272 Self::Table(table) if table.collapses_borders() => table
1276 .halved_collapsed_border_widths()
1277 .to_physical(self.style().writing_mode),
1278 _ => {
1279 let border = self.style().get_border();
1280 let resolve = |width: &BorderSideWidth, style: BorderStyle| {
1281 if style.none_or_hidden() {
1282 Au::zero()
1283 } else {
1284 width.0
1285 }
1286 };
1287 PhysicalSides::new(
1288 resolve(&border.border_top_width, border.border_top_style),
1289 resolve(&border.border_right_width, border.border_right_style),
1290 resolve(&border.border_bottom_width, border.border_bottom_style),
1291 resolve(&border.border_left_width, border.border_left_style),
1292 )
1293 },
1294 };
1295 LogicalSides::from_physical(&border_width, containing_block_writing_mode)
1296 }
1297}
1298
1299impl From<stylo::Display> for Display {
1300 fn from(packed: stylo::Display) -> Self {
1301 let outside = packed.outside();
1302 let inside = packed.inside();
1303
1304 let outside = match outside {
1305 stylo::DisplayOutside::Block => DisplayOutside::Block,
1306 stylo::DisplayOutside::Inline => DisplayOutside::Inline,
1307 stylo::DisplayOutside::TableCaption => {
1308 return Display::GeneratingBox(DisplayGeneratingBox::LayoutInternal(
1309 DisplayLayoutInternal::TableCaption,
1310 ));
1311 },
1312 stylo::DisplayOutside::InternalTable => {
1313 let internal = match inside {
1314 stylo::DisplayInside::TableRowGroup => DisplayLayoutInternal::TableRowGroup,
1315 stylo::DisplayInside::TableColumn => DisplayLayoutInternal::TableColumn,
1316 stylo::DisplayInside::TableColumnGroup => {
1317 DisplayLayoutInternal::TableColumnGroup
1318 },
1319 stylo::DisplayInside::TableHeaderGroup => {
1320 DisplayLayoutInternal::TableHeaderGroup
1321 },
1322 stylo::DisplayInside::TableFooterGroup => {
1323 DisplayLayoutInternal::TableFooterGroup
1324 },
1325 stylo::DisplayInside::TableRow => DisplayLayoutInternal::TableRow,
1326 stylo::DisplayInside::TableCell => DisplayLayoutInternal::TableCell,
1327 _ => unreachable!("Non-internal DisplayInside found"),
1328 };
1329 return Display::GeneratingBox(DisplayGeneratingBox::LayoutInternal(internal));
1330 },
1331 stylo::DisplayOutside::None if inside == stylo::DisplayInside::Contents => {
1334 return Display::Contents;
1335 },
1336 stylo::DisplayOutside::None => return Display::None,
1337 };
1338
1339 let inside = match inside {
1340 stylo::DisplayInside::Flow => DisplayInside::Flow {
1341 is_list_item: packed.is_list_item(),
1342 },
1343 stylo::DisplayInside::FlowRoot => DisplayInside::FlowRoot {
1344 is_list_item: packed.is_list_item(),
1345 },
1346 stylo::DisplayInside::Flex => DisplayInside::Flex,
1347 stylo::DisplayInside::Grid => DisplayInside::Grid,
1348 stylo::DisplayInside::GridLanes => unreachable!("Should be disabled behind a pref"),
1349 stylo::DisplayInside::Table => DisplayInside::Table,
1350
1351 stylo::DisplayInside::None => return Display::None,
1353 stylo::DisplayInside::Contents => return Display::Contents,
1354
1355 stylo::DisplayInside::TableRowGroup |
1356 stylo::DisplayInside::TableColumn |
1357 stylo::DisplayInside::TableColumnGroup |
1358 stylo::DisplayInside::TableHeaderGroup |
1359 stylo::DisplayInside::TableFooterGroup |
1360 stylo::DisplayInside::TableRow |
1361 stylo::DisplayInside::TableCell => unreachable!("Internal DisplayInside found"),
1362 };
1363 Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { outside, inside })
1364 }
1365}
1366
1367pub(crate) trait Clamp: Sized {
1368 fn clamp_below_max(self, max: Option<Self>) -> Self;
1369 fn clamp_between_extremums(self, min: Self, max: Option<Self>) -> Self;
1370}
1371
1372impl Clamp for Au {
1373 fn clamp_below_max(self, max: Option<Self>) -> Self {
1374 match max {
1375 None => self,
1376 Some(max) => self.min(max),
1377 }
1378 }
1379
1380 fn clamp_between_extremums(self, min: Self, max: Option<Self>) -> Self {
1381 self.clamp_below_max(max).max(min)
1382 }
1383}
1384
1385pub(crate) trait TransformExt {
1386 fn change_basis(&self, x: f32, y: f32, z: f32) -> Self;
1387}
1388
1389impl TransformExt for LayoutTransform {
1390 fn change_basis(&self, x: f32, y: f32, z: f32) -> Self {
1392 let pre_translation = Self::translation(x, y, z);
1393 let post_translation = Self::translation(-x, -y, -z);
1394 post_translation.then(self).then(&pre_translation)
1395 }
1396}