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_containing_block_for_absolute_descendants(
356 &self,
357 fragment_flags: FragmentFlags,
358 ) -> bool;
359 fn establishes_containing_block_for_all_descendants(
360 &self,
361 fragment_flags: FragmentFlags,
362 ) -> bool;
363 fn preferred_aspect_ratio(
364 &self,
365 natural_aspect_ratio: Option<CSSFloat>,
366 padding_border_sums: &LogicalVec2<Au>,
367 ) -> Option<AspectRatio>;
368 fn background_is_transparent(&self) -> bool;
369 fn get_webrender_primitive_flags(&self) -> wr::PrimitiveFlags;
370 fn bidi_control_chars(&self) -> (&'static str, &'static str);
371 fn resolve_align_self(
372 &self,
373 resolved_auto_value: ItemPlacement,
374 resolved_normal_value: AlignFlags,
375 ) -> SelfAlignment;
376 fn depends_on_block_constraints_due_to_relative_positioning(
377 &self,
378 writing_mode: WritingMode,
379 ) -> bool;
380 fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool;
381 fn is_atomic_inline_level(&self, fragment_flags: FragmentFlags) -> bool;
382 fn overflow_direction(&self) -> OverflowDirection;
383 fn to_bidi_level(&self) -> Level;
384}
385
386impl ComputedValuesExt for ComputedValues {
387 fn physical_box_offsets(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>> {
388 fn convert(inset: &Inset) -> LengthPercentageOrAuto<'_> {
389 match inset {
390 Inset::LengthPercentage(v) => LengthPercentageOrAuto::LengthPercentage(v),
391 Inset::Auto => LengthPercentageOrAuto::Auto,
392 Inset::AnchorFunction(_) => unreachable!("anchor() should be disabled"),
393 Inset::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"),
394 Inset::AnchorContainingCalcFunction(_) => {
395 unreachable!("anchor() and anchor-size() should be disabled")
396 },
397 }
398 }
399 let position = self.get_position();
400 PhysicalSides::new(
401 convert(&position.top),
402 convert(&position.right),
403 convert(&position.bottom),
404 convert(&position.left),
405 )
406 }
407
408 fn box_offsets(&self, writing_mode: WritingMode) -> LogicalSides<LengthPercentageOrAuto<'_>> {
409 LogicalSides::from_physical(&self.physical_box_offsets(), writing_mode)
410 }
411
412 fn box_size(
413 &self,
414 containing_block_writing_mode: WritingMode,
415 ) -> LogicalVec2<Size<LengthPercentage>> {
416 let position = self.get_position();
417 LogicalVec2::from_physical_size(
418 &PhysicalSize::new(
419 position.clone_width().into(),
420 position.clone_height().into(),
421 ),
422 containing_block_writing_mode,
423 )
424 }
425
426 fn min_box_size(
427 &self,
428 containing_block_writing_mode: WritingMode,
429 ) -> LogicalVec2<Size<LengthPercentage>> {
430 let position = self.get_position();
431 LogicalVec2::from_physical_size(
432 &PhysicalSize::new(
433 position.clone_min_width().into(),
434 position.clone_min_height().into(),
435 ),
436 containing_block_writing_mode,
437 )
438 }
439
440 fn max_box_size(
441 &self,
442 containing_block_writing_mode: WritingMode,
443 ) -> LogicalVec2<Size<LengthPercentage>> {
444 let position = self.get_position();
445 LogicalVec2::from_physical_size(
446 &PhysicalSize::new(
447 position.clone_max_width().into(),
448 position.clone_max_height().into(),
449 ),
450 containing_block_writing_mode,
451 )
452 }
453
454 fn content_box_size_for_box_size(
455 &self,
456 box_size: LogicalVec2<Size<Au>>,
457 pbm: &PaddingBorderMargin,
458 ) -> LogicalVec2<Size<Au>> {
459 match self.get_position().box_sizing {
460 BoxSizing::ContentBox => box_size,
461 BoxSizing::BorderBox => box_size.map_inline_and_block_sizes(
464 |value| value - pbm.padding_border_sums.inline,
465 |value| value - pbm.padding_border_sums.block,
466 ),
467 }
468 }
469
470 fn content_min_box_size_for_min_size(
471 &self,
472 min_box_size: LogicalVec2<Size<Au>>,
473 pbm: &PaddingBorderMargin,
474 ) -> LogicalVec2<Size<Au>> {
475 match self.get_position().box_sizing {
476 BoxSizing::ContentBox => min_box_size,
477 BoxSizing::BorderBox => min_box_size.map_inline_and_block_sizes(
479 |value| Au::zero().max(value - pbm.padding_border_sums.inline),
480 |value| Au::zero().max(value - pbm.padding_border_sums.block),
481 ),
482 }
483 }
484
485 fn content_max_box_size_for_max_size(
486 &self,
487 max_box_size: LogicalVec2<Size<Au>>,
488 pbm: &PaddingBorderMargin,
489 ) -> LogicalVec2<Size<Au>> {
490 match self.get_position().box_sizing {
491 BoxSizing::ContentBox => max_box_size,
492 BoxSizing::BorderBox => max_box_size.map_inline_and_block_sizes(
495 |value| value - pbm.padding_border_sums.inline,
496 |value| value - pbm.padding_border_sums.block,
497 ),
498 }
499 }
500
501 fn border_style_color(
502 &self,
503 containing_block_writing_mode: WritingMode,
504 ) -> LogicalSides<BorderStyleColor> {
505 let current_color = self.get_inherited_text().clone_color();
506 LogicalSides::from_physical(
507 &BorderStyleColor::from_border(self.get_border(), ¤t_color),
508 containing_block_writing_mode,
509 )
510 }
511
512 fn physical_margin(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>> {
513 fn convert(inset: &Margin) -> LengthPercentageOrAuto<'_> {
514 match inset {
515 Margin::LengthPercentage(v) => LengthPercentageOrAuto::LengthPercentage(v),
516 Margin::Auto => LengthPercentageOrAuto::Auto,
517 Margin::AnchorSizeFunction(_) | Margin::AnchorContainingCalcFunction(_) => {
518 unreachable!("anchor-size() should be disabled")
519 },
520 }
521 }
522 let margin = self.get_margin();
523 PhysicalSides::new(
524 convert(&margin.margin_top),
525 convert(&margin.margin_right),
526 convert(&margin.margin_bottom),
527 convert(&margin.margin_left),
528 )
529 }
530
531 fn margin(
532 &self,
533 containing_block_writing_mode: WritingMode,
534 ) -> LogicalSides<LengthPercentageOrAuto<'_>> {
535 LogicalSides::from_physical(&self.physical_margin(), containing_block_writing_mode)
536 }
537
538 fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool {
539 (self.get_box().display.is_inline_flow() &&
540 !fragment_flags.intersects(
541 FragmentFlags::IS_REPLACED |
542 FragmentFlags::IS_WIDGET |
543 FragmentFlags::IS_FLEX_OR_GRID_ITEM,
544 )) ||
545 matches!(self.pseudo(), Some(PseudoElement::FirstLetter))
546 }
547
548 fn is_atomic_inline_level(&self, fragment_flags: FragmentFlags) -> bool {
549 self.get_box().display.outside() == stylo::DisplayOutside::Inline &&
550 !self.is_inline_box(fragment_flags) &&
551 !fragment_flags.intersects(FragmentFlags::IS_FLEX_OR_GRID_ITEM)
552 }
553
554 fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool {
556 !self.is_inline_box(fragment_flags)
566 }
567
568 fn has_transform_or_perspective_style(&self) -> bool {
570 !self.get_box().transform.0.is_empty() ||
571 self.get_box().scale != GenericScale::None ||
572 self.get_box().rotate != GenericRotate::None ||
573 self.get_box().translate != GenericTranslate::None ||
574 self.get_box().perspective != Perspective::None
575 }
576
577 #[inline]
580 fn has_effective_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool {
581 self.is_transformable(fragment_flags) && self.has_transform_or_perspective_style()
582 }
583
584 fn z_index_applies(&self, fragment_flags: FragmentFlags) -> bool {
586 if self.get_box().position != ComputedPosition::Static {
589 return true;
590 }
591 fragment_flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM)
603 }
604
605 fn effective_z_index(&self, fragment_flags: FragmentFlags) -> i32 {
609 if self.z_index_applies(fragment_flags) {
610 self.get_position().z_index.integer_or(0)
611 } else {
612 0
613 }
614 }
615
616 fn effective_overflow(&self, fragment_flags: FragmentFlags) -> AxesOverflow {
620 if fragment_flags.contains(FragmentFlags::PROPAGATED_OVERFLOW_TO_VIEWPORT) {
623 return AxesOverflow::default();
624 }
625
626 let mut overflow = AxesOverflow::from(self);
627
628 if fragment_flags.contains(FragmentFlags::IS_REPLACED) {
631 if overflow.x != Overflow::Visible {
632 overflow.x = Overflow::Clip;
633 }
634 if overflow.y != Overflow::Visible {
635 overflow.y = Overflow::Clip;
636 }
637 return overflow;
638 }
639
640 let ignores_overflow = match self.get_box().display.inside() {
641 stylo::DisplayInside::Flow => self.is_inline_box(fragment_flags),
644
645 stylo::DisplayInside::Table => {
653 !matches!(self.pseudo(), Some(PseudoElement::ServoTableGrid)) ||
654 matches!(overflow.x, Overflow::Auto | Overflow::Scroll) ||
655 matches!(overflow.y, Overflow::Auto | Overflow::Scroll)
656 },
657
658 stylo::DisplayInside::TableColumn |
661 stylo::DisplayInside::TableColumnGroup |
662 stylo::DisplayInside::TableRow |
663 stylo::DisplayInside::TableRowGroup |
664 stylo::DisplayInside::TableHeaderGroup |
665 stylo::DisplayInside::TableFooterGroup => true,
666
667 _ => false,
668 };
669 if ignores_overflow {
670 return AxesOverflow::default();
671 }
672
673 overflow
674 }
675
676 fn used_transform_style(&self, fragment_flags: FragmentFlags) -> ComputedTransformStyle {
679 let box_style = self.get_box();
682 if box_style.transform_style == ComputedTransformStyle::Flat {
683 return ComputedTransformStyle::Flat;
684 }
685
686 let effects = self.get_effects();
703 let overflow = self.effective_overflow(fragment_flags);
704 if !matches!(overflow.x, Overflow::Visible | Overflow::Clip) ||
705 !matches!(overflow.y, Overflow::Visible | Overflow::Clip) ||
706 effects.opacity < 1.0 ||
707 !effects.filter.0.is_empty() ||
708 !effects.clip.is_auto() ||
709 self.get_svg().clip_path != ClipPath::None ||
710 self.get_box().isolation == ComputedIsolation::Isolate ||
711 effects.mix_blend_mode != ComputedMixBlendMode::Normal
712 {
713 return ComputedTransformStyle::Flat;
714 }
715
716 box_style.transform_style
718 }
719
720 fn establishes_block_formatting_context(&self, fragment_flags: FragmentFlags) -> bool {
727 if self.establishes_scroll_container(fragment_flags) {
728 return true;
729 }
730
731 if self.get_column().is_multicol() {
732 return true;
733 }
734
735 if self.get_column().column_span == ColumnSpan::All {
736 return true;
737 }
738
739 if self.get_position().align_content.primary() != AlignFlags::NORMAL {
745 return true;
746 }
747
748 false
750 }
751
752 fn establishes_scroll_container(&self, fragment_flags: FragmentFlags) -> bool {
754 self.effective_overflow(fragment_flags)
755 .establishes_scroll_container()
756 }
757
758 fn establishes_stacking_context(&self, fragment_flags: FragmentFlags) -> bool {
760 let will_change_bits = self.clone_will_change().bits;
764 if will_change_bits
765 .intersects(WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL | WillChangeBits::OPACITY)
766 {
767 return true;
768 }
769
770 if self.z_index_applies(fragment_flags) &&
773 (!self.get_position().z_index.is_auto() ||
774 will_change_bits.intersects(WillChangeBits::Z_INDEX))
775 {
776 return true;
777 }
778
779 if matches!(
782 self.get_box().position,
783 ComputedPosition::Fixed | ComputedPosition::Sticky
784 ) {
785 return true;
786 }
787
788 if self.is_transformable(fragment_flags) &&
803 (self.has_transform_or_perspective_style() ||
804 self.used_transform_style(fragment_flags) ==
805 ComputedTransformStyle::Preserve3d ||
806 will_change_bits
807 .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE))
808 {
809 return true;
810 }
811
812 let effects = self.get_effects();
816 if effects.opacity != 1.0 {
817 return true;
818 }
819
820 if !effects.filter.0.is_empty() {
824 return true;
825 }
826
827 if effects.mix_blend_mode != ComputedMixBlendMode::Normal {
831 return true;
832 }
833
834 if self.get_svg().clip_path != ClipPath::None {
838 return true;
839 }
840
841 if self.get_box().isolation == ComputedIsolation::Isolate {
845 return true;
846 }
847
848 if fragment_flags.contains(FragmentFlags::IS_ROOT_ELEMENT) {
851 return true;
852 }
853
854 false
856 }
857
858 fn establishes_containing_block_for_absolute_descendants(
865 &self,
866 fragment_flags: FragmentFlags,
867 ) -> bool {
868 if self.establishes_containing_block_for_all_descendants(fragment_flags) {
869 return true;
870 }
871
872 if self
877 .clone_will_change()
878 .bits
879 .intersects(WillChangeBits::POSITION)
880 {
881 return true;
882 }
883
884 self.clone_position() != ComputedPosition::Static
885 }
886
887 fn establishes_containing_block_for_all_descendants(
892 &self,
893 fragment_flags: FragmentFlags,
894 ) -> bool {
895 let will_change_bits = self.clone_will_change().bits;
900
901 if self.is_transformable(fragment_flags) &&
917 (self.has_transform_or_perspective_style() ||
918 self.used_transform_style(fragment_flags) ==
919 ComputedTransformStyle::Preserve3d ||
920 will_change_bits
921 .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE))
922 {
923 return true;
924 }
925
926 if !fragment_flags.contains(FragmentFlags::IS_ROOT_ELEMENT) &&
931 (!self.get_effects().filter.0.is_empty() ||
932 will_change_bits.intersects(WillChangeBits::FIXPOS_CB_NON_SVG))
933 {
934 return true;
935 }
936
937 false
939 }
940
941 fn preferred_aspect_ratio(
945 &self,
946 natural_aspect_ratio: Option<CSSFloat>,
947 padding_border_sums: &LogicalVec2<Au>,
948 ) -> Option<AspectRatio> {
949 let GenericAspectRatio {
950 auto,
951 ratio: mut preferred_ratio,
952 } = self.clone_aspect_ratio();
953
954 if matches!(preferred_ratio, PreferredRatio::Ratio(ratio) if ratio.is_degenerate()) {
957 preferred_ratio = PreferredRatio::None;
958 }
959
960 let to_logical_ratio = |physical_ratio| {
961 if self.writing_mode.is_horizontal() {
962 physical_ratio
963 } else {
964 1.0 / physical_ratio
965 }
966 };
967
968 match (auto, preferred_ratio) {
969 (_, PreferredRatio::None) => natural_aspect_ratio
977 .map(to_logical_ratio)
978 .map(AspectRatio::from_logical_content_ratio),
979 (true, PreferredRatio::Ratio(preferred_ratio)) => Some({
986 let physical_ratio = natural_aspect_ratio
987 .unwrap_or_else(|| (preferred_ratio.0).0 / (preferred_ratio.1).0);
988 AspectRatio::from_logical_content_ratio(to_logical_ratio(physical_ratio))
989 }),
990
991 (false, PreferredRatio::Ratio(preferred_ratio)) => {
995 let box_sizing_adjustment = match self.clone_box_sizing() {
998 BoxSizing::ContentBox => LogicalVec2::zero(),
999 BoxSizing::BorderBox => *padding_border_sums,
1000 };
1001 Some(AspectRatio {
1002 i_over_b: to_logical_ratio((preferred_ratio.0).0 / (preferred_ratio.1).0),
1003 box_sizing_adjustment,
1004 })
1005 },
1006 }
1007 }
1008
1009 fn background_is_transparent(&self) -> bool {
1011 let background = self.get_background();
1012 let color = self.resolve_color(&background.background_color);
1013 color.alpha == 0.0 &&
1014 background
1015 .background_image
1016 .0
1017 .iter()
1018 .all(|layer| matches!(layer, ComputedImageLayer::None))
1019 }
1020
1021 fn get_webrender_primitive_flags(&self) -> wr::PrimitiveFlags {
1024 match self.get_box().backface_visibility {
1025 BackfaceVisiblity::Visible => wr::PrimitiveFlags::default(),
1026 BackfaceVisiblity::Hidden => wr::PrimitiveFlags::empty(),
1027 }
1028 }
1029
1030 fn bidi_control_chars(&self) -> (&'static str, &'static str) {
1034 match (
1035 self.get_text().unicode_bidi,
1036 self.get_inherited_box().direction,
1037 ) {
1038 (UnicodeBidi::Normal, _) => ("", ""),
1039 (UnicodeBidi::Embed, Direction::Ltr) => ("\u{202a}", "\u{202c}"),
1040 (UnicodeBidi::Embed, Direction::Rtl) => ("\u{202b}", "\u{202c}"),
1041 (UnicodeBidi::Isolate, Direction::Ltr) => ("\u{2066}", "\u{2069}"),
1042 (UnicodeBidi::Isolate, Direction::Rtl) => ("\u{2067}", "\u{2069}"),
1043 (UnicodeBidi::BidiOverride, Direction::Ltr) => ("\u{202d}", "\u{202c}"),
1044 (UnicodeBidi::BidiOverride, Direction::Rtl) => ("\u{202e}", "\u{202c}"),
1045 (UnicodeBidi::IsolateOverride, Direction::Ltr) => {
1046 ("\u{2068}\u{202d}", "\u{202c}\u{2069}")
1047 },
1048 (UnicodeBidi::IsolateOverride, Direction::Rtl) => {
1049 ("\u{2068}\u{202e}", "\u{202c}\u{2069}")
1050 },
1051 (UnicodeBidi::Plaintext, _) => ("\u{2068}", "\u{2069}"),
1052 }
1053 }
1054
1055 fn resolve_align_self(
1056 &self,
1057 resolved_auto_value: ItemPlacement,
1058 resolved_normal_value: AlignFlags,
1059 ) -> SelfAlignment {
1060 SelfAlignment(match self.clone_align_self().0 {
1061 AlignFlags::AUTO => resolved_auto_value.0,
1062 AlignFlags::NORMAL => resolved_normal_value,
1063 value => value,
1064 })
1065 }
1066
1067 fn depends_on_block_constraints_due_to_relative_positioning(
1068 &self,
1069 writing_mode: WritingMode,
1070 ) -> bool {
1071 if !matches!(
1072 self.get_box().position,
1073 ComputedPosition::Relative | ComputedPosition::Sticky
1074 ) {
1075 return false;
1076 }
1077 let box_offsets = self.box_offsets(writing_mode);
1078 let has_percentage = |offset: LengthPercentageOrAuto<'_>| {
1079 offset
1080 .non_auto()
1081 .is_some_and(LengthPercentage::has_percentage)
1082 };
1083 has_percentage(box_offsets.block_start) || has_percentage(box_offsets.block_end)
1084 }
1085
1086 fn overflow_direction(&self) -> OverflowDirection {
1088 let inline_end_direction = self.writing_mode.inline_end_physical_side();
1089 let block_end_direction = self.writing_mode.block_end_physical_side();
1090
1091 let rightward = inline_end_direction == PhysicalSide::Right ||
1092 block_end_direction == PhysicalSide::Right;
1093 let downward = inline_end_direction == PhysicalSide::Bottom ||
1094 block_end_direction == PhysicalSide::Bottom;
1095
1096 OverflowDirection {
1098 rightward,
1099 downward,
1100 }
1101 }
1102
1103 fn to_bidi_level(&self) -> Level {
1107 if self.writing_mode.is_bidi_ltr() {
1108 Level::ltr()
1109 } else {
1110 Level::rtl()
1111 }
1112 }
1113}
1114
1115pub(crate) enum LayoutStyle<'a> {
1116 Default(&'a ComputedValues),
1117 Table(TableLayoutStyle<'a>),
1118}
1119
1120impl LayoutStyle<'_> {
1121 #[inline]
1122 pub(crate) fn style(&self) -> &ComputedValues {
1123 match self {
1124 Self::Default(style) => style,
1125 Self::Table(table) => table.style(),
1126 }
1127 }
1128
1129 #[inline]
1130 pub(crate) fn is_table(&self) -> bool {
1131 matches!(self, Self::Table(_))
1132 }
1133
1134 pub(crate) fn content_box_sizes_and_padding_border_margin(
1135 &self,
1136 containing_block: &IndefiniteContainingBlock,
1137 ) -> ContentBoxSizesAndPBM {
1138 let containing_block_size_or_zero =
1144 containing_block.size.map(|value| value.unwrap_or_default());
1145 let writing_mode = containing_block.style.writing_mode;
1146 let pbm = self.padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1147 writing_mode,
1148 containing_block_size_or_zero.inline,
1149 );
1150 let style = self.style();
1151 let box_size = style.box_size(writing_mode);
1152 let min_size = style.min_box_size(writing_mode);
1153 let max_size = style.max_box_size(writing_mode);
1154 let preferred_size_computes_to_auto = box_size.map(|size| size.is_initial());
1155
1156 let depends_on_block_constraints = |size: &Size<LengthPercentage>| {
1157 match size {
1158 Size::Stretch => true,
1166 Size::Numeric(length_percentage) => length_percentage.has_percentage(),
1167 _ => false,
1168 }
1169 };
1170 let depends_on_block_constraints = depends_on_block_constraints(&box_size.block) ||
1171 depends_on_block_constraints(&min_size.block) ||
1172 depends_on_block_constraints(&max_size.block) ||
1173 style.depends_on_block_constraints_due_to_relative_positioning(writing_mode);
1174
1175 let box_size = box_size.map_with(&containing_block.size, |size, basis| {
1176 size.resolve_percentages_for_preferred(*basis)
1177 });
1178 let content_box_size = style.content_box_size_for_box_size(box_size, &pbm);
1179 let min_size = min_size.percentages_relative_to_basis(&containing_block_size_or_zero);
1180 let content_min_box_size = style.content_min_box_size_for_min_size(min_size, &pbm);
1181 let max_size = max_size.map_with(&containing_block.size, |size, basis| {
1182 size.resolve_percentages_for_max(*basis)
1183 });
1184 let content_max_box_size = style.content_max_box_size_for_max_size(max_size, &pbm);
1185 ContentBoxSizesAndPBM {
1186 content_box_sizes: LogicalVec2 {
1187 block: Sizes::new(
1188 content_box_size.block,
1189 content_min_box_size.block,
1190 content_max_box_size.block,
1191 ),
1192 inline: Sizes::new(
1193 content_box_size.inline,
1194 content_min_box_size.inline,
1195 content_max_box_size.inline,
1196 ),
1197 },
1198 pbm,
1199 depends_on_block_constraints,
1200 preferred_size_computes_to_auto,
1201 }
1202 }
1203
1204 pub(crate) fn padding_border_margin(
1205 &self,
1206 containing_block: &ContainingBlock,
1207 ) -> PaddingBorderMargin {
1208 self.padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1209 containing_block.style.writing_mode,
1210 containing_block.size.inline,
1211 )
1212 }
1213
1214 pub(crate) fn padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1215 &self,
1216 writing_mode: WritingMode,
1217 containing_block_inline_size: Au,
1218 ) -> PaddingBorderMargin {
1219 let padding = self
1220 .padding(writing_mode)
1221 .percentages_relative_to(containing_block_inline_size);
1222 let style = self.style();
1223 let border = self.border_width(writing_mode);
1224 let margin = style
1225 .margin(writing_mode)
1226 .percentages_relative_to(containing_block_inline_size);
1227 PaddingBorderMargin {
1228 padding_border_sums: LogicalVec2 {
1229 inline: padding.inline_sum() + border.inline_sum(),
1230 block: padding.block_sum() + border.block_sum(),
1231 },
1232 padding,
1233 border,
1234 margin,
1235 }
1236 }
1237
1238 pub(crate) fn padding(
1239 &self,
1240 containing_block_writing_mode: WritingMode,
1241 ) -> LogicalSides<LengthPercentage> {
1242 if matches!(self, Self::Table(table) if table.collapses_borders()) {
1243 return LogicalSides::zero();
1246 }
1247 let padding = self.style().get_padding().clone();
1248 LogicalSides::from_physical(
1249 &PhysicalSides::new(
1250 padding.padding_top.0,
1251 padding.padding_right.0,
1252 padding.padding_bottom.0,
1253 padding.padding_left.0,
1254 ),
1255 containing_block_writing_mode,
1256 )
1257 }
1258
1259 pub(crate) fn border_width(
1260 &self,
1261 containing_block_writing_mode: WritingMode,
1262 ) -> LogicalSides<Au> {
1263 let border_width = match self {
1264 Self::Table(table) if table.collapses_borders() => table
1268 .halved_collapsed_border_widths()
1269 .to_physical(self.style().writing_mode),
1270 _ => {
1271 let border = self.style().get_border();
1272 let resolve = |width: &BorderSideWidth, style: BorderStyle| {
1273 if style.none_or_hidden() {
1274 Au::zero()
1275 } else {
1276 width.0
1277 }
1278 };
1279 PhysicalSides::new(
1280 resolve(&border.border_top_width, border.border_top_style),
1281 resolve(&border.border_right_width, border.border_right_style),
1282 resolve(&border.border_bottom_width, border.border_bottom_style),
1283 resolve(&border.border_left_width, border.border_left_style),
1284 )
1285 },
1286 };
1287 LogicalSides::from_physical(&border_width, containing_block_writing_mode)
1288 }
1289}
1290
1291impl From<stylo::Display> for Display {
1292 fn from(packed: stylo::Display) -> Self {
1293 let outside = packed.outside();
1294 let inside = packed.inside();
1295
1296 let outside = match outside {
1297 stylo::DisplayOutside::Block => DisplayOutside::Block,
1298 stylo::DisplayOutside::Inline => DisplayOutside::Inline,
1299 stylo::DisplayOutside::TableCaption => {
1300 return Display::GeneratingBox(DisplayGeneratingBox::LayoutInternal(
1301 DisplayLayoutInternal::TableCaption,
1302 ));
1303 },
1304 stylo::DisplayOutside::InternalTable => {
1305 let internal = match inside {
1306 stylo::DisplayInside::TableRowGroup => DisplayLayoutInternal::TableRowGroup,
1307 stylo::DisplayInside::TableColumn => DisplayLayoutInternal::TableColumn,
1308 stylo::DisplayInside::TableColumnGroup => {
1309 DisplayLayoutInternal::TableColumnGroup
1310 },
1311 stylo::DisplayInside::TableHeaderGroup => {
1312 DisplayLayoutInternal::TableHeaderGroup
1313 },
1314 stylo::DisplayInside::TableFooterGroup => {
1315 DisplayLayoutInternal::TableFooterGroup
1316 },
1317 stylo::DisplayInside::TableRow => DisplayLayoutInternal::TableRow,
1318 stylo::DisplayInside::TableCell => DisplayLayoutInternal::TableCell,
1319 _ => unreachable!("Non-internal DisplayInside found"),
1320 };
1321 return Display::GeneratingBox(DisplayGeneratingBox::LayoutInternal(internal));
1322 },
1323 stylo::DisplayOutside::None if inside == stylo::DisplayInside::Contents => {
1326 return Display::Contents;
1327 },
1328 stylo::DisplayOutside::None => return Display::None,
1329 };
1330
1331 let inside = match inside {
1332 stylo::DisplayInside::Flow => DisplayInside::Flow {
1333 is_list_item: packed.is_list_item(),
1334 },
1335 stylo::DisplayInside::FlowRoot => DisplayInside::FlowRoot {
1336 is_list_item: packed.is_list_item(),
1337 },
1338 stylo::DisplayInside::Flex => DisplayInside::Flex,
1339 stylo::DisplayInside::Grid => DisplayInside::Grid,
1340 stylo::DisplayInside::Table => DisplayInside::Table,
1341
1342 stylo::DisplayInside::None => return Display::None,
1344 stylo::DisplayInside::Contents => return Display::Contents,
1345
1346 stylo::DisplayInside::TableRowGroup |
1347 stylo::DisplayInside::TableColumn |
1348 stylo::DisplayInside::TableColumnGroup |
1349 stylo::DisplayInside::TableHeaderGroup |
1350 stylo::DisplayInside::TableFooterGroup |
1351 stylo::DisplayInside::TableRow |
1352 stylo::DisplayInside::TableCell => unreachable!("Internal DisplayInside found"),
1353 };
1354 Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { outside, inside })
1355 }
1356}
1357
1358pub(crate) trait Clamp: Sized {
1359 fn clamp_below_max(self, max: Option<Self>) -> Self;
1360 fn clamp_between_extremums(self, min: Self, max: Option<Self>) -> Self;
1361}
1362
1363impl Clamp for Au {
1364 fn clamp_below_max(self, max: Option<Self>) -> Self {
1365 match max {
1366 None => self,
1367 Some(max) => self.min(max),
1368 }
1369 }
1370
1371 fn clamp_between_extremums(self, min: Self, max: Option<Self>) -> Self {
1372 self.clamp_below_max(max).max(min)
1373 }
1374}
1375
1376pub(crate) trait TransformExt {
1377 fn change_basis(&self, x: f32, y: f32, z: f32) -> Self;
1378}
1379
1380impl TransformExt for LayoutTransform {
1381 fn change_basis(&self, x: f32, y: f32, z: f32) -> Self {
1383 let pre_translation = Self::translation(x, y, z);
1384 let post_translation = Self::translation(-x, -y, -z);
1385 post_translation.then(self).then(&pre_translation)
1386 }
1387}