servo/components/layout_2020/positioned.rs
2024-01-15 14:31:21 +00:00

890 lines
38 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
use rayon::iter::IntoParallelRefMutIterator;
use rayon::prelude::{IndexedParallelIterator, ParallelIterator};
use serde::Serialize;
use style::computed_values::position::T as Position;
use style::properties::ComputedValues;
use style::values::computed::{CSSPixelLength, Length};
use style::values::specified::text::TextDecorationLine;
use style::Zero;
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::dom::NodeExt;
use crate::dom_traversal::{Contents, NodeAndStyleInfo};
use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragment_tree::{
AbsoluteBoxOffsets, BoxFragment, CollapsedBlockMargins, Fragment, HoistedSharedFragment,
};
use crate::geom::{LengthOrAuto, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalVec2};
use crate::style_ext::{ComputedValuesExt, DisplayInside};
use crate::{ContainingBlock, DefiniteContainingBlock};
#[derive(Debug, Serialize)]
pub(crate) struct AbsolutelyPositionedBox {
pub context: IndependentFormattingContext,
}
pub(crate) struct PositioningContext {
for_nearest_positioned_ancestor: Option<Vec<HoistedAbsolutelyPositionedBox>>,
// For nearest `containing block for all descendants` as defined by the CSS transforms
// spec.
// https://www.w3.org/TR/css-transforms-1/#containing-block-for-all-descendants
for_nearest_containing_block_for_all_descendants: Vec<HoistedAbsolutelyPositionedBox>,
}
pub(crate) struct HoistedAbsolutelyPositionedBox {
absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>,
/// A reference to a Fragment which is shared between this `HoistedAbsolutelyPositionedBox`
/// and its placeholder `AbsoluteOrFixedPositionedFragment` in the original tree position.
/// This will be used later in order to paint this hoisted box in tree order.
pub fragment: ArcRefCell<HoistedSharedFragment>,
}
impl AbsolutelyPositionedBox {
pub fn construct<'dom>(
context: &LayoutContext,
node_info: &NodeAndStyleInfo<impl NodeExt<'dom>>,
display_inside: DisplayInside,
contents: Contents,
) -> Self {
Self {
context: IndependentFormattingContext::construct(
context,
node_info,
display_inside,
contents,
// Text decorations are not propagated to any out-of-flow descendants.
TextDecorationLine::NONE,
),
}
}
pub(crate) fn to_hoisted(
self_: ArcRefCell<Self>,
initial_start_corner: LogicalVec2<Length>,
containing_block: &ContainingBlock,
) -> HoistedAbsolutelyPositionedBox {
fn absolute_box_offsets(
initial_static_start: Length,
start: LengthPercentageOrAuto<'_>,
end: LengthPercentageOrAuto<'_>,
) -> AbsoluteBoxOffsets {
match (start.non_auto(), end.non_auto()) {
(None, None) => AbsoluteBoxOffsets::StaticStart {
start: initial_static_start,
},
(Some(start), Some(end)) => AbsoluteBoxOffsets::Both {
start: start.clone(),
end: end.clone(),
},
(None, Some(end)) => AbsoluteBoxOffsets::End { end: end.clone() },
(Some(start), None) => AbsoluteBoxOffsets::Start {
start: start.clone(),
},
}
}
let box_offsets = {
let box_ = self_.borrow();
let box_offsets = box_.context.style().box_offsets(containing_block);
LogicalVec2 {
inline: absolute_box_offsets(
initial_start_corner.inline,
box_offsets.inline_start,
box_offsets.inline_end,
),
block: absolute_box_offsets(
initial_start_corner.block,
box_offsets.block_start,
box_offsets.block_end,
),
}
};
HoistedAbsolutelyPositionedBox {
fragment: ArcRefCell::new(HoistedSharedFragment::new(box_offsets)),
absolutely_positioned_box: self_,
}
}
}
impl PositioningContext {
pub(crate) fn new_for_containing_block_for_all_descendants() -> Self {
Self {
for_nearest_positioned_ancestor: None,
for_nearest_containing_block_for_all_descendants: Vec::new(),
}
}
/// Create a [PositioninContext] to use for laying out a subtree. The idea is that
/// when subtree layout is finished, the newly hoisted boxes can be processed
/// (normally adjusting their static insets) and then appended to the parent
/// [PositioningContext].
pub(crate) fn new_for_subtree(collects_for_nearest_positioned_ancestor: bool) -> Self {
Self {
for_nearest_positioned_ancestor: if collects_for_nearest_positioned_ancestor {
Some(Vec::new())
} else {
None
},
for_nearest_containing_block_for_all_descendants: Vec::new(),
}
}
pub(crate) fn collects_for_nearest_positioned_ancestor(&self) -> bool {
self.for_nearest_positioned_ancestor.is_some()
}
pub(crate) fn new_for_style(style: &ComputedValues) -> Option<Self> {
if style.establishes_containing_block_for_all_descendants() {
Some(Self::new_for_containing_block_for_all_descendants())
} else if style.establishes_containing_block_for_absolute_descendants() {
Some(Self {
for_nearest_positioned_ancestor: Some(Vec::new()),
for_nearest_containing_block_for_all_descendants: Vec::new(),
})
} else {
None
}
}
/// Absolute and fixed position fragments are hoisted up to their containing blocks
/// from their tree position. When these fragments have static inset start positions,
/// that position (relative to the ancestor containing block) needs to be included
/// with the hoisted fragment so that it can be laid out properly at the containing
/// block.
///
/// This function is used to update the static position of hoisted boxes added after
/// the given index at every level of the fragment tree as the hoisted fragments move
/// up to their containing blocks. Once an ancestor fragment is laid out, this
/// function can be used to aggregate its offset to any descendent boxes that are
/// being hoisted. In this case, the appropriate index to use is the result of
/// [`PositioningContext::len()`] cached before laying out the [`Fragment`].
pub(crate) fn adjust_static_position_of_hoisted_fragments(
&mut self,
parent_fragment: &Fragment,
index: PositioningContextLength,
) {
let start_offset = match &parent_fragment {
Fragment::Box(b) | Fragment::Float(b) => &b.content_rect.start_corner,
Fragment::AbsoluteOrFixedPositioned(_) => return,
Fragment::Anonymous(a) => &a.rect.start_corner,
_ => unreachable!(),
};
self.adjust_static_position_of_hoisted_fragments_with_offset(start_offset, index);
}
/// See documentation for [adjust_static_position_of_hoisted_fragments].
pub(crate) fn adjust_static_position_of_hoisted_fragments_with_offset(
&mut self,
start_offset: &LogicalVec2<CSSPixelLength>,
index: PositioningContextLength,
) {
let update_fragment_if_needed = |hoisted_fragment: &mut HoistedAbsolutelyPositionedBox| {
let mut fragment = hoisted_fragment.fragment.borrow_mut();
if let AbsoluteBoxOffsets::StaticStart { start } = &mut fragment.box_offsets.inline {
*start += start_offset.inline;
}
if let AbsoluteBoxOffsets::StaticStart { start } = &mut fragment.box_offsets.block {
*start += start_offset.block;
}
};
self.for_nearest_positioned_ancestor
.as_mut()
.map(|hoisted_boxes| {
hoisted_boxes
.iter_mut()
.skip(index.for_nearest_positioned_ancestor)
.for_each(update_fragment_if_needed);
});
self.for_nearest_containing_block_for_all_descendants
.iter_mut()
.skip(index.for_nearest_containing_block_for_all_descendants)
.for_each(update_fragment_if_needed);
}
/// Given `fragment_layout_fn`, a closure which lays out a fragment in a provided
/// `PositioningContext`, create a new positioning context if necessary for the fragment and
/// lay out the fragment and all its children. Returns the newly created `BoxFragment`.
pub(crate) fn layout_maybe_position_relative_fragment(
&mut self,
layout_context: &LayoutContext,
containing_block: &ContainingBlock,
style: &ComputedValues,
fragment_layout_fn: impl FnOnce(&mut Self) -> BoxFragment,
) -> BoxFragment {
// Try to create a context, but if one isn't necessary, simply create the fragment
// using the given closure and the current `PositioningContext`.
let mut new_context = match Self::new_for_style(style) {
Some(new_context) => new_context,
None => return fragment_layout_fn(self),
};
let mut new_fragment = fragment_layout_fn(&mut new_context);
new_context.layout_collected_children(layout_context, &mut new_fragment);
// If the new context has any hoisted boxes for the nearest containing block for
// pass them up the tree.
self.append(new_context);
if style.clone_position() == Position::Relative {
new_fragment.content_rect.start_corner +=
&relative_adjustement(style, containing_block);
}
new_fragment
}
// Lay out the hoisted boxes collected into this `PositioningContext` and add them
// to the given `BoxFragment`.
pub fn layout_collected_children(
&mut self,
layout_context: &LayoutContext,
new_fragment: &mut BoxFragment,
) {
let padding_rect = LogicalRect {
size: new_fragment.content_rect.size.clone(),
// Ignore the content rects position in its own containing block:
start_corner: LogicalVec2::zero(),
}
.inflate(&new_fragment.padding);
let containing_block = DefiniteContainingBlock {
size: padding_rect.size.clone(),
style: &new_fragment.style,
};
let take_hoisted_boxes_pending_layout = |context: &mut Self| match context
.for_nearest_positioned_ancestor
.as_mut()
{
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)
// more absolutely-positioned boxes while doing layout for others.
let mut hoisted_boxes = take_hoisted_boxes_pending_layout(self);
let mut laid_out_child_fragments = Vec::new();
while !hoisted_boxes.is_empty() {
HoistedAbsolutelyPositionedBox::layout_many(
layout_context,
&mut hoisted_boxes,
&mut laid_out_child_fragments,
&mut self.for_nearest_containing_block_for_all_descendants,
&containing_block,
);
hoisted_boxes = take_hoisted_boxes_pending_layout(self);
}
new_fragment.children.extend(laid_out_child_fragments);
}
pub(crate) fn push(&mut self, box_: HoistedAbsolutelyPositionedBox) {
if let Some(nearest) = &mut self.for_nearest_positioned_ancestor {
let position = box_
.absolutely_positioned_box
.borrow()
.context
.style()
.clone_position();
match position {
Position::Fixed => {}, // fall through
Position::Absolute => return nearest.push(box_),
Position::Static | Position::Relative | Position::Sticky => unreachable!(),
}
}
self.for_nearest_containing_block_for_all_descendants
.push(box_)
}
fn is_empty(&self) -> bool {
self.for_nearest_containing_block_for_all_descendants
.is_empty() &&
self.for_nearest_positioned_ancestor
.as_ref()
.map_or(true, |vector| vector.is_empty())
}
pub(crate) fn append(&mut self, other: Self) {
if other.is_empty() {
return;
}
vec_append_owned(
&mut self.for_nearest_containing_block_for_all_descendants,
other.for_nearest_containing_block_for_all_descendants,
);
match (
self.for_nearest_positioned_ancestor.as_mut(),
other.for_nearest_positioned_ancestor,
) {
(Some(us), Some(them)) => vec_append_owned(us, them),
(None, Some(them)) => {
// This is the case where we have laid out the absolute children in a containing
// block for absolutes and we then are passing up the fixed-position descendants
// to the containing block for all descendants.
vec_append_owned(
&mut self.for_nearest_containing_block_for_all_descendants,
them,
);
},
(None, None) => {},
_ => unreachable!(),
}
}
pub(crate) fn layout_initial_containing_block_children(
&mut self,
layout_context: &LayoutContext,
initial_containing_block: &DefiniteContainingBlock,
fragments: &mut Vec<ArcRefCell<Fragment>>,
) {
debug_assert!(self.for_nearest_positioned_ancestor.is_none());
// Loop because its possible that we discover (the static position of)
// more absolutely-positioned boxes while doing layout for others.
while !self
.for_nearest_containing_block_for_all_descendants
.is_empty()
{
HoistedAbsolutelyPositionedBox::layout_many(
layout_context,
&mut std::mem::take(&mut self.for_nearest_containing_block_for_all_descendants),
fragments,
&mut self.for_nearest_containing_block_for_all_descendants,
initial_containing_block,
)
}
}
pub(crate) fn clear(&mut self) {
self.for_nearest_containing_block_for_all_descendants
.clear();
self.for_nearest_positioned_ancestor
.as_mut()
.map(|v| v.clear());
}
/// Get the length of this [PositioningContext].
pub(crate) fn len(&self) -> PositioningContextLength {
PositioningContextLength {
for_nearest_positioned_ancestor: self
.for_nearest_positioned_ancestor
.as_ref()
.map_or(0, |vec| vec.len()),
for_nearest_containing_block_for_all_descendants: self
.for_nearest_containing_block_for_all_descendants
.len(),
}
}
/// Truncate this [PositioningContext] to the given [PositioningContextLength]. This
/// is useful for "unhoisting" boxes in this context and returning it to the state at
/// the time that [`len()`] was called.
pub(crate) fn truncate(&mut self, length: &PositioningContextLength) {
if let Some(vec) = self.for_nearest_positioned_ancestor.as_mut() {
vec.truncate(length.for_nearest_positioned_ancestor);
}
self.for_nearest_containing_block_for_all_descendants
.truncate(length.for_nearest_containing_block_for_all_descendants);
}
}
/// A data structure which stores the size of a positioning context.
#[derive(PartialEq)]
pub(crate) struct PositioningContextLength {
/// The number of boxes that will be hoisted the the nearest positioned ancestor for
/// layout.
for_nearest_positioned_ancestor: usize,
/// The number of boxes that will be hoisted the the nearest ancestor which
/// establishes a containing block for all descendants for layout.
for_nearest_containing_block_for_all_descendants: usize,
}
impl Zero for PositioningContextLength {
fn zero() -> Self {
PositioningContextLength {
for_nearest_positioned_ancestor: 0,
for_nearest_containing_block_for_all_descendants: 0,
}
}
fn is_zero(&self) -> bool {
self.for_nearest_positioned_ancestor == 0 &&
self.for_nearest_containing_block_for_all_descendants == 0
}
}
impl HoistedAbsolutelyPositionedBox {
pub(crate) fn layout_many(
layout_context: &LayoutContext,
boxes: &mut [Self],
fragments: &mut Vec<ArcRefCell<Fragment>>,
for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>,
containing_block: &DefiniteContainingBlock,
) {
if layout_context.use_rayon {
let mut new_fragments = Vec::new();
let mut new_hoisted_boxes = Vec::new();
boxes
.par_iter_mut()
.map(|hoisted_box| {
let mut new_hoisted_boxes: Vec<HoistedAbsolutelyPositionedBox> = Vec::new();
let new_fragment = ArcRefCell::new(Fragment::Box(hoisted_box.layout(
layout_context,
&mut new_hoisted_boxes,
containing_block,
)));
hoisted_box.fragment.borrow_mut().fragment = Some(new_fragment.clone());
(new_fragment, new_hoisted_boxes)
})
.unzip_into_vecs(&mut new_fragments, &mut new_hoisted_boxes);
fragments.extend(new_fragments);
for_nearest_containing_block_for_all_descendants
.extend(new_hoisted_boxes.into_iter().flatten());
} else {
fragments.extend(boxes.iter_mut().map(|box_| {
let new_fragment = ArcRefCell::new(Fragment::Box(box_.layout(
layout_context,
for_nearest_containing_block_for_all_descendants,
containing_block,
)));
box_.fragment.borrow_mut().fragment = Some(new_fragment.clone());
new_fragment
}))
}
}
pub(crate) fn layout(
&mut self,
layout_context: &LayoutContext,
for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>,
containing_block: &DefiniteContainingBlock,
) -> BoxFragment {
let cbis = containing_block.size.inline;
let cbbs = containing_block.size.block;
let mut absolutely_positioned_box = self.absolutely_positioned_box.borrow_mut();
let pbm = absolutely_positioned_box
.context
.style()
.padding_border_margin(&containing_block.into());
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
let used_size = replaced.contents.used_size_as_if_inline_element(
&containing_block.into(),
&replaced.style,
None,
&pbm,
);
LogicalVec2 {
inline: LengthOrAuto::LengthPercentage(used_size.inline),
block: LengthOrAuto::LengthPercentage(used_size.block),
}
},
IndependentFormattingContext::NonReplaced(non_replaced) => non_replaced
.style
.content_box_size(&containing_block.into(), &pbm),
};
let shared_fragment = self.fragment.borrow();
let inline_axis_solver = AbsoluteAxisSolver {
containing_size: cbis,
padding_border_sum: pbm.padding_border_sums.inline,
computed_margin_start: pbm.margin.inline_start,
computed_margin_end: pbm.margin.inline_end,
avoid_negative_margin_start: true,
box_offsets: &shared_fragment.box_offsets.inline,
};
let block_axis_solver = AbsoluteAxisSolver {
containing_size: cbbs,
padding_border_sum: pbm.padding_border_sums.block,
computed_margin_start: pbm.margin.block_start,
computed_margin_end: pbm.margin.block_end,
avoid_negative_margin_start: false,
box_offsets: &shared_fragment.box_offsets.block,
};
let overconstrained = LogicalVec2 {
inline: inline_axis_solver.is_overconstrained_for_size(computed_size.inline),
block: block_axis_solver.is_overconstrained_for_size(computed_size.block),
};
let mut inline_axis = inline_axis_solver.solve_for_size(computed_size.inline);
let mut block_axis = block_axis_solver.solve_for_size(computed_size.block);
let mut positioning_context =
PositioningContext::new_for_style(absolutely_positioned_box.context.style()).unwrap();
let mut new_fragment = {
let content_size;
let fragments;
match &mut 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
let style = &replaced.style;
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 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_sum;
non_replaced
.inline_content_sizes(layout_context)
.shrink_to_fit(available_size)
});
// 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 = inline_axis_solver
.solve_for_size(LengthOrAuto::LengthPercentage(max));
inline_size = inline_axis.size.auto_is(|| unreachable!());
}
}
// 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 = inline_axis_solver
.solve_for_size(LengthOrAuto::LengthPercentage(min_size.inline));
inline_size = inline_axis.size.auto_is(|| unreachable!());
}
struct Result {
content_size: LogicalVec2<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"
);
// 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,
);
let block_size =
size.auto_is(|| independent_layout.content_block_size.into());
Result {
content_size: LogicalVec2 {
inline: inline_size,
block: block_size,
},
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 = block_axis_solver
.solve_for_size(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 = block_axis_solver
.solve_for_size(LengthOrAuto::LengthPercentage(min_size.block));
result = try_layout(LengthOrAuto::LengthPercentage(min_size.block));
}
content_size = result.content_size;
fragments = result.fragments;
},
};
let margin = LogicalSides {
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,
Anchor::End(end) => {
cbis - end - pb.inline_end - margin.inline_end - content_size.inline
},
};
let block_start = match block_axis.anchor {
Anchor::Start(start) => start + pb.block_start + margin.block_start,
Anchor::End(end) => {
cbbs - end - pb.block_end - margin.block_end - content_size.block
},
};
let content_rect = LogicalRect {
start_corner: LogicalVec2 {
inline: inline_start,
block: block_start,
},
size: content_size,
};
let physical_overconstrained =
overconstrained.to_physical(containing_block.style.writing_mode);
BoxFragment::new_with_overconstrained(
absolutely_positioned_box.context.base_fragment_info(),
absolutely_positioned_box.context.style().clone(),
fragments,
content_rect,
pbm.padding,
pbm.border,
margin,
None, /* clearance */
// We do not set the baseline offset, because absolutely positioned
// elements are not inflow.
None, /* last_inflow_baseline_offset */
CollapsedBlockMargins::zero(),
physical_overconstrained,
)
};
positioning_context.layout_collected_children(layout_context, &mut new_fragment);
// Any hoisted boxes that remain in this positioning context are going to be hoisted
// up above this absolutely positioned box. These will necessarily be fixed position
// elements, because absolutely positioned elements form containing blocks for all
// other elements. If any of them have a static start position though, we need to
// adjust it to account for the start corner of this absolute.
positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
&new_fragment.content_rect.start_corner,
PositioningContextLength::zero(),
);
for_nearest_containing_block_for_all_descendants
.extend(positioning_context.for_nearest_containing_block_for_all_descendants);
new_fragment
}
}
enum Anchor {
Start(Length),
End(Length),
}
struct AxisResult {
anchor: Anchor,
size: LengthOrAuto,
margin_start: Length,
margin_end: Length,
}
struct AbsoluteAxisSolver<'a> {
containing_size: Length,
padding_border_sum: Length,
computed_margin_start: LengthOrAuto,
computed_margin_end: LengthOrAuto,
avoid_negative_margin_start: bool,
box_offsets: &'a AbsoluteBoxOffsets,
}
impl<'a> AbsoluteAxisSolver<'a> {
/// This unifies some of the parts in common in:
///
/// * https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width
/// * https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-height
///
/// … and:
///
/// * https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
/// * https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
///
/// In the replaced case, `size` is never `Auto`.
fn solve_for_size(&self, computed_size: LengthOrAuto) -> AxisResult {
match self.box_offsets {
AbsoluteBoxOffsets::StaticStart { start } => AxisResult {
anchor: Anchor::Start(*start),
size: computed_size,
margin_start: self.computed_margin_start.auto_is(Length::zero),
margin_end: self.computed_margin_end.auto_is(Length::zero),
},
AbsoluteBoxOffsets::Start { start } => AxisResult {
anchor: Anchor::Start(start.percentage_relative_to(self.containing_size)),
size: computed_size,
margin_start: self.computed_margin_start.auto_is(Length::zero),
margin_end: self.computed_margin_end.auto_is(Length::zero),
},
AbsoluteBoxOffsets::End { end } => AxisResult {
anchor: Anchor::End(end.percentage_relative_to(self.containing_size)),
size: computed_size,
margin_start: self.computed_margin_start.auto_is(Length::zero),
margin_end: self.computed_margin_end.auto_is(Length::zero),
},
AbsoluteBoxOffsets::Both { start, end } => {
let start = start.percentage_relative_to(self.containing_size);
let end = end.percentage_relative_to(self.containing_size);
let margin_start;
let margin_end;
let used_size;
if let LengthOrAuto::LengthPercentage(s) = computed_size {
used_size = s;
let margins = self.containing_size - start - end - self.padding_border_sum - s;
match (self.computed_margin_start, self.computed_margin_end) {
(LengthOrAuto::Auto, LengthOrAuto::Auto) => {
if self.avoid_negative_margin_start && margins < Length::zero() {
margin_start = Length::zero();
margin_end = margins;
} else {
margin_start = margins / 2.;
margin_end = margins / 2.;
}
},
(LengthOrAuto::Auto, LengthOrAuto::LengthPercentage(end)) => {
margin_start = margins - end;
margin_end = end;
},
(LengthOrAuto::LengthPercentage(start), LengthOrAuto::Auto) => {
margin_start = start;
margin_end = margins - start;
},
(
LengthOrAuto::LengthPercentage(start),
LengthOrAuto::LengthPercentage(end),
) => {
margin_start = start;
margin_end = end;
},
}
} else {
margin_start = self.computed_margin_start.auto_is(Length::zero);
margin_end = self.computed_margin_end.auto_is(Length::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 {
anchor: Anchor::Start(start),
size: LengthOrAuto::LengthPercentage(used_size),
margin_start,
margin_end,
}
},
}
}
fn is_overconstrained_for_size(&self, computed_size: LengthOrAuto) -> bool {
!computed_size.is_auto() &&
self.box_offsets.both_specified() &&
!self.computed_margin_start.is_auto() &&
!self.computed_margin_end.is_auto()
}
}
fn vec_append_owned<T>(a: &mut Vec<T>, mut b: Vec<T>) {
if a.is_empty() {
*a = b
} else {
a.append(&mut b)
}
}
/// https://drafts.csswg.org/css2/visuren.html#relative-positioning
pub(crate) fn relative_adjustement(
style: &ComputedValues,
containing_block: &ContainingBlock,
) -> LogicalVec2<Length> {
// "If the height of the containing block is not specified explicitly (i.e.,
// it depends on content height), and this element is not absolutely
// positioned, the value computes to 'auto'.""
// https://www.w3.org/TR/CSS2/visudet.html#the-height-property
let cbis = containing_block.inline_size;
let cbbs = containing_block.block_size.auto_is(Length::zero);
let box_offsets = style
.box_offsets(containing_block)
.map_inline_and_block_axes(
|v| v.percentage_relative_to(cbis),
|v| v.percentage_relative_to(cbbs),
);
fn adjust(start: LengthOrAuto, end: LengthOrAuto) -> Length {
match (start, end) {
(LengthOrAuto::Auto, LengthOrAuto::Auto) => Length::zero(),
(LengthOrAuto::Auto, LengthOrAuto::LengthPercentage(end)) => -end,
(LengthOrAuto::LengthPercentage(start), _) => start,
}
}
LogicalVec2 {
inline: adjust(box_offsets.inline_start, box_offsets.inline_end),
block: adjust(box_offsets.block_start, box_offsets.block_end),
}
}