mirror of
https://github.com/servo/servo.git
synced 2025-07-23 15:23:42 +01:00
Refactor positioned layout (#33922)
This unifies the size resolution into AbsoluteAxisSolver, since it needs to know the size in order to resolve auto margins correctly anyways. This will allow adding support for sizing keywords in a follow-up patch. Also, this avoids doing multiple layouts due to min and max constraints, improving performance. Additionally, tables may end up having a custom size, different than what we would expect by just looking at the sizing properties. This patch ensures that we resolve margins correctly with the final size, resulting in 2 tests now passing. Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
parent
fee927475b
commit
2319764a1e
4 changed files with 195 additions and 256 deletions
|
@ -30,7 +30,6 @@ pub use flow::BoxTree;
|
||||||
pub use fragment_tree::FragmentTree;
|
pub use fragment_tree::FragmentTree;
|
||||||
use geom::AuOrAuto;
|
use geom::AuOrAuto;
|
||||||
use style::properties::ComputedValues;
|
use style::properties::ComputedValues;
|
||||||
use style_ext::{Clamp, ComputedValuesExt};
|
|
||||||
|
|
||||||
use crate::geom::LogicalVec2;
|
use crate::geom::LogicalVec2;
|
||||||
|
|
||||||
|
@ -59,22 +58,6 @@ impl<'a> IndefiniteContainingBlock<'a> {
|
||||||
style,
|
style,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_for_intrinsic_inline_size_of_child(
|
|
||||||
&self,
|
|
||||||
style: &'a ComputedValues,
|
|
||||||
auto_minimum: &LogicalVec2<Au>,
|
|
||||||
) -> Self {
|
|
||||||
let (content_box_size, content_min_size, content_max_size, _, _) =
|
|
||||||
style.content_box_sizes_and_padding_border_margin_deprecated(self);
|
|
||||||
let block_size = content_box_size.block.map(|v| {
|
|
||||||
v.clamp_between_extremums(
|
|
||||||
content_min_size.block.auto_is(|| auto_minimum.block),
|
|
||||||
content_max_size.block,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
IndefiniteContainingBlock::new_for_style_and_block_size(style, block_size)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'_ ContainingBlock<'a>> for IndefiniteContainingBlock<'a> {
|
impl<'a> From<&'_ ContainingBlock<'a>> for IndefiniteContainingBlock<'a> {
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
use app_units::Au;
|
use app_units::Au;
|
||||||
use rayon::iter::IntoParallelRefMutIterator;
|
use rayon::iter::IntoParallelRefMutIterator;
|
||||||
use rayon::prelude::{IndexedParallelIterator, ParallelIterator};
|
use rayon::prelude::{IndexedParallelIterator, ParallelIterator};
|
||||||
|
@ -25,7 +27,8 @@ use crate::geom::{
|
||||||
AuOrAuto, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalVec2, PhysicalPoint,
|
AuOrAuto, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalVec2, PhysicalPoint,
|
||||||
PhysicalRect, PhysicalVec, ToLogical, ToLogicalWithContainingBlock,
|
PhysicalRect, PhysicalVec, ToLogical, ToLogicalWithContainingBlock,
|
||||||
};
|
};
|
||||||
use crate::style_ext::{ComputedValuesExt, DisplayInside};
|
use crate::sizing::ContentSizes;
|
||||||
|
use crate::style_ext::{Clamp, ComputedValuesExt, DisplayInside};
|
||||||
use crate::{ContainingBlock, DefiniteContainingBlock, IndefiniteContainingBlock};
|
use crate::{ContainingBlock, DefiniteContainingBlock, IndefiniteContainingBlock};
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
|
@ -240,13 +243,11 @@ impl PositioningContext {
|
||||||
style: &new_fragment.style,
|
style: &new_fragment.style,
|
||||||
};
|
};
|
||||||
|
|
||||||
let take_hoisted_boxes_pending_layout = |context: &mut Self| match context
|
let take_hoisted_boxes_pending_layout =
|
||||||
.for_nearest_positioned_ancestor
|
|context: &mut Self| match context.for_nearest_positioned_ancestor.as_mut() {
|
||||||
.as_mut()
|
Some(fragments) => mem::take(fragments),
|
||||||
{
|
None => mem::take(&mut context.for_nearest_containing_block_for_all_descendants),
|
||||||
Some(fragments) => std::mem::take(fragments),
|
};
|
||||||
None => std::mem::take(&mut context.for_nearest_containing_block_for_all_descendants),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Loop because it’s possible that we discover (the static position of)
|
// Loop because it’s possible that we discover (the static position of)
|
||||||
// more absolutely-positioned boxes while doing layout for others.
|
// more absolutely-positioned boxes while doing layout for others.
|
||||||
|
@ -337,7 +338,7 @@ impl PositioningContext {
|
||||||
{
|
{
|
||||||
HoistedAbsolutelyPositionedBox::layout_many(
|
HoistedAbsolutelyPositionedBox::layout_many(
|
||||||
layout_context,
|
layout_context,
|
||||||
&mut std::mem::take(&mut self.for_nearest_containing_block_for_all_descendants),
|
&mut mem::take(&mut self.for_nearest_containing_block_for_all_descendants),
|
||||||
fragments,
|
fragments,
|
||||||
&mut self.for_nearest_containing_block_for_all_descendants,
|
&mut self.for_nearest_containing_block_for_all_descendants,
|
||||||
initial_containing_block,
|
initial_containing_block,
|
||||||
|
@ -345,14 +346,6 @@ impl PositioningContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn clear(&mut self) {
|
|
||||||
self.for_nearest_containing_block_for_all_descendants
|
|
||||||
.clear();
|
|
||||||
if let Some(v) = self.for_nearest_positioned_ancestor.as_mut() {
|
|
||||||
v.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the length of this [PositioningContext].
|
/// Get the length of this [PositioningContext].
|
||||||
pub(crate) fn len(&self) -> PositioningContextLength {
|
pub(crate) fn len(&self) -> PositioningContextLength {
|
||||||
PositioningContextLength {
|
PositioningContextLength {
|
||||||
|
@ -454,37 +447,40 @@ impl HoistedAbsolutelyPositionedBox {
|
||||||
) -> BoxFragment {
|
) -> BoxFragment {
|
||||||
let cbis = containing_block.size.inline;
|
let cbis = containing_block.size.inline;
|
||||||
let cbbs = containing_block.size.block;
|
let cbbs = containing_block.size.block;
|
||||||
let mut absolutely_positioned_box = self.absolutely_positioned_box.borrow_mut();
|
|
||||||
let containing_block_writing_mode = containing_block.style.writing_mode;
|
let containing_block_writing_mode = containing_block.style.writing_mode;
|
||||||
let style = absolutely_positioned_box.context.style().clone();
|
let mut absolutely_positioned_box = self.absolutely_positioned_box.borrow_mut();
|
||||||
let pbm = style.padding_border_margin(&containing_block.into());
|
let context = &mut absolutely_positioned_box.context;
|
||||||
let indefinite_containing_block = containing_block.into();
|
let style = context.style().clone();
|
||||||
|
let containing_block = &containing_block.into();
|
||||||
|
let pbm = style.padding_border_margin(containing_block);
|
||||||
|
|
||||||
let computed_size = match &absolutely_positioned_box.context {
|
let (computed_size, computed_min_size, computed_max_size) = match context {
|
||||||
IndependentFormattingContext::Replaced(replaced) => {
|
IndependentFormattingContext::Replaced(replaced) => {
|
||||||
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
|
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
|
||||||
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
|
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
|
||||||
let used_size = replaced.contents.used_size_as_if_inline_element(
|
let used_size = replaced
|
||||||
&containing_block.into(),
|
.contents
|
||||||
&replaced.style,
|
.used_size_as_if_inline_element(containing_block, &style, &pbm)
|
||||||
&pbm,
|
.map(|size| AuOrAuto::LengthPercentage(*size));
|
||||||
);
|
(used_size, Default::default(), Default::default())
|
||||||
LogicalVec2 {
|
|
||||||
inline: AuOrAuto::LengthPercentage(used_size.inline),
|
|
||||||
block: AuOrAuto::LengthPercentage(used_size.block),
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
IndependentFormattingContext::NonReplaced(non_replaced) => non_replaced
|
IndependentFormattingContext::NonReplaced(_) => (
|
||||||
.style
|
style.content_box_size_deprecated(containing_block, &pbm),
|
||||||
.content_box_size_deprecated(&indefinite_containing_block, &pbm),
|
style
|
||||||
|
.content_min_box_size_deprecated(containing_block, &pbm)
|
||||||
|
.map(|value| value.map(Au::from).auto_is(Au::zero)),
|
||||||
|
style
|
||||||
|
.content_max_box_size_deprecated(containing_block, &pbm)
|
||||||
|
.map(|value| value.map(Au::from)),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let shared_fragment = self.fragment.borrow();
|
let shared_fragment = self.fragment.borrow();
|
||||||
let static_position_rect = shared_fragment
|
let static_position_rect = shared_fragment
|
||||||
.static_position_rect
|
.static_position_rect
|
||||||
.to_logical(&indefinite_containing_block);
|
.to_logical(containing_block);
|
||||||
|
|
||||||
let box_offset = style.box_offsets(&indefinite_containing_block);
|
let box_offset = style.box_offsets(containing_block);
|
||||||
|
|
||||||
// When the "static-position rect" doesn't come into play, we do not do any alignment
|
// When the "static-position rect" doesn't come into play, we do not do any alignment
|
||||||
// in the inline axis.
|
// in the inline axis.
|
||||||
|
@ -497,18 +493,21 @@ impl HoistedAbsolutelyPositionedBox {
|
||||||
false => shared_fragment.resolved_alignment.inline,
|
false => shared_fragment.resolved_alignment.inline,
|
||||||
};
|
};
|
||||||
|
|
||||||
let inline_axis_solver = AbsoluteAxisSolver {
|
let mut inline_axis_solver = AbsoluteAxisSolver {
|
||||||
axis: AxisDirection::Inline,
|
axis: AxisDirection::Inline,
|
||||||
containing_size: cbis,
|
containing_size: cbis,
|
||||||
padding_border_sum: pbm.padding_border_sums.inline,
|
padding_border_sum: pbm.padding_border_sums.inline,
|
||||||
computed_margin_start: pbm.margin.inline_start,
|
computed_margin_start: pbm.margin.inline_start,
|
||||||
computed_margin_end: pbm.margin.inline_end,
|
computed_margin_end: pbm.margin.inline_end,
|
||||||
|
computed_size: computed_size.inline,
|
||||||
|
computed_min_size: computed_min_size.inline,
|
||||||
|
computed_max_size: computed_max_size.inline,
|
||||||
avoid_negative_margin_start: true,
|
avoid_negative_margin_start: true,
|
||||||
box_offsets: inline_box_offsets,
|
box_offsets: inline_box_offsets,
|
||||||
static_position_rect_axis: static_position_rect.get_axis(AxisDirection::Inline),
|
static_position_rect_axis: static_position_rect.get_axis(AxisDirection::Inline),
|
||||||
alignment: inline_alignment,
|
alignment: inline_alignment,
|
||||||
flip_anchor: shared_fragment.original_parent_writing_mode.is_bidi_ltr() !=
|
flip_anchor: shared_fragment.original_parent_writing_mode.is_bidi_ltr() !=
|
||||||
indefinite_containing_block.style.writing_mode.is_bidi_ltr(),
|
containing_block_writing_mode.is_bidi_ltr(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// When the "static-position rect" doesn't come into play, we re-resolve "align-self"
|
// When the "static-position rect" doesn't come into play, we re-resolve "align-self"
|
||||||
|
@ -521,12 +520,15 @@ impl HoistedAbsolutelyPositionedBox {
|
||||||
true => style.clone_align_self().0 .0,
|
true => style.clone_align_self().0 .0,
|
||||||
false => shared_fragment.resolved_alignment.block,
|
false => shared_fragment.resolved_alignment.block,
|
||||||
};
|
};
|
||||||
let block_axis_solver = AbsoluteAxisSolver {
|
let mut block_axis_solver = AbsoluteAxisSolver {
|
||||||
axis: AxisDirection::Block,
|
axis: AxisDirection::Block,
|
||||||
containing_size: cbbs,
|
containing_size: cbbs,
|
||||||
padding_border_sum: pbm.padding_border_sums.block,
|
padding_border_sum: pbm.padding_border_sums.block,
|
||||||
computed_margin_start: pbm.margin.block_start,
|
computed_margin_start: pbm.margin.block_start,
|
||||||
computed_margin_end: pbm.margin.block_end,
|
computed_margin_end: pbm.margin.block_end,
|
||||||
|
computed_size: computed_size.block,
|
||||||
|
computed_min_size: computed_min_size.block,
|
||||||
|
computed_max_size: computed_max_size.block,
|
||||||
avoid_negative_margin_start: false,
|
avoid_negative_margin_start: false,
|
||||||
box_offsets: block_box_offsets,
|
box_offsets: block_box_offsets,
|
||||||
static_position_rect_axis: static_position_rect.get_axis(AxisDirection::Block),
|
static_position_rect_axis: static_position_rect.get_axis(AxisDirection::Block),
|
||||||
|
@ -534,168 +536,90 @@ impl HoistedAbsolutelyPositionedBox {
|
||||||
flip_anchor: false,
|
flip_anchor: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut inline_axis = inline_axis_solver.solve_for_size(computed_size.inline);
|
// We can solve the inline axis, but the block one can depend on layout results,
|
||||||
let mut block_axis = block_axis_solver.solve_for_size(computed_size.block);
|
// so we may have to resolve it properly later on.
|
||||||
|
let mut inline_axis = inline_axis_solver.solve(Some(|| {
|
||||||
|
let block_size = computed_size.block.map(|v| {
|
||||||
|
v.clamp_between_extremums(computed_min_size.block, computed_max_size.block)
|
||||||
|
});
|
||||||
|
let containing_block_for_children =
|
||||||
|
IndefiniteContainingBlock::new_for_style_and_block_size(&style, block_size);
|
||||||
|
context
|
||||||
|
.inline_content_sizes(
|
||||||
|
layout_context,
|
||||||
|
&containing_block_for_children,
|
||||||
|
&containing_block.into(),
|
||||||
|
)
|
||||||
|
.sizes
|
||||||
|
}));
|
||||||
|
let mut block_axis = block_axis_solver.solve_tentatively();
|
||||||
|
|
||||||
let mut positioning_context = PositioningContext::new_for_style(&style).unwrap();
|
let mut positioning_context = PositioningContext::new_for_style(&style).unwrap();
|
||||||
let mut new_fragment = {
|
let mut new_fragment = {
|
||||||
let content_size: LogicalVec2<Au>;
|
let content_size: LogicalVec2<Au>;
|
||||||
let fragments;
|
let fragments;
|
||||||
match &mut absolutely_positioned_box.context {
|
match context {
|
||||||
IndependentFormattingContext::Replaced(replaced) => {
|
IndependentFormattingContext::Replaced(replaced) => {
|
||||||
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
|
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
|
||||||
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
|
// https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
|
||||||
content_size = computed_size.auto_is(|| unreachable!());
|
content_size = computed_size.auto_is(|| unreachable!());
|
||||||
fragments = replaced.contents.make_fragments(
|
fragments = replaced.contents.make_fragments(
|
||||||
&style,
|
&style,
|
||||||
&containing_block.into(),
|
containing_block,
|
||||||
content_size.to_physical_size(containing_block_writing_mode),
|
content_size.to_physical_size(containing_block_writing_mode),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
IndependentFormattingContext::NonReplaced(non_replaced) => {
|
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_deprecated(&indefinite_containing_block, &pbm)
|
|
||||||
.map(|t| t.map(Au::from).auto_is(Au::zero));
|
|
||||||
let max_size = style
|
|
||||||
.content_max_box_size_deprecated(&containing_block.into(), &pbm)
|
|
||||||
.map(|t| t.map(Au::from));
|
|
||||||
|
|
||||||
// https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width
|
// https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width
|
||||||
// https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-height
|
// https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-height
|
||||||
let mut inline_size = inline_axis.size.auto_is(|| {
|
let inline_size = inline_axis.size.non_auto().unwrap();
|
||||||
let anchor = match inline_axis.anchor {
|
let containing_block_for_children = ContainingBlock {
|
||||||
Anchor::Start(start) => start,
|
inline_size,
|
||||||
Anchor::End(end) => end,
|
block_size: block_axis.size,
|
||||||
};
|
style: &style,
|
||||||
let margin_sum = inline_axis.margin_start + inline_axis.margin_end;
|
};
|
||||||
let available_size =
|
// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
|
||||||
cbis - anchor - pbm.padding_border_sums.inline - margin_sum;
|
assert_eq!(
|
||||||
|
containing_block_writing_mode.is_horizontal(),
|
||||||
|
style.writing_mode.is_horizontal(),
|
||||||
|
"Mixed horizontal and vertical writing modes are not supported yet"
|
||||||
|
);
|
||||||
|
|
||||||
let style = non_replaced.style.clone();
|
let independent_layout = non_replaced.layout(
|
||||||
let containing_block_for_children =
|
layout_context,
|
||||||
IndefiniteContainingBlock::from(containing_block)
|
&mut positioning_context,
|
||||||
.new_for_intrinsic_inline_size_of_child(
|
&containing_block_for_children,
|
||||||
&style,
|
containing_block,
|
||||||
&LogicalVec2::zero(),
|
);
|
||||||
);
|
|
||||||
non_replaced
|
|
||||||
.inline_content_sizes(layout_context, &containing_block_for_children)
|
|
||||||
.sizes
|
|
||||||
.shrink_to_fit(available_size)
|
|
||||||
});
|
|
||||||
|
|
||||||
// If the tentative used inline size is greater than ‘max-inline-size’,
|
let (block_size, inline_size) = match independent_layout
|
||||||
// recalculate the inline size and margins with ‘max-inline-size’ as the
|
.content_inline_size_for_table
|
||||||
// computed ‘inline-size’. We can assume the new inline size won’t be ‘auto’,
|
{
|
||||||
// because a non-‘auto’ computed ‘inline-size’ always becomes the used value.
|
Some(table_inline_size) => {
|
||||||
// https://drafts.csswg.org/css2/#min-max-widths (step 2)
|
// Tables can override their sizes regardless of the sizing properties,
|
||||||
if let Some(max) = max_size.inline {
|
// so we may need to solve again to update margins.
|
||||||
if inline_size > max {
|
if inline_size != table_inline_size {
|
||||||
inline_axis =
|
inline_axis = inline_axis_solver.solve_with_size(table_inline_size);
|
||||||
inline_axis_solver.solve_for_size(AuOrAuto::LengthPercentage(max));
|
}
|
||||||
inline_size = inline_axis.size.auto_is(|| unreachable!());
|
let table_block_size = independent_layout.content_block_size;
|
||||||
}
|
if block_axis.size != AuOrAuto::LengthPercentage(table_block_size) {
|
||||||
}
|
block_axis = block_axis_solver.solve_with_size(table_block_size);
|
||||||
|
}
|
||||||
// If the tentative used inline size is less than ‘min-inline-size’,
|
(table_block_size, table_inline_size)
|
||||||
// recalculate the inline size and margins with ‘min-inline-size’ as the
|
},
|
||||||
// computed ‘inline-size’. We can assume the new inline size won’t be ‘auto’,
|
None => {
|
||||||
// because a non-‘auto’ computed ‘inline-size’ always becomes the used value.
|
// Now we can properly solve the block size.
|
||||||
// https://drafts.csswg.org/css2/#min-max-widths (step 3)
|
block_axis = block_axis_solver
|
||||||
if inline_size < min_size.inline {
|
.solve(Some(|| independent_layout.content_block_size.into()));
|
||||||
inline_axis = inline_axis_solver
|
(block_axis.size.non_auto().unwrap(), inline_size)
|
||||||
.solve_for_size(AuOrAuto::LengthPercentage(min_size.inline));
|
},
|
||||||
inline_size = inline_axis.size.auto_is(|| unreachable!());
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Result {
|
|
||||||
content_size: LogicalVec2<Au>,
|
|
||||||
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: &style,
|
|
||||||
};
|
|
||||||
// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
|
|
||||||
assert_eq!(
|
|
||||||
containing_block.style.writing_mode.is_horizontal(),
|
|
||||||
containing_block_for_children
|
|
||||||
.style
|
|
||||||
.writing_mode
|
|
||||||
.is_horizontal(),
|
|
||||||
"Mixed horizontal and vertical writing modes are not supported yet"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Clear the context since we will lay out the same descendants
|
|
||||||
// more than once. Otherwise, absolute descendants will create
|
|
||||||
// multiple fragments which could later lead to double-borrow
|
|
||||||
// errors.
|
|
||||||
positioning_context.clear();
|
|
||||||
|
|
||||||
let independent_layout = non_replaced.layout(
|
|
||||||
layout_context,
|
|
||||||
&mut positioning_context,
|
|
||||||
&containing_block_for_children,
|
|
||||||
&containing_block.into(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let (block_size, inline_size) =
|
|
||||||
match independent_layout.content_inline_size_for_table {
|
|
||||||
Some(inline_size) => {
|
|
||||||
(independent_layout.content_block_size, inline_size)
|
|
||||||
},
|
|
||||||
None => (
|
|
||||||
size.auto_is(|| independent_layout.content_block_size),
|
|
||||||
inline_size,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
Result {
|
|
||||||
content_size: LogicalVec2 {
|
|
||||||
inline: inline_size,
|
|
||||||
block: block_size,
|
|
||||||
},
|
|
||||||
fragments: independent_layout.fragments,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut result = try_layout(block_axis.size);
|
content_size = LogicalVec2 {
|
||||||
|
inline: inline_size,
|
||||||
// If the tentative used block size is greater than ‘max-block-size’,
|
block: block_size,
|
||||||
// recalculate the block size and margins with ‘max-block-size’ as the
|
};
|
||||||
// computed ‘block-size’. We can assume the new block size won’t be ‘auto’,
|
fragments = independent_layout.fragments;
|
||||||
// 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 =
|
|
||||||
block_axis_solver.solve_for_size(AuOrAuto::LengthPercentage(max));
|
|
||||||
result = try_layout(AuOrAuto::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 won’t 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 = block_axis_solver
|
|
||||||
.solve_for_size(AuOrAuto::LengthPercentage(min_size.block));
|
|
||||||
result = try_layout(AuOrAuto::LengthPercentage(min_size.block));
|
|
||||||
}
|
|
||||||
|
|
||||||
content_size = result.content_size;
|
|
||||||
fragments = result.fragments;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -736,10 +660,10 @@ impl HoistedAbsolutelyPositionedBox {
|
||||||
inline_axis_solver.solve_alignment(margin_box_rect, &mut content_rect);
|
inline_axis_solver.solve_alignment(margin_box_rect, &mut content_rect);
|
||||||
|
|
||||||
BoxFragment::new(
|
BoxFragment::new(
|
||||||
absolutely_positioned_box.context.base_fragment_info(),
|
context.base_fragment_info(),
|
||||||
style,
|
style,
|
||||||
fragments,
|
fragments,
|
||||||
content_rect.to_physical(Some(&containing_block.into())),
|
content_rect.to_physical(Some(containing_block)),
|
||||||
pbm.padding.to_physical(containing_block_writing_mode),
|
pbm.padding.to_physical(containing_block_writing_mode),
|
||||||
pbm.border.to_physical(containing_block_writing_mode),
|
pbm.border.to_physical(containing_block_writing_mode),
|
||||||
margin.to_physical(containing_block_writing_mode),
|
margin.to_physical(containing_block_writing_mode),
|
||||||
|
@ -800,11 +724,21 @@ impl AbsoluteBoxOffsets<'_> {
|
||||||
!self.start.is_auto() || !self.end.is_auto()
|
!self.start.is_auto() || !self.end.is_auto()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Anchor {
|
enum Anchor {
|
||||||
Start(Au),
|
Start(Au),
|
||||||
End(Au),
|
End(Au),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Anchor {
|
||||||
|
fn inset(&self) -> Au {
|
||||||
|
match self {
|
||||||
|
Self::Start(start) => *start,
|
||||||
|
Self::End(end) => *end,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct AxisResult {
|
struct AxisResult {
|
||||||
anchor: Anchor,
|
anchor: Anchor,
|
||||||
size: AuOrAuto,
|
size: AuOrAuto,
|
||||||
|
@ -818,12 +752,16 @@ struct AbsoluteAxisSolver<'a> {
|
||||||
padding_border_sum: Au,
|
padding_border_sum: Au,
|
||||||
computed_margin_start: AuOrAuto,
|
computed_margin_start: AuOrAuto,
|
||||||
computed_margin_end: AuOrAuto,
|
computed_margin_end: AuOrAuto,
|
||||||
|
computed_size: AuOrAuto,
|
||||||
|
computed_min_size: Au,
|
||||||
|
computed_max_size: Option<Au>,
|
||||||
avoid_negative_margin_start: bool,
|
avoid_negative_margin_start: bool,
|
||||||
box_offsets: AbsoluteBoxOffsets<'a>,
|
box_offsets: AbsoluteBoxOffsets<'a>,
|
||||||
static_position_rect_axis: RectAxis,
|
static_position_rect_axis: RectAxis,
|
||||||
alignment: AlignFlags,
|
alignment: AlignFlags,
|
||||||
flip_anchor: bool,
|
flip_anchor: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AbsoluteAxisSolver<'a> {
|
impl<'a> AbsoluteAxisSolver<'a> {
|
||||||
/// This unifies some of the parts in common in:
|
/// This unifies some of the parts in common in:
|
||||||
///
|
///
|
||||||
|
@ -836,79 +774,81 @@ impl<'a> AbsoluteAxisSolver<'a> {
|
||||||
/// * <https://drafts.csswg.org/css2/visudet.html#abs-replaced-height>
|
/// * <https://drafts.csswg.org/css2/visudet.html#abs-replaced-height>
|
||||||
///
|
///
|
||||||
/// In the replaced case, `size` is never `Auto`.
|
/// In the replaced case, `size` is never `Auto`.
|
||||||
fn solve_for_size(&self, computed_size: AuOrAuto) -> AxisResult {
|
fn solve(&self, get_content_size: Option<impl FnOnce() -> ContentSizes>) -> AxisResult {
|
||||||
|
let solve_for_anchor = |anchor: Anchor| {
|
||||||
|
let margin_start = self.computed_margin_start.auto_is(Au::zero);
|
||||||
|
let margin_end = self.computed_margin_end.auto_is(Au::zero);
|
||||||
|
let size = self
|
||||||
|
.computed_size
|
||||||
|
.non_auto()
|
||||||
|
.or_else(|| {
|
||||||
|
let content_size = get_content_size?();
|
||||||
|
let available_size = self.containing_size -
|
||||||
|
anchor.inset() -
|
||||||
|
self.padding_border_sum -
|
||||||
|
margin_start -
|
||||||
|
margin_end;
|
||||||
|
Some(content_size.shrink_to_fit(available_size))
|
||||||
|
})
|
||||||
|
.map(|size| {
|
||||||
|
size.clamp_between_extremums(self.computed_min_size, self.computed_max_size)
|
||||||
|
})
|
||||||
|
.map_or(AuOrAuto::Auto, AuOrAuto::LengthPercentage);
|
||||||
|
AxisResult {
|
||||||
|
anchor,
|
||||||
|
size,
|
||||||
|
margin_start,
|
||||||
|
margin_end,
|
||||||
|
}
|
||||||
|
};
|
||||||
match (
|
match (
|
||||||
self.box_offsets.start.non_auto(),
|
self.box_offsets.start.non_auto(),
|
||||||
self.box_offsets.end.non_auto(),
|
self.box_offsets.end.non_auto(),
|
||||||
) {
|
) {
|
||||||
(None, None) => AxisResult {
|
(None, None) => solve_for_anchor(if self.flip_anchor {
|
||||||
anchor: if self.flip_anchor {
|
Anchor::End(self.containing_size - self.static_position_rect_axis.origin)
|
||||||
Anchor::End(self.containing_size - self.static_position_rect_axis.origin)
|
} else {
|
||||||
} else {
|
Anchor::Start(self.static_position_rect_axis.origin)
|
||||||
Anchor::Start(self.static_position_rect_axis.origin)
|
}),
|
||||||
},
|
(Some(start), None) => {
|
||||||
size: computed_size,
|
solve_for_anchor(Anchor::Start(start.to_used_value(self.containing_size)))
|
||||||
margin_start: self.computed_margin_start.auto_is(Au::zero),
|
|
||||||
margin_end: self.computed_margin_end.auto_is(Au::zero),
|
|
||||||
},
|
},
|
||||||
(Some(start), None) => AxisResult {
|
(None, Some(end)) => {
|
||||||
anchor: Anchor::Start(start.to_used_value(self.containing_size)),
|
solve_for_anchor(Anchor::End(end.to_used_value(self.containing_size)))
|
||||||
size: computed_size,
|
|
||||||
margin_start: self.computed_margin_start.auto_is(Au::zero),
|
|
||||||
margin_end: self.computed_margin_end.auto_is(Au::zero),
|
|
||||||
},
|
|
||||||
(None, Some(end)) => AxisResult {
|
|
||||||
anchor: Anchor::End(end.to_used_value(self.containing_size)),
|
|
||||||
size: computed_size,
|
|
||||||
margin_start: self.computed_margin_start.auto_is(Au::zero),
|
|
||||||
margin_end: self.computed_margin_end.auto_is(Au::zero),
|
|
||||||
},
|
},
|
||||||
(Some(start), Some(end)) => {
|
(Some(start), Some(end)) => {
|
||||||
let start = start.to_used_value(self.containing_size);
|
let start = start.to_used_value(self.containing_size);
|
||||||
let end = end.to_used_value(self.containing_size);
|
let end = end.to_used_value(self.containing_size);
|
||||||
|
let mut free_space = self.containing_size - start - end - self.padding_border_sum;
|
||||||
let margin_start;
|
let used_size = self
|
||||||
let margin_end;
|
.computed_size
|
||||||
let used_size;
|
.auto_is(|| {
|
||||||
if let AuOrAuto::LengthPercentage(s) = computed_size {
|
free_space -
|
||||||
used_size = s;
|
self.computed_margin_start.auto_is(Au::zero) -
|
||||||
let margins = self.containing_size - start - end - self.padding_border_sum - s;
|
self.computed_margin_end.auto_is(Au::zero)
|
||||||
|
})
|
||||||
|
.clamp_between_extremums(self.computed_min_size, self.computed_max_size);
|
||||||
|
free_space -= used_size;
|
||||||
|
let (margin_start, margin_end) =
|
||||||
match (self.computed_margin_start, self.computed_margin_end) {
|
match (self.computed_margin_start, self.computed_margin_end) {
|
||||||
(AuOrAuto::Auto, AuOrAuto::Auto) => {
|
(AuOrAuto::Auto, AuOrAuto::Auto) => {
|
||||||
if self.avoid_negative_margin_start && margins < Au::zero() {
|
if self.avoid_negative_margin_start && free_space < Au::zero() {
|
||||||
margin_start = Au::zero();
|
(Au::zero(), free_space)
|
||||||
margin_end = margins;
|
|
||||||
} else {
|
} else {
|
||||||
margin_start = margins / 2;
|
let margin_start = free_space / 2;
|
||||||
margin_end = margins - margin_start;
|
(margin_start, free_space - margin_start)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => {
|
(AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => {
|
||||||
margin_start = margins - end;
|
(free_space - end, end)
|
||||||
margin_end = end;
|
|
||||||
},
|
},
|
||||||
(AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => {
|
(AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => {
|
||||||
margin_start = start;
|
(start, free_space - start)
|
||||||
margin_end = margins - start;
|
|
||||||
},
|
},
|
||||||
(AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
|
(AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
|
||||||
margin_start = start;
|
(start, end)
|
||||||
margin_end = end;
|
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
} else {
|
|
||||||
margin_start = self.computed_margin_start.auto_is(Au::zero);
|
|
||||||
margin_end = self.computed_margin_end.auto_is(Au::zero);
|
|
||||||
|
|
||||||
// This may be negative, but the caller will later effectively
|
|
||||||
// clamp it to ‘min-inline-size’ or ‘min-block-size’.
|
|
||||||
used_size = self.containing_size -
|
|
||||||
start -
|
|
||||||
end -
|
|
||||||
self.padding_border_sum -
|
|
||||||
margin_start -
|
|
||||||
margin_end;
|
|
||||||
};
|
|
||||||
AxisResult {
|
AxisResult {
|
||||||
anchor: Anchor::Start(start),
|
anchor: Anchor::Start(start),
|
||||||
size: AuOrAuto::LengthPercentage(used_size),
|
size: AuOrAuto::LengthPercentage(used_size),
|
||||||
|
@ -919,6 +859,26 @@ impl<'a> AbsoluteAxisSolver<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn solve_tentatively(&mut self) -> AxisResult {
|
||||||
|
self.solve(None::<fn() -> ContentSizes>)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solve_with_size(&mut self, size: Au) -> AxisResult {
|
||||||
|
// Override sizes
|
||||||
|
let old_size = mem::replace(&mut self.computed_size, AuOrAuto::LengthPercentage(size));
|
||||||
|
let old_min_size = mem::replace(&mut self.computed_min_size, Au::zero());
|
||||||
|
let old_max_size = mem::replace(&mut self.computed_max_size, None);
|
||||||
|
|
||||||
|
let result = self.solve_tentatively();
|
||||||
|
|
||||||
|
// Restore original sizes
|
||||||
|
self.computed_size = old_size;
|
||||||
|
self.computed_min_size = old_min_size;
|
||||||
|
self.computed_max_size = old_max_size;
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
fn origin_for_alignment_or_justification(&self, margin_box_axis: RectAxis) -> Option<Au> {
|
fn origin_for_alignment_or_justification(&self, margin_box_axis: RectAxis) -> Option<Au> {
|
||||||
let alignment_container = match (
|
let alignment_container = match (
|
||||||
self.box_offsets.start.non_auto(),
|
self.box_offsets.start.non_auto(),
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
[position-absolute-center-006.html]
|
|
||||||
expected: FAIL
|
|
|
@ -1,2 +0,0 @@
|
||||||
[position-absolute-center-007.html]
|
|
||||||
expected: FAIL
|
|
Loading…
Add table
Add a link
Reference in a new issue