mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
layout: Add incremental box tree construction for inline boxes (#38084)
This changes extend the incremental box tree construction for inline boxes. Since an `InlineItem` can be split into multiple `InlineItem`s by a block element, the reason such an inline item is marked as damaged may simply be the removal of the block element or the need to reconstruct its box tree. Therefore, under the current LayoutDamage design, theoretically, even damaged inline items might still have some of their splits reusable. However, based on the principle of simplicity and effectiveness, this PR only considers reusing undamaged inline boxes. Testing: This should not change observable behavior and is thus covered by existing WPT tests. Signed-off-by: sharpshooter_pt <ibluegalaxy_taoj@163.com>
This commit is contained in:
parent
3f1e170410
commit
5cd57f9dba
3 changed files with 99 additions and 23 deletions
|
@ -85,6 +85,15 @@ pub(crate) struct InlineFormattingContextBuilder {
|
|||
/// [to be split]: https://www.w3.org/TR/CSS2/visuren.html#anonymous-block-level
|
||||
block_in_inline_splits: Vec<Vec<ArcRefCell<InlineItem>>>,
|
||||
|
||||
/// If the [`InlineBox`] of an inline-level element is not damaged, it can be reused
|
||||
/// to support incremental layout. An [`InlineBox`] can be split by block elements
|
||||
/// into multiple [`InlineBox`]es, all inserted into different
|
||||
/// [`InlineFormattingContext`]s. Therefore, [`Self::old_block_in_inline_splits`] is
|
||||
/// used to hold all these split inline boxes from the previous box tree construction
|
||||
/// that are about to be reused, ensuring they can be sequentially inserted into each
|
||||
/// newly built [`InlineFormattingContext`].
|
||||
old_block_in_inline_splits: Vec<Vec<ArcRefCell<InlineBox>>>,
|
||||
|
||||
/// Whether or not the inline formatting context under construction has any
|
||||
/// uncollapsible text content.
|
||||
pub has_uncollapsible_text_content: bool,
|
||||
|
@ -233,20 +242,60 @@ impl InlineFormattingContextBuilder {
|
|||
|
||||
pub(crate) fn start_inline_box(
|
||||
&mut self,
|
||||
inline_box: InlineBox,
|
||||
inline_box_creator: impl FnOnce() -> ArcRefCell<InlineBox>,
|
||||
block_in_inline_splits: Option<Vec<ArcRefCell<InlineItem>>>,
|
||||
old_layout_box: Option<LayoutBox>,
|
||||
) {
|
||||
self.push_control_character_string(inline_box.base.style.bidi_control_chars().0);
|
||||
// If there is an existing undamaged layout box that's compatible, use the `InlineBox` within it.
|
||||
if let Some(LayoutBox::InlineLevel(inline_level_box)) = old_layout_box {
|
||||
let old_block_in_inline_splits: Vec<ArcRefCell<InlineBox>> = inline_level_box
|
||||
.iter()
|
||||
.rev() // reverse to facilate the `Vec::pop` operation
|
||||
.filter_map(|inline_item| match &*inline_item.borrow() {
|
||||
InlineItem::StartInlineBox(inline_box) => Some(inline_box.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
debug_assert!(
|
||||
old_block_in_inline_splits.is_empty() ||
|
||||
old_block_in_inline_splits.len() == inline_level_box.len(),
|
||||
"Create inline box with incompatible `old_layout_box`"
|
||||
);
|
||||
|
||||
self.start_inline_box_internal(
|
||||
inline_box_creator,
|
||||
block_in_inline_splits,
|
||||
old_block_in_inline_splits,
|
||||
);
|
||||
} else {
|
||||
self.start_inline_box_internal(inline_box_creator, block_in_inline_splits, vec![]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_inline_box_internal(
|
||||
&mut self,
|
||||
inline_box_creator: impl FnOnce() -> ArcRefCell<InlineBox>,
|
||||
block_in_inline_splits: Option<Vec<ArcRefCell<InlineItem>>>,
|
||||
mut old_block_in_inline_splits: Vec<ArcRefCell<InlineBox>>,
|
||||
) {
|
||||
let inline_box = old_block_in_inline_splits
|
||||
.pop()
|
||||
.unwrap_or_else(inline_box_creator);
|
||||
|
||||
let borrowed_inline_box = inline_box.borrow();
|
||||
self.push_control_character_string(borrowed_inline_box.base.style.bidi_control_chars().0);
|
||||
|
||||
// Don't push a `SharedInlineStyles` if we are pushing this box when splitting
|
||||
// an IFC for a block-in-inline split. Shared styles are pushed as part of setting
|
||||
// up the second split of the IFC.
|
||||
if inline_box.is_first_split {
|
||||
if borrowed_inline_box.is_first_split {
|
||||
self.shared_inline_styles_stack
|
||||
.push(inline_box.shared_inline_styles.clone());
|
||||
.push(borrowed_inline_box.shared_inline_styles.clone());
|
||||
}
|
||||
std::mem::drop(borrowed_inline_box);
|
||||
|
||||
let (identifier, inline_box) = self.inline_boxes.start_inline_box(inline_box);
|
||||
let identifier = self.inline_boxes.start_inline_box(inline_box.clone());
|
||||
let inline_level_box = ArcRefCell::new(InlineItem::StartInlineBox(inline_box));
|
||||
self.inline_items.push(inline_level_box.clone());
|
||||
self.inline_box_stack.push(identifier);
|
||||
|
@ -254,6 +303,9 @@ impl InlineFormattingContextBuilder {
|
|||
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);
|
||||
|
||||
self.old_block_in_inline_splits
|
||||
.push(old_block_in_inline_splits);
|
||||
}
|
||||
|
||||
/// End the ongoing inline box in this [`InlineFormattingContextBuilder`], returning
|
||||
|
@ -271,6 +323,14 @@ impl InlineFormattingContextBuilder {
|
|||
self.push_control_character_string(inline_level_box.base.style.bidi_control_chars().1);
|
||||
}
|
||||
|
||||
debug_assert!(
|
||||
self.old_block_in_inline_splits
|
||||
.last()
|
||||
.is_some_and(|inline_boxes| inline_boxes.is_empty()),
|
||||
"Reuse incompatible `old_block_in_inline_splits` for inline boxes",
|
||||
);
|
||||
let _ = self.old_block_in_inline_splits.pop();
|
||||
|
||||
block_in_inline_splits.unwrap_or_default()
|
||||
}
|
||||
|
||||
|
@ -394,20 +454,31 @@ impl InlineFormattingContextBuilder {
|
|||
let mut new_builder = Self::new_for_shared_styles(self.shared_inline_styles_stack.clone());
|
||||
|
||||
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)
|
||||
{
|
||||
let old_block_in_inline_splits = std::mem::take(&mut self.old_block_in_inline_splits);
|
||||
for (identifier, already_collected_inline_boxes, being_recollected_inline_boxes) in izip!(
|
||||
self.inline_box_stack.iter(),
|
||||
block_in_inline_splits,
|
||||
old_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),
|
||||
// BoxSlot once the inline box has been fully processed. Meanwhile, being
|
||||
// sure to give the old-block-in-inline-split to new InlineFormattingContext,
|
||||
// so that them will be inserted into each following InlineFormattingContext.
|
||||
let split_inline_box_callback = || {
|
||||
ArcRefCell::new(
|
||||
self.inline_boxes
|
||||
.get(identifier)
|
||||
.borrow()
|
||||
.split_around_block(),
|
||||
)
|
||||
};
|
||||
new_builder.start_inline_box_internal(
|
||||
split_inline_box_callback,
|
||||
Some(already_collected_inline_boxes),
|
||||
being_recollected_inline_boxes,
|
||||
);
|
||||
}
|
||||
let mut inline_builder_from_before_split = std::mem::replace(self, new_builder);
|
||||
|
@ -440,6 +511,7 @@ impl InlineFormattingContextBuilder {
|
|||
}
|
||||
|
||||
assert!(self.inline_box_stack.is_empty());
|
||||
debug_assert!(self.old_block_in_inline_splits.is_empty());
|
||||
Some(InlineFormattingContext::new_with_builder(
|
||||
self,
|
||||
layout_context,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue