Handle min/max sizes in non-replaced positioned boxes

This commit is contained in:
Delan Azabani 2023-04-12 15:38:52 +08:00
parent df83e2a998
commit 73dcce12b6
34 changed files with 240 additions and 113 deletions

View file

@ -15,7 +15,7 @@ use rayon::iter::{IntoParallelRefMutIterator, ParallelExtend};
use rayon_croissant::ParallelIteratorExt;
use style::computed_values::position::T as Position;
use style::properties::ComputedValues;
use style::values::computed::{Length, LengthPercentage};
use style::values::computed::{CSSPixelLength, Length, LengthPercentage};
use style::values::specified::text::TextDecorationLine;
use style::Zero;
@ -424,7 +424,7 @@ impl HoistedAbsolutelyPositionedBox {
.style()
.padding_border_margin(&containing_block.into());
let size = match &absolutely_positioned_box.context {
let computed_size = match &absolutely_positioned_box.context {
IndependentFormattingContext::Replaced(replaced) => {
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
@ -445,32 +445,32 @@ impl HoistedAbsolutelyPositionedBox {
let shared_fragment = self.fragment.borrow();
let inline_axis = solve_axis(
cbis,
pbm.padding_border_sums.inline,
pbm.margin.inline_start,
pbm.margin.inline_end,
/* avoid_negative_margin_start */ true,
&shared_fragment.box_offsets.inline,
size.inline,
);
let block_axis = solve_axis(
cbis,
pbm.padding_border_sums.block,
pbm.margin.block_start,
pbm.margin.block_end,
/* avoid_negative_margin_start */ false,
&shared_fragment.box_offsets.block,
size.block,
);
let margin = Sides {
inline_start: inline_axis.margin_start,
inline_end: inline_axis.margin_end,
block_start: block_axis.margin_start,
block_end: block_axis.margin_end,
let solve_inline_axis = |computed_size| {
solve_axis(
cbis,
pbm.padding_border_sums.inline,
pbm.margin.inline_start,
pbm.margin.inline_end,
/* avoid_negative_margin_start */ true,
&shared_fragment.box_offsets.inline,
computed_size,
)
};
let solve_block_axis = |computed_size| {
solve_axis(
// TODO(delan) shouldnt this be cbbs?
cbis,
pbm.padding_border_sums.block,
pbm.margin.block_start,
pbm.margin.block_end,
/* avoid_negative_margin_start */ false,
&shared_fragment.box_offsets.block,
computed_size,
)
};
let mut inline_axis = solve_inline_axis(computed_size.inline);
let mut block_axis = solve_block_axis(computed_size.block);
let mut positioning_context =
PositioningContext::new_for_style(absolutely_positioned_box.context.style()).unwrap();
@ -482,55 +482,134 @@ impl HoistedAbsolutelyPositionedBox {
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
let style = &replaced.style;
content_size = size.auto_is(|| unreachable!());
content_size = computed_size.auto_is(|| unreachable!());
fragments = replaced
.contents
.make_fragments(style, content_size.clone());
},
IndependentFormattingContext::NonReplaced(non_replaced) => {
// https://drafts.csswg.org/css2/#min-max-widths
// https://drafts.csswg.org/css2/#min-max-heights
let min_size = non_replaced
.style
.content_min_box_size(&containing_block.into(), &pbm)
.auto_is(|| Length::zero());
let max_size = non_replaced
.style
.content_max_box_size(&containing_block.into(), &pbm);
// https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width
// https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-height
let inline_size = inline_axis.size.auto_is(|| {
let mut inline_size = inline_axis.size.auto_is(|| {
let anchor = match inline_axis.anchor {
Anchor::Start(start) => start,
Anchor::End(end) => end,
};
let margin_sum = inline_axis.margin_start + inline_axis.margin_end;
let available_size =
cbis - anchor - pbm.padding_border_sums.inline - margin.inline_sum();
cbis - anchor - pbm.padding_border_sums.inline - margin_sum;
non_replaced
.inline_content_sizes(layout_context)
.shrink_to_fit(available_size)
});
let containing_block_for_children = ContainingBlock {
inline_size,
block_size: block_axis.size,
style: &non_replaced.style,
};
// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
assert_eq!(
containing_block.style.writing_mode,
containing_block_for_children.style.writing_mode,
"Mixed writing modes are not supported yet"
);
let dummy_tree_rank = 0;
let independent_layout = non_replaced.layout(
layout_context,
&mut positioning_context,
&containing_block_for_children,
dummy_tree_rank,
);
// If the tentative used inline size is greater than max-inline-size,
// recalculate the inline size and margins with max-inline-size as the
// computed inline-size. We can assume the new inline size wont be auto,
// because a non-auto computed inline-size always becomes the used value.
// https://drafts.csswg.org/css2/#min-max-widths (step 2)
if let Some(max) = max_size.inline {
if inline_size > max {
inline_axis = solve_inline_axis(LengthOrAuto::LengthPercentage(max));
inline_size = inline_axis.size.auto_is(|| unreachable!());
}
}
content_size = Vec2 {
inline: inline_size,
block: block_axis
.size
.auto_is(|| independent_layout.content_block_size),
// If the tentative used inline size is less than min-inline-size,
// recalculate the inline size and margins with min-inline-size as the
// computed inline-size. We can assume the new inline size wont be auto,
// because a non-auto computed inline-size always becomes the used value.
// https://drafts.csswg.org/css2/#min-max-widths (step 3)
if inline_size < min_size.inline {
inline_axis =
solve_inline_axis(LengthOrAuto::LengthPercentage(min_size.inline));
inline_size = inline_axis.size.auto_is(|| unreachable!());
}
struct Result {
content_size: Vec2<CSSPixelLength>,
fragments: Vec<Fragment>,
}
// If we end up recalculating the block size and margins below, we also need
// to relayout the children with a containing block of that size, otherwise
// percentages may be resolved incorrectly.
let mut try_layout = |size| {
let containing_block_for_children = ContainingBlock {
inline_size,
block_size: size,
style: &non_replaced.style,
};
// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
assert_eq!(
containing_block.style.writing_mode,
containing_block_for_children.style.writing_mode,
"Mixed writing modes are not supported yet"
);
let dummy_tree_rank = 0;
let independent_layout = non_replaced.layout(
layout_context,
&mut positioning_context,
&containing_block_for_children,
dummy_tree_rank,
);
let block_size = size.auto_is(|| independent_layout.content_block_size);
Result {
content_size: Vec2 {
inline: inline_size,
block: block_size,
},
fragments: independent_layout.fragments,
}
};
fragments = independent_layout.fragments
let mut result = try_layout(block_axis.size);
// If the tentative used block size is greater than max-block-size,
// recalculate the block size and margins with max-block-size as the
// computed block-size. We can assume the new block size wont be auto,
// because a non-auto computed block-size always becomes the used value.
// https://drafts.csswg.org/css2/#min-max-heights (step 2)
if let Some(max) = max_size.block {
if result.content_size.block > max {
block_axis = solve_block_axis(LengthOrAuto::LengthPercentage(max));
result = try_layout(LengthOrAuto::LengthPercentage(max));
}
}
// If the tentative used block size is less than min-block-size,
// recalculate the block size and margins with min-block-size as the
// computed block-size. We can assume the new block size wont be auto,
// because a non-auto computed block-size always becomes the used value.
// https://drafts.csswg.org/css2/#min-max-heights (step 3)
if result.content_size.block < min_size.block {
block_axis =
solve_block_axis(LengthOrAuto::LengthPercentage(min_size.block));
result = try_layout(LengthOrAuto::LengthPercentage(min_size.block));
}
content_size = result.content_size;
fragments = result.fragments;
},
};
let margin = Sides {
inline_start: inline_axis.margin_start,
inline_end: inline_axis.margin_end,
block_start: block_axis.margin_start,
block_end: block_axis.margin_end,
};
let pb = &pbm.padding + &pbm.border;
let inline_start = match inline_axis.anchor {
Anchor::Start(start) => start + pb.inline_start + margin.inline_start,
@ -601,24 +680,24 @@ fn solve_axis(
computed_margin_end: LengthOrAuto,
avoid_negative_margin_start: bool,
box_offsets: &AbsoluteBoxOffsets,
size: LengthOrAuto,
computed_size: LengthOrAuto,
) -> AxisResult {
match box_offsets {
AbsoluteBoxOffsets::StaticStart { start } => AxisResult {
anchor: Anchor::Start(*start),
size,
size: computed_size,
margin_start: computed_margin_start.auto_is(Length::zero),
margin_end: computed_margin_end.auto_is(Length::zero),
},
AbsoluteBoxOffsets::Start { start } => AxisResult {
anchor: Anchor::Start(start.percentage_relative_to(containing_size)),
size,
size: computed_size,
margin_start: computed_margin_start.auto_is(Length::zero),
margin_end: computed_margin_end.auto_is(Length::zero),
},
AbsoluteBoxOffsets::End { end } => AxisResult {
anchor: Anchor::End(end.percentage_relative_to(containing_size)),
size,
size: computed_size,
margin_start: computed_margin_start.auto_is(Length::zero),
margin_end: computed_margin_end.auto_is(Length::zero),
},
@ -629,7 +708,7 @@ fn solve_axis(
let margin_start;
let margin_end;
let used_size;
if let LengthOrAuto::LengthPercentage(s) = size {
if let LengthOrAuto::LengthPercentage(s) = computed_size {
used_size = s;
let margins = containing_size - start - end - padding_border_sum - s;
match (computed_margin_start, computed_margin_end) {
@ -661,7 +740,9 @@ fn solve_axis(
} else {
margin_start = computed_margin_start.auto_is(Length::zero);
margin_end = computed_margin_end.auto_is(Length::zero);
// FIXME(nox): What happens if that is negative?
// This may be negative, but the caller will later effectively
// clamp it to min-inline-size or min-block-size.
used_size =
containing_size - start - end - padding_border_sum - margin_start - margin_end
};

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-height-003.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-height-004.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-height-005.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-height-010.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-height-011.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-height-012.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-max-height-002.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-max-height-003.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-max-height-004.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-max-height-005.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-max-height-006.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-max-height-007.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-max-height-008.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-max-height-009.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-max-height-010.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-max-height-011.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-max-height-012.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-width-025.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[absolute-non-replaced-width-026.xht]
expected: FAIL

View file

@ -1,4 +0,0 @@
[abspos-change-in-inline-block.html]
[No crash]
expected: FAIL

View file

@ -1,2 +0,0 @@
[abspos-width-change-inline-container-001.html]
expected: FAIL

View file

@ -1,4 +0,0 @@
[detach-abspos-before-layout.html]
[No crash or DCHECK failure]
expected: FAIL

View file

@ -1,2 +0,0 @@
[relpos-percentage-left-in-scrollable-2.html]
expected: ERROR

View file

@ -1,2 +0,0 @@
[relpos-percentage-left-in-scrollable.html]
expected: ERROR

View file

@ -1,2 +1,3 @@
[relpos-percentage-top-in-scrollable.html]
expected: ERROR
[Top percentage resolved correctly for overflow contribution]
expected: FAIL

View file

@ -91426,6 +91426,19 @@
{}
]
],
"absolute-non-replaced-max-001.html": [
"4b208c5831e1b36a3c503129562175457ef41c0b",
[
null,
[
[
"/css/CSS2/positioning/absolute-non-replaced-max-001-ref.html",
"=="
]
],
{}
]
],
"absolute-non-replaced-max-height-002.xht": [
"7637068a9c4a2cc584acfa329d5aabb09e7101ab",
[
@ -91569,6 +91582,32 @@
{}
]
],
"absolute-non-replaced-min-001.html": [
"0e6d17661e39beb872f886eba06acf47be9a931c",
[
null,
[
[
"/css/CSS2/positioning/absolute-non-replaced-min-max-001-ref.html",
"=="
]
],
{}
]
],
"absolute-non-replaced-min-max-001.html": [
"2e240b7509adc3bd8cb9cae67165e0dc42296e07",
[
null,
[
[
"/css/CSS2/positioning/absolute-non-replaced-min-max-001-ref.html",
"=="
]
],
{}
]
],
"absolute-non-replaced-width-001.xht": [
"6d2b8a33a0db793d9e52e94b33aa1c95884a2dbe",
[
@ -359766,6 +359805,10 @@
"51ec44e9cf55daa92b0822590933275a6706b9aa",
[]
],
"absolute-non-replaced-max-001-ref.html": [
"742b3063abd693a543ca4ce88bda161ae04d8668",
[]
],
"absolute-non-replaced-max-height-002-ref.xht": [
"75b0e54b442a868bfd5b86c671cbb8b67211c800",
[]
@ -359786,6 +359829,10 @@
"aea00aaa43cbb10e905796731278e68f1033e428",
[]
],
"absolute-non-replaced-min-max-001-ref.html": [
"3bbba2be908c099806ffdaaca440cf4513e1e92b",
[]
],
"absolute-non-replaced-width-002-ref.xht": [
"e88ac8098391299e2e106a831fb02f22e7eff9c8",
[]

View file

@ -0,0 +1,2 @@
[absolute-non-replaced-max-001.html]
expected: FAIL

View file

@ -0,0 +1,2 @@
[absolute-non-replaced-min-001.html]
expected: FAIL

View file

@ -0,0 +1,7 @@
<!doctype html>
<meta charset="utf-8">
<link rel="author" name="Delan Azabani" href="mailto:dazabani@igalia.com">
<p>Test passes if there is a green square containing a smaller black square below.
<div style="width: 2em; height: 2em; background: green;">
<div style="width: 1em; height: 1em; background: black;"></div>
</div>

View file

@ -0,0 +1,12 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS Test: max-width and max-height affect percentage sizes in descendants</title>
<link rel="author" name="Delan Azabani" href="mailto:dazabani@igalia.com">
<link rel="help" href="https://drafts.csswg.org/css2/#min-max-widths">
<link rel="help" href="https://drafts.csswg.org/css2/#min-max-heights">
<link rel="match" href="absolute-non-replaced-max-001-ref.html">
<meta name="assert" value="This test verifies that when the used width and height are affected by max-width and max-height, the descendants are laid out with a containing block of the new size, so any percentage sizes are resolved against that new size.">
<p>Test passes if there is a green square containing a smaller black square below.
<div style="position: absolute; width: 4em; height: 4em; max-width: 2em; max-height: 2em; background: green;">
<div style="width: 50%; height: 50%; background: black;"></div>
</div>

View file

@ -0,0 +1,12 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS Test: min-width and min-height affect percentage sizes in descendants</title>
<link rel="author" name="Delan Azabani" href="mailto:dazabani@igalia.com">
<link rel="help" href="https://drafts.csswg.org/css2/#min-max-widths">
<link rel="help" href="https://drafts.csswg.org/css2/#min-max-heights">
<link rel="match" href="absolute-non-replaced-min-max-001-ref.html">
<meta name="assert" value="This test verifies that when the used width and height are affected by min-width and min-height, the descendants are laid out with a containing block of the new size, so any percentage sizes are resolved against that new size.">
<p>Test passes if there is a green square below.
<div style="position: absolute; width: 0; height: 0; min-width: 1em; min-height: 1em; background: black;">
<div style="width: 100%; height: 100%; background: green;"></div>
</div>

View file

@ -0,0 +1,5 @@
<!doctype html>
<meta charset="utf-8">
<link rel="author" name="Delan Azabani" href="mailto:dazabani@igalia.com">
<p>Test passes if there is a green square below.
<div style="width: 1em; height: 1em; background: green;"></div>

View file

@ -0,0 +1,10 @@
<!doctype html>
<meta charset="utf-8">
<title>CSS Test: min-width and min-height take precedence over max-width and max-height when contradictory</title>
<link rel="author" name="Delan Azabani" href="mailto:dazabani@igalia.com">
<link rel="help" href="https://drafts.csswg.org/css2/#min-max-widths">
<link rel="help" href="https://drafts.csswg.org/css2/#min-max-heights">
<link rel="match" href="absolute-non-replaced-min-max-001-ref.html">
<meta name="assert" value="This test verifies that when min-width is greater than max-width, or min-height is greater than max-height, the minimums take precedence, because the steps that recalculate sizes and margins for the minimums (step 3) come after the steps for the maximums (step 2).">
<p>Test passes if there is a green square below.
<div style="position: absolute; width: 0; height: 0; min-width: 1em; min-height: 1em; max-width: 0; max-height: 0; background: green;"></div>