diff --git a/components/layout/flow/construct.rs b/components/layout/flow/construct.rs index f11efb7c559..7537c98065d 100644 --- a/components/layout/flow/construct.rs +++ b/components/layout/flow/construct.rs @@ -466,6 +466,7 @@ impl<'dom> BlockContainerBuilder<'dom, '_> { contents: Contents, box_slot: BoxSlot<'dom>, ) { + let old_layout_box = box_slot.take_layout_box_if_undamaged(info.damage); let (is_list_item, non_replaced_contents) = match (display_inside, contents) { ( DisplayInside::Flow { is_list_item }, @@ -485,7 +486,7 @@ impl<'dom> BlockContainerBuilder<'dom, '_> { propagated_data, )) }; - let old_layout_box = box_slot.take_layout_box_if_undamaged(info.damage); + let atomic = self .ensure_inline_formatting_context_builder() .push_atomic(construction_callback, old_layout_box); @@ -497,7 +498,11 @@ impl<'dom> BlockContainerBuilder<'dom, '_> { // 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. self.ensure_inline_formatting_context_builder() - .start_inline_box(InlineBox::new(info), None); + .start_inline_box( + || ArcRefCell::new(InlineBox::new(info)), + None, + old_layout_box, + ); if is_list_item { if let Some((marker_info, marker_contents)) = diff --git a/components/layout/flow/inline/construct.rs b/components/layout/flow/inline/construct.rs index b7c124819b8..91353272361 100644 --- a/components/layout/flow/inline/construct.rs +++ b/components/layout/flow/inline/construct.rs @@ -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>>, + /// 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>>, + /// 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, block_in_inline_splits: Option>>, + old_layout_box: Option, ) { - 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> = 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, + block_in_inline_splits: Option>>, + mut old_block_in_inline_splits: Vec>, + ) { + 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, diff --git a/components/layout/flow/inline/inline_box.rs b/components/layout/flow/inline/inline_box.rs index 0db6dd72d59..e9d68dd12c7 100644 --- a/components/layout/flow/inline/inline_box.rs +++ b/components/layout/flow/inline/inline_box.rs @@ -114,8 +114,8 @@ impl InlineBoxes { pub(super) fn start_inline_box( &mut self, - mut inline_box: InlineBox, - ) -> (InlineBoxIdentifier, ArcRefCell) { + inline_box: ArcRefCell, + ) -> InlineBoxIdentifier { assert!(self.inline_boxes.len() <= u32::MAX as usize); assert!(self.inline_box_tree.len() <= u32::MAX as usize); @@ -126,14 +126,13 @@ impl InlineBoxes { index_of_start_in_tree, index_in_inline_boxes, }; - inline_box.identifier = identifier; - let inline_box = ArcRefCell::new(inline_box); + inline_box.borrow_mut().identifier = identifier; - self.inline_boxes.push(inline_box.clone()); + self.inline_boxes.push(inline_box); self.inline_box_tree .push(InlineBoxTreePathToken::Start(identifier)); - (identifier, inline_box) + identifier } pub(super) fn get_path(