mirror of
https://github.com/servo/servo.git
synced 2025-06-09 00:53:26 +00:00
layout: Implement keyword sizes for block layout heuristics (#34695)
Block layout uses some heuristics to guess whether margins are separated by clearance and then don't collapse. These heuristics now take the min-content, max-content, fit-content and stretch sizing keywords into account. Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
parent
2ab66ce678
commit
6be0a64939
9 changed files with 212 additions and 63 deletions
|
@ -39,7 +39,7 @@ use crate::layout_box_base::LayoutBoxBase;
|
||||||
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength};
|
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength};
|
||||||
use crate::replaced::ReplacedContents;
|
use crate::replaced::ReplacedContents;
|
||||||
use crate::sizing::{self, ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult};
|
use crate::sizing::{self, ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult};
|
||||||
use crate::style_ext::{Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, PaddingBorderMargin};
|
use crate::style_ext::{ComputedValuesExt, ContentBoxSizesAndPBM, PaddingBorderMargin};
|
||||||
use crate::{
|
use crate::{
|
||||||
ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock,
|
ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock,
|
||||||
SizeConstraint,
|
SizeConstraint,
|
||||||
|
@ -102,6 +102,7 @@ impl BlockLevelBox {
|
||||||
|
|
||||||
fn find_block_margin_collapsing_with_parent(
|
fn find_block_margin_collapsing_with_parent(
|
||||||
&self,
|
&self,
|
||||||
|
layout_context: &LayoutContext,
|
||||||
collected_margin: &mut CollapsedMargin,
|
collected_margin: &mut CollapsedMargin,
|
||||||
containing_block: &ContainingBlock,
|
containing_block: &ContainingBlock,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
@ -122,9 +123,13 @@ impl BlockLevelBox {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pbm = style.padding_border_margin(containing_block);
|
let ContentBoxSizesAndPBM {
|
||||||
let start_margin = pbm.margin.block_start.auto_is(Au::zero);
|
content_box_sizes,
|
||||||
collected_margin.adjoin_assign(&CollapsedMargin::new(start_margin));
|
pbm,
|
||||||
|
..
|
||||||
|
} = style.content_box_sizes_and_padding_border_margin(&containing_block.into());
|
||||||
|
let margin = pbm.margin.auto_is(Au::zero);
|
||||||
|
collected_margin.adjoin_assign(&CollapsedMargin::new(margin.block_start));
|
||||||
|
|
||||||
let child_boxes = match self {
|
let child_boxes = match self {
|
||||||
BlockLevelBox::SameFormattingContextBlock { ref contents, .. } => match contents {
|
BlockLevelBox::SameFormattingContextBlock { ref contents, .. } => match contents {
|
||||||
|
@ -138,35 +143,45 @@ impl BlockLevelBox {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let min_size = style
|
// FIXME: For BlockLevelBox::Independent, this should take floats into account.
|
||||||
.content_min_box_size_deprecated(containing_block, &pbm)
|
let available_inline_size =
|
||||||
.auto_is(Au::zero);
|
containing_block.size.inline - pbm.padding_border_sums.inline - margin.inline_sum();
|
||||||
let max_size = style.content_max_box_size_deprecated(containing_block, &pbm);
|
let available_block_size = containing_block.size.block.non_auto().map(|block_size| {
|
||||||
let prefered_size = style.content_box_size_deprecated(containing_block, &pbm);
|
Au::zero().max(block_size - pbm.padding_border_sums.block - margin.block_sum())
|
||||||
let inline_size = prefered_size
|
});
|
||||||
.inline
|
|
||||||
.auto_is(|| {
|
let tentative_block_size = content_box_sizes.block.resolve_extrinsic(
|
||||||
let margin_inline_start = pbm.margin.inline_start.auto_is(Au::zero);
|
Size::FitContent,
|
||||||
let margin_inline_end = pbm.margin.inline_end.auto_is(Au::zero);
|
Au::zero(),
|
||||||
containing_block.size.inline -
|
available_block_size,
|
||||||
pbm.padding_border_sums.inline -
|
);
|
||||||
margin_inline_start -
|
|
||||||
margin_inline_end
|
let get_inline_content_sizes = || {
|
||||||
})
|
let constraint_space = ConstraintSpace::new(
|
||||||
.clamp_between_extremums(min_size.inline, max_size.inline);
|
tentative_block_size,
|
||||||
let block_size = prefered_size
|
style.writing_mode,
|
||||||
.block
|
None, /* TODO: support preferred aspect ratios on non-replaced boxes */
|
||||||
.map(|size| size.clamp_between_extremums(min_size.block, max_size.block));
|
);
|
||||||
|
self.inline_content_sizes(layout_context, &constraint_space)
|
||||||
|
.sizes
|
||||||
|
};
|
||||||
|
let inline_size = content_box_sizes.inline.resolve(
|
||||||
|
Size::Stretch,
|
||||||
|
Au::zero(),
|
||||||
|
available_inline_size,
|
||||||
|
get_inline_content_sizes,
|
||||||
|
);
|
||||||
|
|
||||||
let containing_block_for_children = ContainingBlock {
|
let containing_block_for_children = ContainingBlock {
|
||||||
size: ContainingBlockSize {
|
size: ContainingBlockSize {
|
||||||
inline: inline_size,
|
inline: inline_size,
|
||||||
block: block_size,
|
block: tentative_block_size.to_auto_or(),
|
||||||
},
|
},
|
||||||
style,
|
style,
|
||||||
};
|
};
|
||||||
|
|
||||||
if !Self::find_block_margin_collapsing_with_parent_from_slice(
|
if !Self::find_block_margin_collapsing_with_parent_from_slice(
|
||||||
|
layout_context,
|
||||||
child_boxes,
|
child_boxes,
|
||||||
collected_margin,
|
collected_margin,
|
||||||
&containing_block_for_children,
|
&containing_block_for_children,
|
||||||
|
@ -181,13 +196,13 @@ impl BlockLevelBox {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let end_margin = pbm.margin.block_end.auto_is(Au::zero);
|
collected_margin.adjoin_assign(&CollapsedMargin::new(margin.block_end));
|
||||||
collected_margin.adjoin_assign(&CollapsedMargin::new(end_margin));
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_block_margin_collapsing_with_parent_from_slice(
|
fn find_block_margin_collapsing_with_parent_from_slice(
|
||||||
|
layout_context: &LayoutContext,
|
||||||
boxes: &[ArcRefCell<BlockLevelBox>],
|
boxes: &[ArcRefCell<BlockLevelBox>],
|
||||||
margin: &mut CollapsedMargin,
|
margin: &mut CollapsedMargin,
|
||||||
containing_block: &ContainingBlock,
|
containing_block: &ContainingBlock,
|
||||||
|
@ -195,7 +210,7 @@ impl BlockLevelBox {
|
||||||
boxes.iter().all(|block_level_box| {
|
boxes.iter().all(|block_level_box| {
|
||||||
block_level_box
|
block_level_box
|
||||||
.borrow()
|
.borrow()
|
||||||
.find_block_margin_collapsing_with_parent(margin, containing_block)
|
.find_block_margin_collapsing_with_parent(layout_context, margin, containing_block)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,6 +245,15 @@ impl OutsideMarker {
|
||||||
&self.base.style
|
&self.base.style
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn inline_content_sizes(
|
||||||
|
&self,
|
||||||
|
layout_context: &LayoutContext,
|
||||||
|
constraint_space: &ConstraintSpace,
|
||||||
|
) -> InlineContentSizesResult {
|
||||||
|
self.base
|
||||||
|
.inline_content_sizes(layout_context, constraint_space, &self.block_container)
|
||||||
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
layout_context: &LayoutContext<'_>,
|
layout_context: &LayoutContext<'_>,
|
||||||
|
@ -242,11 +266,7 @@ impl OutsideMarker {
|
||||||
&self.marker_style,
|
&self.marker_style,
|
||||||
None, /* TODO: support preferred aspect ratios on non-replaced boxes */
|
None, /* TODO: support preferred aspect ratios on non-replaced boxes */
|
||||||
);
|
);
|
||||||
let content_sizes = self.base.inline_content_sizes(
|
let content_sizes = self.inline_content_sizes(layout_context, &constraint_space);
|
||||||
layout_context,
|
|
||||||
&constraint_space,
|
|
||||||
&self.block_container,
|
|
||||||
);
|
|
||||||
let containing_block_for_children = ContainingBlock {
|
let containing_block_for_children = ContainingBlock {
|
||||||
size: ContainingBlockSize {
|
size: ContainingBlockSize {
|
||||||
inline: content_sizes.sizes.max_content,
|
inline: content_sizes.sizes.max_content,
|
||||||
|
@ -770,6 +790,25 @@ impl BlockLevelBox {
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn inline_content_sizes(
|
||||||
|
&self,
|
||||||
|
layout_context: &LayoutContext,
|
||||||
|
constraint_space: &ConstraintSpace,
|
||||||
|
) -> InlineContentSizesResult {
|
||||||
|
let independent_formatting_context = match self {
|
||||||
|
BlockLevelBox::Independent(independent) => independent,
|
||||||
|
BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(box_) => &box_.borrow().context,
|
||||||
|
BlockLevelBox::OutOfFlowFloatBox(float_box) => &float_box.contents,
|
||||||
|
BlockLevelBox::OutsideMarker(outside_marker) => {
|
||||||
|
return outside_marker.inline_content_sizes(layout_context, constraint_space)
|
||||||
|
},
|
||||||
|
BlockLevelBox::SameFormattingContextBlock { base, contents, .. } => {
|
||||||
|
return base.inline_content_sizes(layout_context, constraint_space, contents)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
independent_formatting_context.inline_content_sizes(layout_context, constraint_space)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lay out a normal flow non-replaced block that does not establish a new formatting
|
/// Lay out a normal flow non-replaced block that does not establish a new formatting
|
||||||
|
@ -844,6 +883,7 @@ fn layout_in_flow_non_replaced_block_level_same_formatting_context(
|
||||||
if !collapsible_with_parent_start_margin && start_margin_can_collapse_with_children {
|
if !collapsible_with_parent_start_margin && start_margin_can_collapse_with_children {
|
||||||
if let BlockContainer::BlockLevelBoxes(child_boxes) = contents {
|
if let BlockContainer::BlockLevelBoxes(child_boxes) = contents {
|
||||||
BlockLevelBox::find_block_margin_collapsing_with_parent_from_slice(
|
BlockLevelBox::find_block_margin_collapsing_with_parent_from_slice(
|
||||||
|
layout_context,
|
||||||
child_boxes,
|
child_boxes,
|
||||||
&mut block_start_margin,
|
&mut block_start_margin,
|
||||||
containing_block,
|
containing_block,
|
||||||
|
|
|
@ -261,16 +261,6 @@ pub(crate) trait ComputedValuesExt {
|
||||||
box_size: LogicalVec2<Size<Au>>,
|
box_size: LogicalVec2<Size<Au>>,
|
||||||
pbm: &PaddingBorderMargin,
|
pbm: &PaddingBorderMargin,
|
||||||
) -> LogicalVec2<Size<Au>>;
|
) -> LogicalVec2<Size<Au>>;
|
||||||
fn content_max_box_size(
|
|
||||||
&self,
|
|
||||||
containing_block: &ContainingBlock,
|
|
||||||
pbm: &PaddingBorderMargin,
|
|
||||||
) -> LogicalVec2<Size<Au>>;
|
|
||||||
fn content_max_box_size_deprecated(
|
|
||||||
&self,
|
|
||||||
containing_block: &ContainingBlock,
|
|
||||||
pbm: &PaddingBorderMargin,
|
|
||||||
) -> LogicalVec2<Option<Au>>;
|
|
||||||
fn content_max_box_size_for_max_size(
|
fn content_max_box_size_for_max_size(
|
||||||
&self,
|
&self,
|
||||||
box_size: LogicalVec2<Size<Au>>,
|
box_size: LogicalVec2<Size<Au>>,
|
||||||
|
@ -470,27 +460,6 @@ impl ComputedValuesExt for ComputedValues {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn content_max_box_size(
|
|
||||||
&self,
|
|
||||||
containing_block: &ContainingBlock,
|
|
||||||
pbm: &PaddingBorderMargin,
|
|
||||||
) -> LogicalVec2<Size<Au>> {
|
|
||||||
let max_box_size = self
|
|
||||||
.max_box_size(containing_block.style.writing_mode)
|
|
||||||
.percentages_relative_to(containing_block);
|
|
||||||
|
|
||||||
self.content_max_box_size_for_max_size(max_box_size, pbm)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn content_max_box_size_deprecated(
|
|
||||||
&self,
|
|
||||||
containing_block: &ContainingBlock,
|
|
||||||
pbm: &PaddingBorderMargin,
|
|
||||||
) -> LogicalVec2<Option<Au>> {
|
|
||||||
self.content_max_box_size(containing_block, pbm)
|
|
||||||
.map(Size::to_numeric)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn content_max_box_size_for_max_size(
|
fn content_max_box_size_for_max_size(
|
||||||
&self,
|
&self,
|
||||||
max_box_size: LogicalVec2<Size<Au>>,
|
max_box_size: LogicalVec2<Size<Au>>,
|
||||||
|
|
52
tests/wpt/meta/MANIFEST.json
vendored
52
tests/wpt/meta/MANIFEST.json
vendored
|
@ -243091,6 +243091,58 @@
|
||||||
{}
|
{}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
"float-clearance-with-margin-collapse-and-fit-content-and-padding-percentage-001.html": [
|
||||||
|
"d5e293f85e9cde73c1efa60d66ccdea4618512db",
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"/css/reference/ref-filled-green-100px-square.xht",
|
||||||
|
"=="
|
||||||
|
]
|
||||||
|
],
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"float-clearance-with-margin-collapse-and-fit-content-and-padding-percentage-002.html": [
|
||||||
|
"a9e0e95522403b396a86aac3a5400ef8693c56d4",
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"/css/reference/ref-filled-green-100px-square.xht",
|
||||||
|
"=="
|
||||||
|
]
|
||||||
|
],
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"float-clearance-with-margin-collapse-and-fit-content-and-padding-percentage-003.html": [
|
||||||
|
"0e27b9ef7d16c19c76fab7314e089a122b8b9123",
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"/css/reference/ref-filled-green-100px-square.xht",
|
||||||
|
"=="
|
||||||
|
]
|
||||||
|
],
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"float-clearance-with-margin-collapse-and-fit-content-and-padding-percentage-004.html": [
|
||||||
|
"6d08bd81a73703fd068849117c0e12210097adee",
|
||||||
|
[
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"/css/reference/ref-filled-green-100px-square.xht",
|
||||||
|
"=="
|
||||||
|
]
|
||||||
|
],
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
],
|
||||||
"grid-item-image-percentage-min-height-computes-as-0.html": [
|
"grid-item-image-percentage-min-height-computes-as-0.html": [
|
||||||
"ef3da03100229a846ca77156bdf34e5e37c92508",
|
"ef3da03100229a846ca77156bdf34e5e37c92508",
|
||||||
[
|
[
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
[float-clearance-with-margin-collapse-and-fit-content-and-padding-percentage-003.html]
|
||||||
|
expected: FAIL
|
|
@ -0,0 +1,2 @@
|
||||||
|
[float-clearance-with-margin-collapse-and-fit-content-and-padding-percentage-004.html]
|
||||||
|
expected: FAIL
|
|
@ -0,0 +1,22 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<link rel="author" title="Oriol Brufau" href="obrufau@igalia.com">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#fit-content-size">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css2/#floats">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css2/#clearance">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css2/#collapsing-margins">
|
||||||
|
<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
|
||||||
|
<meta name="assert" content="
|
||||||
|
The element with `width: fit-content` becomes 0px wide.
|
||||||
|
Therefore, the padding percentage of its child resolves to 0px.
|
||||||
|
Therefore, the 100px of margin collapse with the top margin of the element with `clear: both`.
|
||||||
|
Therefore, the element with `clear: both` is already past the float and receives no clearance.
|
||||||
|
">
|
||||||
|
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
|
||||||
|
<div style="display: inline-block; background: red">
|
||||||
|
<div style="width: 100px; height: 100px; float: left; background: green"></div>
|
||||||
|
<div style="clear: both">
|
||||||
|
<div style="width: fit-content">
|
||||||
|
<div style="padding-top: 100%; margin-bottom: 100px;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,22 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<link rel="author" title="Oriol Brufau" href="obrufau@igalia.com">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#fit-content-size">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css2/#floats">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css2/#clearance">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css2/#collapsing-margins">
|
||||||
|
<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
|
||||||
|
<meta name="assert" content="
|
||||||
|
The element with `width: fit-content` becomes 0px wide.
|
||||||
|
Therefore, the padding percentage of its child resolves to 0px.
|
||||||
|
Therefore, the 75px of margin collapse with the top margin of the element with `clear: both`.
|
||||||
|
Therefore, the element with `clear: both` receives 25px of clearance.
|
||||||
|
">
|
||||||
|
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
|
||||||
|
<div style="display: inline-block; background: red">
|
||||||
|
<div style="width: 100px; height: 100px; float: left; background: green"></div>
|
||||||
|
<div style="clear: both">
|
||||||
|
<div style="width: fit-content">
|
||||||
|
<div style="padding-top: 100%; margin-bottom: 75px;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<link rel="author" title="Oriol Brufau" href="obrufau@igalia.com">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#fit-content-size">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css2/#floats">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css2/#clearance">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css2/#collapsing-margins">
|
||||||
|
<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
|
||||||
|
<meta name="assert" content="
|
||||||
|
The element with `width: fit-content` becomes 0px wide.
|
||||||
|
Therefore, the padding percentage of its child resolves to 0px.
|
||||||
|
Therefore, the 100px of margin collapse with the top margin of the element with `clear: both`.
|
||||||
|
Therefore, the element with `clear: both` is already past the float and receives no clearance.
|
||||||
|
">
|
||||||
|
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
|
||||||
|
<div style="display: inline-block; background: red">
|
||||||
|
<div style="width: 100px; height: 100px; float: left; background: green"></div>
|
||||||
|
<div style="clear: both; width: fit-content">
|
||||||
|
<div style="padding-top: 100%; margin-bottom: 100px;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<link rel="author" title="Oriol Brufau" href="obrufau@igalia.com">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#fit-content-size">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css2/#floats">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css2/#clearance">
|
||||||
|
<link rel="help" href="https://drafts.csswg.org/css2/#collapsing-margins">
|
||||||
|
<link rel="match" href="../reference/ref-filled-green-100px-square.xht">
|
||||||
|
<meta name="assert" content="
|
||||||
|
The element with `width: fit-content` becomes 0px wide.
|
||||||
|
Therefore, the padding percentage of its child resolves to 0px.
|
||||||
|
Therefore, the 75px of margin collapse with the top margin of the element with `clear: both`.
|
||||||
|
Therefore, the element with `clear: both` receives 25px of clearance.
|
||||||
|
">
|
||||||
|
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
|
||||||
|
<div style="display: inline-block; background: red">
|
||||||
|
<div style="width: 100px; height: 100px; float: left; background: green"></div>
|
||||||
|
<div style="clear: both; width: fit-content">
|
||||||
|
<div style="padding-top: 100%; margin-bottom: 75px;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
Loading…
Add table
Add a link
Reference in a new issue