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:
Oriol Brufau 2024-10-21 12:41:42 +02:00 committed by GitHub
parent fee927475b
commit 2319764a1e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 195 additions and 256 deletions

View file

@ -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> {

View file

@ -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 its possible that we discover (the static position of) // Loop because its 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 wont 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 wont 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 wont 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 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 = 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(),

View file

@ -1,2 +0,0 @@
[position-absolute-center-006.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[position-absolute-center-007.html]
expected: FAIL