From c6a399d76030481e31377e7e7e097435bb50a976 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Fri, 25 Apr 2025 05:22:22 -0700 Subject: [PATCH] layout: Implement `justify-self` for block-level boxes (#36595) https://drafts.csswg.org/css-align/#justify-block Testing: Improves various WPT tests. `justify-self-auto-margins-2.html` fails but I think the test is wrong. Signed-off-by: Oriol Brufau --- components/layout/flow/mod.rs | 105 ++++++++++++++---- .../justify-self-auto-margins-2.html.ini | 2 + .../justify-self-block-in-inline.html.ini | 3 - .../blocks/justify-self-htb-ltr-htb.html.ini | 42 ------- .../blocks/justify-self-htb-ltr-vlr.html.ini | 36 ------ .../blocks/justify-self-htb-ltr-vrl.html.ini | 36 ------ .../blocks/justify-self-htb-rtl-htb.html.ini | 42 ------- .../blocks/justify-self-htb-rtl-vlr.html.ini | 36 ------ .../blocks/justify-self-htb-rtl-vrl.html.ini | 36 ------ .../blocks/justify-self-text-align-2.html.ini | 6 - .../blocks/justify-self-text-align.html.ini | 2 - .../blocks/safe-justify-self-htb.html.ini | 36 ------ 12 files changed, 84 insertions(+), 298 deletions(-) create mode 100644 tests/wpt/meta/css/css-align/blocks/justify-self-auto-margins-2.html.ini delete mode 100644 tests/wpt/meta/css/css-align/blocks/justify-self-block-in-inline.html.ini delete mode 100644 tests/wpt/meta/css/css-align/blocks/justify-self-htb-ltr-htb.html.ini delete mode 100644 tests/wpt/meta/css/css-align/blocks/justify-self-htb-rtl-htb.html.ini delete mode 100644 tests/wpt/meta/css/css-align/blocks/justify-self-text-align-2.html.ini delete mode 100644 tests/wpt/meta/css/css-align/blocks/justify-self-text-align.html.ini delete mode 100644 tests/wpt/meta/css/css-align/blocks/safe-justify-self-htb.html.ini diff --git a/components/layout/flow/mod.rs b/components/layout/flow/mod.rs index f92650ef340..d983e8592c4 100644 --- a/components/layout/flow/mod.rs +++ b/components/layout/flow/mod.rs @@ -896,6 +896,7 @@ fn layout_in_flow_non_replaced_block_level_same_formatting_context( block_sizes, depends_on_block_constraints, available_block_size, + justify_self, } = solve_containing_block_padding_and_border_for_in_flow_box( containing_block, &layout_style, @@ -909,6 +910,7 @@ fn layout_in_flow_non_replaced_block_level_same_formatting_context( containing_block, &pbm, containing_block_for_children.size.inline, + justify_self, ); let computed_block_size = style.content_block_size(); @@ -1154,6 +1156,7 @@ impl IndependentNonReplacedContents { block_sizes, depends_on_block_constraints, available_block_size, + justify_self, } = solve_containing_block_padding_and_border_for_in_flow_box( containing_block, &layout_style, @@ -1185,7 +1188,7 @@ impl IndependentNonReplacedContents { let ResolvedMargins { margin, effective_margin_inline_start, - } = solve_margins(containing_block, &pbm, inline_size); + } = solve_margins(containing_block, &pbm, inline_size, justify_self); let content_rect = LogicalRect { start_corner: LogicalVec2 { @@ -1300,17 +1303,12 @@ impl IndependentNonReplacedContents { .sizes }; - // TODO: the automatic inline size should take `justify-self` into account. + let justify_self = resolve_justify_self(style, containing_block.style); let is_table = self.is_table(); - let automatic_inline_size = if is_table { - Size::FitContent - } else { - Size::Stretch - }; let compute_inline_size = |stretch_size| { content_box_sizes.inline.resolve( Direction::Inline, - automatic_inline_size, + automatic_inline_size(justify_self, is_table), Au::zero, Some(stretch_size), get_inline_content_sizes, @@ -1472,6 +1470,7 @@ impl IndependentNonReplacedContents { &pbm, content_size.inline + pbm.padding_border_sums.inline, placement_rect, + justify_self, ); let margin = LogicalSides { @@ -1558,6 +1557,7 @@ impl ReplacedContents { let effective_margin_inline_start; let (margin_block_start, margin_block_end) = solve_block_margins_for_in_flow_block_level(pbm); + let justify_self = resolve_justify_self(&base.style, containing_block.style); let containing_block_writing_mode = containing_block.style.writing_mode; let physical_content_size = content_size.to_physical_size(containing_block_writing_mode); @@ -1597,6 +1597,7 @@ impl ReplacedContents { pbm, size.inline, placement_rect, + justify_self, ); // Clearance prevents margin collapse between this block and previous ones, @@ -1620,6 +1621,7 @@ impl ReplacedContents { containing_block, pbm, content_size.inline, + justify_self, ); }; @@ -1671,6 +1673,7 @@ struct ContainingBlockPaddingAndBorder<'a> { block_sizes: Sizes, depends_on_block_constraints: bool, available_block_size: Option, + justify_self: AlignFlags, } struct ResolvedMargins { @@ -1719,6 +1722,9 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>( // The available block size may actually be definite, but it should be irrelevant // since the sizing properties are set to their initial value. available_block_size: None, + // The initial `justify-self` is `auto`, but use `normal` (behaving as `stretch`). + // This is being discussed in . + justify_self: AlignFlags::NORMAL, }; } @@ -1755,16 +1761,11 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>( None, /* TODO: support preferred aspect ratios on non-replaced boxes */ )) }; - // TODO: the automatic inline size should take `justify-self` into account. + let justify_self = resolve_justify_self(style, containing_block.style); let is_table = layout_style.is_table(); - let automatic_inline_size = if is_table { - Size::FitContent - } else { - Size::Stretch - }; let inline_size = content_box_sizes.inline.resolve( Direction::Inline, - automatic_inline_size, + automatic_inline_size(justify_self, is_table), Au::zero, Some(available_inline_size), get_inline_content_sizes, @@ -1793,6 +1794,7 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>( block_sizes: content_box_sizes.block, depends_on_block_constraints, available_block_size, + justify_self, } } @@ -1804,9 +1806,15 @@ fn solve_margins( containing_block: &ContainingBlock<'_>, pbm: &PaddingBorderMargin, inline_size: Au, + justify_self: AlignFlags, ) -> ResolvedMargins { let (inline_margins, effective_margin_inline_start) = - solve_inline_margins_for_in_flow_block_level(containing_block, pbm, inline_size); + solve_inline_margins_for_in_flow_block_level( + containing_block, + pbm, + inline_size, + justify_self, + ); let block_margins = solve_block_margins_for_in_flow_block_level(pbm); ResolvedMargins { margin: LogicalSides { @@ -1829,14 +1837,63 @@ fn solve_block_margins_for_in_flow_block_level(pbm: &PaddingBorderMargin) -> (Au ) } -/// This is supposed to handle 'justify-self', but no browser supports it on block boxes. -/// Instead, `
` and `
` are implemented via internal 'text-align' values. +/// Resolves the `justify-self` value, preserving flags. +fn resolve_justify_self(style: &ComputedValues, parent_style: &ComputedValues) -> AlignFlags { + let is_ltr = |style: &ComputedValues| style.writing_mode.line_left_is_inline_start(); + let alignment = match style.clone_justify_self().0.0 { + AlignFlags::AUTO => parent_style.clone_justify_items().computed.0, + alignment => alignment, + }; + let alignment_value = match alignment.value() { + AlignFlags::LEFT if is_ltr(parent_style) => AlignFlags::START, + AlignFlags::LEFT => AlignFlags::END, + AlignFlags::RIGHT if is_ltr(parent_style) => AlignFlags::END, + AlignFlags::RIGHT => AlignFlags::START, + AlignFlags::SELF_START if is_ltr(parent_style) == is_ltr(style) => AlignFlags::START, + AlignFlags::SELF_START => AlignFlags::END, + AlignFlags::SELF_END if is_ltr(parent_style) == is_ltr(style) => AlignFlags::END, + AlignFlags::SELF_END => AlignFlags::START, + alignment_value => alignment_value, + }; + alignment.flags() | alignment_value +} + +/// Determines the automatic size for the inline axis of a block-level box. +/// +#[inline] +fn automatic_inline_size(justify_self: AlignFlags, is_table: bool) -> Size { + match justify_self { + AlignFlags::STRETCH => Size::Stretch, + AlignFlags::NORMAL if !is_table => Size::Stretch, + _ => Size::FitContent, + } +} + +/// Justifies a block-level box, distributing the free space according to `justify-self`. +/// Note `
` and `
` are implemented via internal 'text-align' values, +/// which are also handled here. /// The provided free space should already take margins into account. In particular, /// it should be zero if there is an auto margin. /// -fn justify_self_alignment(containing_block: &ContainingBlock, free_space: Au) -> Au { +fn justify_self_alignment( + containing_block: &ContainingBlock, + free_space: Au, + justify_self: AlignFlags, +) -> Au { + let mut alignment = justify_self.value(); + let is_safe = justify_self.flags() == AlignFlags::SAFE || alignment == AlignFlags::NORMAL; + if is_safe && free_space <= Au::zero() { + alignment = AlignFlags::START + } + match alignment { + AlignFlags::NORMAL => {}, + AlignFlags::CENTER => return free_space / 2, + AlignFlags::END => return free_space, + _ => return Au::zero(), + } + + // For `justify-self: normal`, fall back to the special 'text-align' values. let style = containing_block.style; - debug_assert!(free_space >= Au::zero()); match style.clone_text_align() { TextAlignKeyword::MozCenter => free_space / 2, TextAlignKeyword::MozLeft if !style.writing_mode.line_left_is_inline_start() => free_space, @@ -1861,6 +1918,7 @@ fn solve_inline_margins_for_in_flow_block_level( containing_block: &ContainingBlock, pbm: &PaddingBorderMargin, inline_size: Au, + justify_self: AlignFlags, ) -> ((Au, Au), Au) { let free_space = containing_block.size.inline - pbm.padding_border_sums.inline - inline_size; let mut justification = Au::zero(); @@ -1878,8 +1936,8 @@ fn solve_inline_margins_for_in_flow_block_level( // But here we may still have some free space to perform 'justify-self' alignment. // This aligns the margin box within the containing block, or in other words, // aligns the border box within the margin-shrunken containing block. - let free_space = Au::zero().max(free_space - start - end); - justification = justify_self_alignment(containing_block, free_space); + justification = + justify_self_alignment(containing_block, free_space - start - end, justify_self); (start, end) }, }; @@ -1902,6 +1960,7 @@ fn solve_inline_margins_avoiding_floats( pbm: &PaddingBorderMargin, inline_size: Au, placement_rect: LogicalRect, + justify_self: AlignFlags, ) -> ((Au, Au), Au) { let free_space = placement_rect.size.inline - inline_size; debug_assert!(free_space >= Au::zero()); @@ -1922,7 +1981,7 @@ fn solve_inline_margins_avoiding_floats( // and Blink and WebKit are broken anyways. So we match Gecko instead: this aligns // the border box within the instersection of the float-shrunken containing-block // and the margin-shrunken containing-block. - justification = justify_self_alignment(containing_block, free_space); + justification = justify_self_alignment(containing_block, free_space, justify_self); (start, end) }, }; diff --git a/tests/wpt/meta/css/css-align/blocks/justify-self-auto-margins-2.html.ini b/tests/wpt/meta/css/css-align/blocks/justify-self-auto-margins-2.html.ini new file mode 100644 index 00000000000..046c147dc0b --- /dev/null +++ b/tests/wpt/meta/css/css-align/blocks/justify-self-auto-margins-2.html.ini @@ -0,0 +1,2 @@ +[justify-self-auto-margins-2.html] + expected: FAIL diff --git a/tests/wpt/meta/css/css-align/blocks/justify-self-block-in-inline.html.ini b/tests/wpt/meta/css/css-align/blocks/justify-self-block-in-inline.html.ini deleted file mode 100644 index f46e4cb70e6..00000000000 --- a/tests/wpt/meta/css/css-align/blocks/justify-self-block-in-inline.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[justify-self-block-in-inline.html] - [.block-in-inline 1] - expected: FAIL diff --git a/tests/wpt/meta/css/css-align/blocks/justify-self-htb-ltr-htb.html.ini b/tests/wpt/meta/css/css-align/blocks/justify-self-htb-ltr-htb.html.ini deleted file mode 100644 index 09cc50092f7..00000000000 --- a/tests/wpt/meta/css/css-align/blocks/justify-self-htb-ltr-htb.html.ini +++ /dev/null @@ -1,42 +0,0 @@ -[justify-self-htb-ltr-htb.html] - [.item 1] - expected: FAIL - - [.item 2] - expected: FAIL - - [.item 3] - expected: FAIL - - [.item 4] - expected: FAIL - - [.item 5] - expected: FAIL - - [.item 6] - expected: FAIL - - [.item 7] - expected: FAIL - - [.item 9] - expected: FAIL - - [.item 10] - expected: FAIL - - [.item 11] - expected: FAIL - - [.item 12] - expected: FAIL - - [.item 13] - expected: FAIL - - [.item 14] - expected: FAIL - - [.item 15] - expected: FAIL diff --git a/tests/wpt/meta/css/css-align/blocks/justify-self-htb-ltr-vlr.html.ini b/tests/wpt/meta/css/css-align/blocks/justify-self-htb-ltr-vlr.html.ini index 91b531b8c25..e299569c1f9 100644 --- a/tests/wpt/meta/css/css-align/blocks/justify-self-htb-ltr-vlr.html.ini +++ b/tests/wpt/meta/css/css-align/blocks/justify-self-htb-ltr-vlr.html.ini @@ -1,42 +1,6 @@ [justify-self-htb-ltr-vlr.html] - [.item 1] - expected: FAIL - - [.item 2] - expected: FAIL - - [.item 3] - expected: FAIL - - [.item 4] - expected: FAIL - - [.item 5] - expected: FAIL - - [.item 6] - expected: FAIL - - [.item 7] - expected: FAIL - - [.item 9] - expected: FAIL - - [.item 10] - expected: FAIL - - [.item 11] - expected: FAIL - [.item 12] expected: FAIL [.item 13] expected: FAIL - - [.item 14] - expected: FAIL - - [.item 15] - expected: FAIL diff --git a/tests/wpt/meta/css/css-align/blocks/justify-self-htb-ltr-vrl.html.ini b/tests/wpt/meta/css/css-align/blocks/justify-self-htb-ltr-vrl.html.ini index f67f288e230..bce83361fd5 100644 --- a/tests/wpt/meta/css/css-align/blocks/justify-self-htb-ltr-vrl.html.ini +++ b/tests/wpt/meta/css/css-align/blocks/justify-self-htb-ltr-vrl.html.ini @@ -1,42 +1,6 @@ [justify-self-htb-ltr-vrl.html] - [.item 1] - expected: FAIL - - [.item 2] - expected: FAIL - - [.item 3] - expected: FAIL - [.item 4] expected: FAIL [.item 5] expected: FAIL - - [.item 6] - expected: FAIL - - [.item 7] - expected: FAIL - - [.item 9] - expected: FAIL - - [.item 10] - expected: FAIL - - [.item 11] - expected: FAIL - - [.item 12] - expected: FAIL - - [.item 13] - expected: FAIL - - [.item 14] - expected: FAIL - - [.item 15] - expected: FAIL diff --git a/tests/wpt/meta/css/css-align/blocks/justify-self-htb-rtl-htb.html.ini b/tests/wpt/meta/css/css-align/blocks/justify-self-htb-rtl-htb.html.ini deleted file mode 100644 index 06c734facf2..00000000000 --- a/tests/wpt/meta/css/css-align/blocks/justify-self-htb-rtl-htb.html.ini +++ /dev/null @@ -1,42 +0,0 @@ -[justify-self-htb-rtl-htb.html] - [.item 1] - expected: FAIL - - [.item 2] - expected: FAIL - - [.item 3] - expected: FAIL - - [.item 4] - expected: FAIL - - [.item 5] - expected: FAIL - - [.item 6] - expected: FAIL - - [.item 7] - expected: FAIL - - [.item 9] - expected: FAIL - - [.item 10] - expected: FAIL - - [.item 11] - expected: FAIL - - [.item 12] - expected: FAIL - - [.item 13] - expected: FAIL - - [.item 14] - expected: FAIL - - [.item 15] - expected: FAIL diff --git a/tests/wpt/meta/css/css-align/blocks/justify-self-htb-rtl-vlr.html.ini b/tests/wpt/meta/css/css-align/blocks/justify-self-htb-rtl-vlr.html.ini index f1cd616a3ce..a8bb4abda0f 100644 --- a/tests/wpt/meta/css/css-align/blocks/justify-self-htb-rtl-vlr.html.ini +++ b/tests/wpt/meta/css/css-align/blocks/justify-self-htb-rtl-vlr.html.ini @@ -1,42 +1,6 @@ [justify-self-htb-rtl-vlr.html] - [.item 1] - expected: FAIL - - [.item 2] - expected: FAIL - - [.item 3] - expected: FAIL - - [.item 4] - expected: FAIL - - [.item 5] - expected: FAIL - - [.item 6] - expected: FAIL - - [.item 7] - expected: FAIL - - [.item 9] - expected: FAIL - - [.item 10] - expected: FAIL - - [.item 11] - expected: FAIL - [.item 12] expected: FAIL [.item 13] expected: FAIL - - [.item 14] - expected: FAIL - - [.item 15] - expected: FAIL diff --git a/tests/wpt/meta/css/css-align/blocks/justify-self-htb-rtl-vrl.html.ini b/tests/wpt/meta/css/css-align/blocks/justify-self-htb-rtl-vrl.html.ini index e4df4202cb0..be0e1f9db72 100644 --- a/tests/wpt/meta/css/css-align/blocks/justify-self-htb-rtl-vrl.html.ini +++ b/tests/wpt/meta/css/css-align/blocks/justify-self-htb-rtl-vrl.html.ini @@ -1,42 +1,6 @@ [justify-self-htb-rtl-vrl.html] - [.item 1] - expected: FAIL - - [.item 2] - expected: FAIL - - [.item 3] - expected: FAIL - [.item 4] expected: FAIL [.item 5] expected: FAIL - - [.item 6] - expected: FAIL - - [.item 7] - expected: FAIL - - [.item 9] - expected: FAIL - - [.item 10] - expected: FAIL - - [.item 11] - expected: FAIL - - [.item 12] - expected: FAIL - - [.item 13] - expected: FAIL - - [.item 14] - expected: FAIL - - [.item 15] - expected: FAIL diff --git a/tests/wpt/meta/css/css-align/blocks/justify-self-text-align-2.html.ini b/tests/wpt/meta/css/css-align/blocks/justify-self-text-align-2.html.ini deleted file mode 100644 index 53ddc48d986..00000000000 --- a/tests/wpt/meta/css/css-align/blocks/justify-self-text-align-2.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[justify-self-text-align-2.html] - [.item 2] - expected: FAIL - - [.item 3] - expected: FAIL diff --git a/tests/wpt/meta/css/css-align/blocks/justify-self-text-align.html.ini b/tests/wpt/meta/css/css-align/blocks/justify-self-text-align.html.ini deleted file mode 100644 index b36a5fb8ed4..00000000000 --- a/tests/wpt/meta/css/css-align/blocks/justify-self-text-align.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[justify-self-text-align.html] - expected: FAIL diff --git a/tests/wpt/meta/css/css-align/blocks/safe-justify-self-htb.html.ini b/tests/wpt/meta/css/css-align/blocks/safe-justify-self-htb.html.ini deleted file mode 100644 index bafb7a7d2c2..00000000000 --- a/tests/wpt/meta/css/css-align/blocks/safe-justify-self-htb.html.ini +++ /dev/null @@ -1,36 +0,0 @@ -[safe-justify-self-htb.html] - [.item 7] - expected: FAIL - - [.item 8] - expected: FAIL - - [.item 9] - expected: FAIL - - [.item 10] - expected: FAIL - - [.item 11] - expected: FAIL - - [.item 12] - expected: FAIL - - [.item 19] - expected: FAIL - - [.item 20] - expected: FAIL - - [.item 21] - expected: FAIL - - [.item 22] - expected: FAIL - - [.item 23] - expected: FAIL - - [.item 24] - expected: FAIL