aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout/flow
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout/flow')
-rw-r--r--components/layout/flow/construct.rs21
-rw-r--r--components/layout/flow/inline/construct.rs64
-rw-r--r--components/layout/flow/inline/inline_box.rs16
-rw-r--r--components/layout/flow/inline/mod.rs8
-rw-r--r--components/layout/flow/mod.rs105
-rw-r--r--components/layout/flow/root.rs32
6 files changed, 177 insertions, 69 deletions
diff --git a/components/layout/flow/construct.rs b/components/layout/flow/construct.rs
index a6471756db8..5ed567f513b 100644
--- a/components/layout/flow/construct.rs
+++ b/components/layout/flow/construct.rs
@@ -458,15 +458,14 @@ where
self.propagated_data.without_text_decorations(),
),
);
- box_slot.set(LayoutBox::InlineLevel(atomic));
+ box_slot.set(LayoutBox::InlineLevel(vec![atomic]));
return;
};
// Otherwise, this is just a normal inline box. Whatever happened before, all we need to do
// before recurring is to remember this ongoing inline level box.
- let inline_item = self
- .inline_formatting_context_builder
- .start_inline_box(InlineBox::new(info));
+ self.inline_formatting_context_builder
+ .start_inline_box(InlineBox::new(info), None);
if is_list_item {
if let Some((marker_info, marker_contents)) =
@@ -486,8 +485,14 @@ where
self.finish_anonymous_table_if_needed();
- self.inline_formatting_context_builder.end_inline_box();
- box_slot.set(LayoutBox::InlineLevel(inline_item));
+ // As we are ending this inline box, during the course of the `traverse()` above, the ongoing
+ // inline formatting context may have been split around block-level elements. In that case,
+ // more than a single inline box tree item may have been produced for this inline-level box.
+ // `InlineFormattingContextBuilder::end_inline_box()` is returning all of those box tree
+ // items.
+ box_slot.set(LayoutBox::InlineLevel(
+ self.inline_formatting_context_builder.end_inline_box(),
+ ));
}
fn handle_block_level_element(
@@ -574,7 +579,7 @@ where
display_inside,
contents,
));
- box_slot.set(LayoutBox::InlineLevel(inline_level_box));
+ box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box]));
return;
}
@@ -607,7 +612,7 @@ where
contents,
self.propagated_data,
));
- box_slot.set(LayoutBox::InlineLevel(inline_level_box));
+ box_slot.set(LayoutBox::InlineLevel(vec![inline_level_box]));
return;
}
diff --git a/components/layout/flow/inline/construct.rs b/components/layout/flow/inline/construct.rs
index 7c668751ef6..61292701a9f 100644
--- a/components/layout/flow/inline/construct.rs
+++ b/components/layout/flow/inline/construct.rs
@@ -6,6 +6,7 @@ use std::borrow::Cow;
use std::char::{ToLowercase, ToUppercase};
use icu_segmenter::WordSegmenter;
+use itertools::izip;
use servo_arc::Arc;
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
use style::values::specified::text::TextTransformCase;
@@ -67,6 +68,16 @@ pub(crate) struct InlineFormattingContextBuilder {
/// When an inline box ends, it's removed from this stack.
inline_box_stack: Vec<InlineBoxIdentifier>,
+ /// Normally, an inline box produces a single box tree [`InlineItem`]. When a block
+ /// element causes an inline box [to be split], it can produce multiple
+ /// [`InlineItem`]s, all inserted into different [`InlineFormattingContext`]s.
+ /// [`Self::block_in_inline_splits`] is responsible for tracking all of these split
+ /// inline box results, so that they can be inserted into the [`crate::dom::BoxSlot`]
+ /// for the DOM element once it has been processed for BoxTree construction.
+ ///
+ /// [to be split]: https://www.w3.org/TR/CSS2/visuren.html#anonymous-block-level
+ block_in_inline_splits: Vec<Vec<ArcRefCell<InlineItem>>>,
+
/// Whether or not the inline formatting context under construction has any
/// uncollapsible text content.
pub has_uncollapsible_text_content: bool,
@@ -162,29 +173,42 @@ impl InlineFormattingContextBuilder {
inline_level_box
}
- pub(crate) fn start_inline_box(&mut self, inline_box: InlineBox) -> ArcRefCell<InlineItem> {
+ pub(crate) fn start_inline_box(
+ &mut self,
+ inline_box: InlineBox,
+ block_in_inline_splits: Option<Vec<ArcRefCell<InlineItem>>>,
+ ) {
self.push_control_character_string(inline_box.base.style.bidi_control_chars().0);
let (identifier, inline_box) = self.inline_boxes.start_inline_box(inline_box);
let inline_level_box = ArcRefCell::new(InlineItem::StartInlineBox(inline_box));
self.inline_items.push(inline_level_box.clone());
self.inline_box_stack.push(identifier);
- inline_level_box
+
+ let mut block_in_inline_splits = block_in_inline_splits.unwrap_or_default();
+ block_in_inline_splits.push(inline_level_box);
+ self.block_in_inline_splits.push(block_in_inline_splits);
}
- pub(crate) fn end_inline_box(&mut self) -> ArcRefCell<InlineBox> {
- let identifier = self.end_inline_box_internal();
+ /// End the ongoing inline box in this [`InlineFormattingContextBuilder`], returning
+ /// shared references to all of the box tree items that were created for it. More than
+ /// a single box tree items may be produced for a single inline box when that inline
+ /// box is split around a block-level element.
+ pub(crate) fn end_inline_box(&mut self) -> Vec<ArcRefCell<InlineItem>> {
+ let (identifier, block_in_inline_splits) = self.end_inline_box_internal();
let inline_level_box = self.inline_boxes.get(&identifier);
- inline_level_box.borrow_mut().is_last_fragment = true;
-
- self.push_control_character_string(
- inline_level_box.borrow().base.style.bidi_control_chars().1,
- );
+ {
+ let mut inline_level_box = inline_level_box.borrow_mut();
+ inline_level_box.is_last_split = true;
+ self.push_control_character_string(inline_level_box.base.style.bidi_control_chars().1);
+ }
- inline_level_box
+ block_in_inline_splits.unwrap_or_default()
}
- fn end_inline_box_internal(&mut self) -> InlineBoxIdentifier {
+ fn end_inline_box_internal(
+ &mut self,
+ ) -> (InlineBoxIdentifier, Option<Vec<ArcRefCell<InlineItem>>>) {
let identifier = self
.inline_box_stack
.pop()
@@ -193,7 +217,12 @@ impl InlineFormattingContextBuilder {
.push(ArcRefCell::new(InlineItem::EndInlineBox));
self.inline_boxes.end_inline_box(identifier);
- identifier
+
+ // This might be `None` if this builder has already drained its block-in-inline-splits
+ // into the new builder on the other side of a new block-in-inline split.
+ let block_in_inline_splits = self.block_in_inline_splits.pop();
+
+ (identifier, block_in_inline_splits)
}
pub(crate) fn push_text<'dom, Node: NodeExt<'dom>>(
@@ -295,12 +324,21 @@ impl InlineFormattingContextBuilder {
// marked as not being the first fragment. No inline content is carried over to this new
// builder.
let mut new_builder = InlineFormattingContextBuilder::new();
- for identifier in self.inline_box_stack.iter() {
+ let block_in_inline_splits = std::mem::take(&mut self.block_in_inline_splits);
+ for (identifier, historical_inline_boxes) in
+ izip!(self.inline_box_stack.iter(), block_in_inline_splits)
+ {
+ // Start a new inline box for every ongoing inline box in this
+ // InlineFormattingContext once we are done processing this block element,
+ // being sure to give the block-in-inline-split to the new
+ // InlineFormattingContext. These will finally be inserted into the DOM's
+ // BoxSlot once the inline box has been fully processed.
new_builder.start_inline_box(
self.inline_boxes
.get(identifier)
.borrow()
.split_around_block(),
+ Some(historical_inline_boxes),
);
}
let mut inline_builder_from_before_split = std::mem::replace(self, new_builder);
diff --git a/components/layout/flow/inline/inline_box.rs b/components/layout/flow/inline/inline_box.rs
index 97398d6e708..de79f876340 100644
--- a/components/layout/flow/inline/inline_box.rs
+++ b/components/layout/flow/inline/inline_box.rs
@@ -23,8 +23,12 @@ pub(crate) struct InlineBox {
pub base: LayoutBoxBase,
/// The identifier of this inline box in the containing [`super::InlineFormattingContext`].
pub(super) identifier: InlineBoxIdentifier,
- pub is_first_fragment: bool,
- pub is_last_fragment: bool,
+ /// Whether or not this is the first instance of an [`InlineBox`] before a possible
+ /// block-in-inline split. When no split occurs, this is always true.
+ pub is_first_split: bool,
+ /// Whether or not this is the last instance of an [`InlineBox`] before a possible
+ /// block-in-inline split. When no split occurs, this is always true.
+ pub is_last_split: bool,
/// The index of the default font in the [`super::InlineFormattingContext`]'s font metrics store.
/// This is initialized during IFC shaping.
pub default_font_index: Option<usize>,
@@ -36,8 +40,8 @@ impl InlineBox {
base: LayoutBoxBase::new(info.into(), info.style.clone()),
// This will be assigned later, when the box is actually added to the IFC.
identifier: InlineBoxIdentifier::default(),
- is_first_fragment: true,
- is_last_fragment: false,
+ is_first_split: true,
+ is_last_split: false,
default_font_index: None,
}
}
@@ -45,8 +49,8 @@ impl InlineBox {
pub(crate) fn split_around_block(&self) -> Self {
Self {
base: LayoutBoxBase::new(self.base.base_fragment_info, self.base.style.clone()),
- is_first_fragment: false,
- is_last_fragment: false,
+ is_first_split: false,
+ is_last_split: false,
..*self
}
}
diff --git a/components/layout/flow/inline/mod.rs b/components/layout/flow/inline/mod.rs
index 490917d95a3..dabb9773410 100644
--- a/components/layout/flow/inline/mod.rs
+++ b/components/layout/flow/inline/mod.rs
@@ -744,7 +744,7 @@ impl InlineFormattingContextLayout<'_> {
self.containing_block,
self.layout_context,
self.current_inline_container_state(),
- inline_box.is_last_fragment,
+ inline_box.is_last_split,
inline_box
.default_font_index
.map(|index| &self.ifc.font_metrics[index].metrics),
@@ -773,7 +773,7 @@ impl InlineFormattingContextLayout<'_> {
);
}
- if inline_box.is_first_fragment {
+ if inline_box.is_first_split {
self.current_line_segment.inline_size += inline_box_state.pbm.padding.inline_start +
inline_box_state.pbm.border.inline_start +
inline_box_state.pbm.margin.inline_start.auto_is(Au::zero);
@@ -2349,10 +2349,10 @@ impl<'layout_data> ContentSizesComputation<'layout_data> {
.auto_is(Au::zero);
let pbm = margin + padding + border;
- if inline_box.is_first_fragment {
+ if inline_box.is_first_split {
self.add_inline_size(pbm.inline_start);
}
- if inline_box.is_last_fragment {
+ if inline_box.is_last_split {
self.ending_inline_pbm_stack.push(pbm.inline_end);
} else {
self.ending_inline_pbm_stack.push(Au::zero());
diff --git a/components/layout/flow/mod.rs b/components/layout/flow/mod.rs
index f92650ef340..d983e8592c4 100644
--- a/components/layout/flow/mod.rs
+++ b/components/layout/flow/mod.rs
@@ -896,6 +896,7 @@ fn layout_in_flow_non_replaced_block_level_same_formatting_context(
block_sizes,
depends_on_block_constraints,
available_block_size,
+ justify_self,
} = solve_containing_block_padding_and_border_for_in_flow_box(
containing_block,
&layout_style,
@@ -909,6 +910,7 @@ fn layout_in_flow_non_replaced_block_level_same_formatting_context(
containing_block,
&pbm,
containing_block_for_children.size.inline,
+ justify_self,
);
let computed_block_size = style.content_block_size();
@@ -1154,6 +1156,7 @@ impl IndependentNonReplacedContents {
block_sizes,
depends_on_block_constraints,
available_block_size,
+ justify_self,
} = solve_containing_block_padding_and_border_for_in_flow_box(
containing_block,
&layout_style,
@@ -1185,7 +1188,7 @@ impl IndependentNonReplacedContents {
let ResolvedMargins {
margin,
effective_margin_inline_start,
- } = solve_margins(containing_block, &pbm, inline_size);
+ } = solve_margins(containing_block, &pbm, inline_size, justify_self);
let content_rect = LogicalRect {
start_corner: LogicalVec2 {
@@ -1300,17 +1303,12 @@ impl IndependentNonReplacedContents {
.sizes
};
- // TODO: the automatic inline size should take `justify-self` into account.
+ let justify_self = resolve_justify_self(style, containing_block.style);
let is_table = self.is_table();
- let automatic_inline_size = if is_table {
- Size::FitContent
- } else {
- Size::Stretch
- };
let compute_inline_size = |stretch_size| {
content_box_sizes.inline.resolve(
Direction::Inline,
- automatic_inline_size,
+ automatic_inline_size(justify_self, is_table),
Au::zero,
Some(stretch_size),
get_inline_content_sizes,
@@ -1472,6 +1470,7 @@ impl IndependentNonReplacedContents {
&pbm,
content_size.inline + pbm.padding_border_sums.inline,
placement_rect,
+ justify_self,
);
let margin = LogicalSides {
@@ -1558,6 +1557,7 @@ impl ReplacedContents {
let effective_margin_inline_start;
let (margin_block_start, margin_block_end) =
solve_block_margins_for_in_flow_block_level(pbm);
+ let justify_self = resolve_justify_self(&base.style, containing_block.style);
let containing_block_writing_mode = containing_block.style.writing_mode;
let physical_content_size = content_size.to_physical_size(containing_block_writing_mode);
@@ -1597,6 +1597,7 @@ impl ReplacedContents {
pbm,
size.inline,
placement_rect,
+ justify_self,
);
// Clearance prevents margin collapse between this block and previous ones,
@@ -1620,6 +1621,7 @@ impl ReplacedContents {
containing_block,
pbm,
content_size.inline,
+ justify_self,
);
};
@@ -1671,6 +1673,7 @@ struct ContainingBlockPaddingAndBorder<'a> {
block_sizes: Sizes,
depends_on_block_constraints: bool,
available_block_size: Option<Au>,
+ justify_self: AlignFlags,
}
struct ResolvedMargins {
@@ -1719,6 +1722,9 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>(
// The available block size may actually be definite, but it should be irrelevant
// since the sizing properties are set to their initial value.
available_block_size: None,
+ // The initial `justify-self` is `auto`, but use `normal` (behaving as `stretch`).
+ // This is being discussed in <https://github.com/w3c/csswg-drafts/issues/11461>.
+ justify_self: AlignFlags::NORMAL,
};
}
@@ -1755,16 +1761,11 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>(
None, /* TODO: support preferred aspect ratios on non-replaced boxes */
))
};
- // TODO: the automatic inline size should take `justify-self` into account.
+ let justify_self = resolve_justify_self(style, containing_block.style);
let is_table = layout_style.is_table();
- let automatic_inline_size = if is_table {
- Size::FitContent
- } else {
- Size::Stretch
- };
let inline_size = content_box_sizes.inline.resolve(
Direction::Inline,
- automatic_inline_size,
+ automatic_inline_size(justify_self, is_table),
Au::zero,
Some(available_inline_size),
get_inline_content_sizes,
@@ -1793,6 +1794,7 @@ fn solve_containing_block_padding_and_border_for_in_flow_box<'a>(
block_sizes: content_box_sizes.block,
depends_on_block_constraints,
available_block_size,
+ justify_self,
}
}
@@ -1804,9 +1806,15 @@ fn solve_margins(
containing_block: &ContainingBlock<'_>,
pbm: &PaddingBorderMargin,
inline_size: Au,
+ justify_self: AlignFlags,
) -> ResolvedMargins {
let (inline_margins, effective_margin_inline_start) =
- solve_inline_margins_for_in_flow_block_level(containing_block, pbm, inline_size);
+ solve_inline_margins_for_in_flow_block_level(
+ containing_block,
+ pbm,
+ inline_size,
+ justify_self,
+ );
let block_margins = solve_block_margins_for_in_flow_block_level(pbm);
ResolvedMargins {
margin: LogicalSides {
@@ -1829,14 +1837,63 @@ fn solve_block_margins_for_in_flow_block_level(pbm: &PaddingBorderMargin) -> (Au
)
}
-/// This is supposed to handle 'justify-self', but no browser supports it on block boxes.
-/// Instead, `<center>` and `<div align>` are implemented via internal 'text-align' values.
+/// Resolves the `justify-self` value, preserving flags.
+fn resolve_justify_self(style: &ComputedValues, parent_style: &ComputedValues) -> AlignFlags {
+ let is_ltr = |style: &ComputedValues| style.writing_mode.line_left_is_inline_start();
+ let alignment = match style.clone_justify_self().0.0 {
+ AlignFlags::AUTO => parent_style.clone_justify_items().computed.0,
+ alignment => alignment,
+ };
+ let alignment_value = match alignment.value() {
+ AlignFlags::LEFT if is_ltr(parent_style) => AlignFlags::START,
+ AlignFlags::LEFT => AlignFlags::END,
+ AlignFlags::RIGHT if is_ltr(parent_style) => AlignFlags::END,
+ AlignFlags::RIGHT => AlignFlags::START,
+ AlignFlags::SELF_START if is_ltr(parent_style) == is_ltr(style) => AlignFlags::START,
+ AlignFlags::SELF_START => AlignFlags::END,
+ AlignFlags::SELF_END if is_ltr(parent_style) == is_ltr(style) => AlignFlags::END,
+ AlignFlags::SELF_END => AlignFlags::START,
+ alignment_value => alignment_value,
+ };
+ alignment.flags() | alignment_value
+}
+
+/// Determines the automatic size for the inline axis of a block-level box.
+/// <https://drafts.csswg.org/css-sizing-3/#automatic-size>
+#[inline]
+fn automatic_inline_size<T>(justify_self: AlignFlags, is_table: bool) -> Size<T> {
+ match justify_self {
+ AlignFlags::STRETCH => Size::Stretch,
+ AlignFlags::NORMAL if !is_table => Size::Stretch,
+ _ => Size::FitContent,
+ }
+}
+
+/// Justifies a block-level box, distributing the free space according to `justify-self`.
+/// Note `<center>` and `<div align>` are implemented via internal 'text-align' values,
+/// which are also handled here.
/// The provided free space should already take margins into account. In particular,
/// it should be zero if there is an auto margin.
/// <https://drafts.csswg.org/css-align/#justify-block>
-fn justify_self_alignment(containing_block: &ContainingBlock, free_space: Au) -> Au {
+fn justify_self_alignment(
+ containing_block: &ContainingBlock,
+ free_space: Au,
+ justify_self: AlignFlags,
+) -> Au {
+ let mut alignment = justify_self.value();
+ let is_safe = justify_self.flags() == AlignFlags::SAFE || alignment == AlignFlags::NORMAL;
+ if is_safe && free_space <= Au::zero() {
+ alignment = AlignFlags::START
+ }
+ match alignment {
+ AlignFlags::NORMAL => {},
+ AlignFlags::CENTER => return free_space / 2,
+ AlignFlags::END => return free_space,
+ _ => return Au::zero(),
+ }
+
+ // For `justify-self: normal`, fall back to the special 'text-align' values.
let style = containing_block.style;
- debug_assert!(free_space >= Au::zero());
match style.clone_text_align() {
TextAlignKeyword::MozCenter => free_space / 2,
TextAlignKeyword::MozLeft if !style.writing_mode.line_left_is_inline_start() => free_space,
@@ -1861,6 +1918,7 @@ fn solve_inline_margins_for_in_flow_block_level(
containing_block: &ContainingBlock,
pbm: &PaddingBorderMargin,
inline_size: Au,
+ justify_self: AlignFlags,
) -> ((Au, Au), Au) {
let free_space = containing_block.size.inline - pbm.padding_border_sums.inline - inline_size;
let mut justification = Au::zero();
@@ -1878,8 +1936,8 @@ fn solve_inline_margins_for_in_flow_block_level(
// But here we may still have some free space to perform 'justify-self' alignment.
// This aligns the margin box within the containing block, or in other words,
// aligns the border box within the margin-shrunken containing block.
- let free_space = Au::zero().max(free_space - start - end);
- justification = justify_self_alignment(containing_block, free_space);
+ justification =
+ justify_self_alignment(containing_block, free_space - start - end, justify_self);
(start, end)
},
};
@@ -1902,6 +1960,7 @@ fn solve_inline_margins_avoiding_floats(
pbm: &PaddingBorderMargin,
inline_size: Au,
placement_rect: LogicalRect<Au>,
+ justify_self: AlignFlags,
) -> ((Au, Au), Au) {
let free_space = placement_rect.size.inline - inline_size;
debug_assert!(free_space >= Au::zero());
@@ -1922,7 +1981,7 @@ fn solve_inline_margins_avoiding_floats(
// and Blink and WebKit are broken anyways. So we match Gecko instead: this aligns
// the border box within the instersection of the float-shrunken containing-block
// and the margin-shrunken containing-block.
- justification = justify_self_alignment(containing_block, free_space);
+ justification = justify_self_alignment(containing_block, free_space, justify_self);
(start, end)
},
};
diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs
index 390b4664e60..187726595f8 100644
--- a/components/layout/flow/root.rs
+++ b/components/layout/flow/root.rs
@@ -195,16 +195,17 @@ impl BoxTree {
},
_ => return None,
},
- LayoutBox::InlineLevel(inline_level_box) => match &*inline_level_box.borrow() {
- InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index)
- if box_style.position.is_absolutely_positioned() =>
- {
- UpdatePoint::AbsolutelyPositionedInlineLevelBox(
- inline_level_box.clone(),
- *text_offset_index,
- )
- },
- _ => return None,
+ LayoutBox::InlineLevel(inline_level_items) => {
+ let inline_level_box = inline_level_items.first()?;
+ let InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index) =
+ &*inline_level_box.borrow()
+ else {
+ return None;
+ };
+ UpdatePoint::AbsolutelyPositionedInlineLevelBox(
+ inline_level_box.clone(),
+ *text_offset_index,
+ )
},
LayoutBox::FlexLevel(flex_level_box) => match &*flex_level_box.borrow() {
FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_)
@@ -428,13 +429,14 @@ impl BoxTree {
acc.union(&child_overflow)
});
- FragmentTree {
+ FragmentTree::new(
+ layout_context,
root_fragments,
scrollable_overflow,
- initial_containing_block: physical_containing_block,
- canvas_background: self.canvas_background.clone(),
- viewport_scroll_sensitivity: self.viewport_scroll_sensitivity,
- }
+ physical_containing_block,
+ self.canvas_background.clone(),
+ self.viewport_scroll_sensitivity,
+ )
}
}