diff --git a/components/layout/construct_modern.rs b/components/layout/construct_modern.rs index 805ce3015c5..d24480d9da4 100644 --- a/components/layout/construct_modern.rs +++ b/components/layout/construct_modern.rs @@ -99,6 +99,9 @@ impl<'dom> ModernContainerJob<'dom> { let formatting_context = IndependentFormattingContext::new( LayoutBoxBase::new(info.into(), info.style.clone()), IndependentFormattingContextContents::Flow(block_formatting_context), + // This is just a series of anonymous text runs, so we don't need to worry + // about what kind of PropagatedBoxTreeData is used here. + Default::default(), ); Some(ModernItem { diff --git a/components/layout/dom.rs b/components/layout/dom.rs index c568438b808..21bbc124460 100644 --- a/components/layout/dom.rs +++ b/components/layout/dom.rs @@ -22,6 +22,8 @@ use style::properties::ComputedValues; use style::selector_parser::PseudoElement; use crate::cell::{ArcRefCell, WeakRefCell}; +use crate::context::LayoutContext; +use crate::dom_traversal::NodeAndStyleInfo; use crate::flexbox::FlexLevelBox; use crate::flow::BlockLevelBox; use crate::flow::inline::{InlineItem, SharedInlineStyles, WeakInlineItem}; @@ -312,6 +314,7 @@ pub(crate) trait NodeExt<'dom> { fn ensure_inner_layout_data(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData>; fn inner_layout_data(&self) -> Option>; + fn inner_layout_data_mut(&self) -> Option>; fn box_slot(&self) -> BoxSlot<'dom>; /// Remove boxes for the element itself, and all of its pseudo-element boxes. @@ -333,6 +336,17 @@ pub(crate) trait NodeExt<'dom> { /// /// When this node has no box yet, `false` is returned. fn isolates_damage_for_damage_propagation(&self) -> bool; + + /// Try to re-run box tree reconstruction from this point. This can succeed if the + /// node itself is still valid and isolates box tree damage from ancestors (for + /// instance, if it starts an independent formatting context). **Note:** This assumes + /// that no ancestors have box damage. + /// + /// Returns `true` if box tree reconstruction was sucessful and `false` otherwise. + fn rebuild_box_tree_from_independent_formatting_context( + &self, + layout_context: &LayoutContext, + ) -> bool; } impl<'dom> NodeExt<'dom> for ServoThreadSafeLayoutNode<'dom> { @@ -428,6 +442,16 @@ impl<'dom> NodeExt<'dom> for ServoThreadSafeLayoutNode<'dom> { }) } + fn inner_layout_data_mut(&self) -> Option> { + self.layout_data().map(|data| { + data.as_any() + .downcast_ref::() + .unwrap() + .0 + .borrow_mut() + }) + } + fn box_slot(&self) -> BoxSlot<'dom> { let pseudo_element_chain = self.pseudo_element_chain(); let Some(primary) = pseudo_element_chain.primary else { @@ -517,4 +541,84 @@ impl<'dom> NodeExt<'dom> for ServoThreadSafeLayoutNode<'dom> { LayoutBox::TaffyItemBox(..) => true, } } + + fn rebuild_box_tree_from_independent_formatting_context( + &self, + layout_context: &LayoutContext, + ) -> bool { + // Do not run incremental box tree layout at the `` or root element as there + // is some special processing that must happen for these elements and it currently + // only happens when doing a full box tree construction traversal. + if self.as_element().is_some_and(|element| { + element.is_body_element_of_html_element_root() || element.is_root() + }) { + return false; + } + + let layout_box = { + let Some(mut inner_layout_data) = self.inner_layout_data_mut() else { + return false; + }; + inner_layout_data.pseudo_boxes.clear(); + inner_layout_data.self_box.clone() + }; + + let layout_box = layout_box.borrow(); + let Some(layout_box) = &*layout_box else { + return false; + }; + + let info = NodeAndStyleInfo::new(*self, self.style(&layout_context.style_context)); + match layout_box { + LayoutBox::DisplayContents(..) => false, + LayoutBox::BlockLevel(block_level) => { + let mut block_level = block_level.borrow_mut(); + match &mut *block_level { + BlockLevelBox::Independent(independent_formatting_context) => { + independent_formatting_context.rebuild(layout_context, &info); + true + }, + BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => { + positioned_box + .borrow_mut() + .context + .rebuild(layout_context, &info); + true + }, + _ => false, + } + }, + LayoutBox::InlineLevel(inline_level) => match inline_level { + InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => { + positioned_box + .borrow_mut() + .context + .rebuild(layout_context, &info); + true + }, + InlineItem::Atomic(atomic_box, _, _) => { + atomic_box.borrow_mut().rebuild(layout_context, &info); + true + }, + _ => false, + }, + LayoutBox::FlexLevel(flex_level_box) => { + let mut flex_level_box = flex_level_box.borrow_mut(); + match &mut *flex_level_box { + FlexLevelBox::FlexItem(flex_item_box) => flex_item_box + .independent_formatting_context + .rebuild(layout_context, &info), + FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => { + positioned_box + .borrow_mut() + .context + .rebuild(layout_context, &info); + }, + } + true + }, + LayoutBox::TableLevelBox(..) => false, + LayoutBox::TaffyItemBox(..) => false, + } + } } diff --git a/components/layout/flexbox/mod.rs b/components/layout/flexbox/mod.rs index c6b102c6ed4..0fc52687135 100644 --- a/components/layout/flexbox/mod.rs +++ b/components/layout/flexbox/mod.rs @@ -212,7 +212,7 @@ impl FlexLevelBox { #[derive(MallocSizeOf)] pub(crate) struct FlexItemBox { - independent_formatting_context: IndependentFormattingContext, + pub(crate) independent_formatting_context: IndependentFormattingContext, } impl std::fmt::Debug for FlexItemBox { diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs index cc763ad3865..6ef1dbacca4 100644 --- a/components/layout/flow/root.rs +++ b/components/layout/flow/root.rs @@ -3,35 +3,27 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use app_units::Au; -use atomic_refcell::AtomicRef; use euclid::Rect; use euclid::default::Size2D as UntypedSize2D; +use layout_api::AxesOverflow; use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode}; -use layout_api::{AxesOverflow, LayoutElementType, LayoutNodeType}; use malloc_size_of_derive::MallocSizeOf; use paint_api::display_list::AxesScrollSensitivity; use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode}; -use servo_arc::Arc; -use style::dom::{NodeInfo, TNode}; -use style::properties::ComputedValues; use style::values::computed::Overflow; -use style::values::specified::box_::DisplayOutside; use style_traits::CSSPixel; use crate::cell::ArcRefCell; use crate::context::LayoutContext; use crate::dom::{LayoutBox, NodeExt}; use crate::dom_traversal::{Contents, NodeAndStyleInfo}; -use crate::flexbox::FlexLevelBox; use crate::flow::float::FloatBox; -use crate::flow::inline::InlineItem; use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox}; use crate::formatting_contexts::IndependentFormattingContext; use crate::fragment_tree::{FragmentFlags, FragmentTree}; use crate::geom::{LogicalVec2, PhysicalSize}; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; -use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside}; -use crate::taffy::{TaffyItemBox, TaffyItemBoxInner}; +use crate::style_ext::Display; use crate::{DefiniteContainingBlock, PropagatedBoxTreeData}; #[derive(MallocSizeOf)] @@ -124,37 +116,6 @@ impl BoxTree { root_overflow }) } - - /// This method attempts to incrementally update the box tree from an - /// arbitrary node that is not necessarily the document's root element. - /// - /// If the node is not a valid candidate for incremental update, the method - /// loops over its parent. The only valid candidates for now are absolutely - /// positioned boxes which don't change their outside display mode (i.e. it - /// will not attempt to update from an absolutely positioned inline element - /// which became an absolutely positioned block element). The value `true` - /// is returned if an incremental update could be done, and `false` - /// otherwise. - /// - /// There are various pain points that need to be taken care of to extend - /// the set of valid candidates: - /// * it is not obvious how to incrementally check whether a block - /// formatting context still contains floats or not; - /// * the propagation of text decorations towards node descendants is - /// hard to do incrementally with our current representation of boxes - /// * how intrinsic content sizes are computed eagerly makes it hard - /// to update those sizes for ancestors of the node from which we - /// made an incremental update. - pub(crate) fn update( - context: &LayoutContext, - dirty_root_from_script: ServoLayoutNode<'_>, - ) -> bool { - let Some(box_tree_update) = IncrementalBoxTreeUpdate::find(dirty_root_from_script) else { - return false; - }; - box_tree_update.update_from_dirty_root(context); - true - } } fn construct_for_root_element( @@ -267,196 +228,3 @@ impl BoxTree { ) } } - -#[expect(clippy::enum_variant_names)] -enum DirtyRootBoxTreeNode { - AbsolutelyPositionedBlockLevelBox(ArcRefCell), - AbsolutelyPositionedInlineLevelBox(InlineItem, usize), - AbsolutelyPositionedFlexLevelBox(ArcRefCell), - AbsolutelyPositionedTaffyLevelBox(ArcRefCell), -} - -struct IncrementalBoxTreeUpdate<'dom> { - node: ServoLayoutNode<'dom>, - box_tree_node: DirtyRootBoxTreeNode, - primary_style: Arc, - display_inside: DisplayInside, -} - -impl<'dom> IncrementalBoxTreeUpdate<'dom> { - fn find(dirty_root_from_script: ServoLayoutNode<'dom>) -> Option { - let mut maybe_dirty_root_node = Some(dirty_root_from_script); - while let Some(dirty_root_node) = maybe_dirty_root_node { - if let Some(dirty_root) = Self::new_if_valid(dirty_root_node) { - return Some(dirty_root); - } - - maybe_dirty_root_node = dirty_root_node.parent_node(); - } - - None - } - - fn new_if_valid(potential_dirty_root_node: ServoLayoutNode<'dom>) -> Option { - if !potential_dirty_root_node.is_element() { - return None; - } - - if potential_dirty_root_node.type_id() == - LayoutNodeType::Element(LayoutElementType::HTMLBodyElement) - { - // This can require changes to the canvas background. - return None; - } - - // Don't update unstyled nodes or nodes that have pseudo-elements. - let potential_thread_safe_dirty_root_node = potential_dirty_root_node.to_threadsafe(); - let element_data = potential_thread_safe_dirty_root_node - .style_data()? - .element_data - .borrow(); - if !element_data.styles.pseudos.is_empty() { - return None; - } - - let layout_data = NodeExt::inner_layout_data(&potential_thread_safe_dirty_root_node)?; - if !layout_data.pseudo_boxes.is_empty() { - return None; - } - - let primary_style = element_data.styles.primary(); - let box_style = primary_style.get_box(); - - if !box_style.position.is_absolutely_positioned() { - return None; - } - - let display_inside = match Display::from(box_style.display) { - Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { inside, .. }) => inside, - _ => return None, - }; - - let box_tree_node = - match &*AtomicRef::filter_map(layout_data.self_box.borrow(), Option::as_ref)? { - LayoutBox::DisplayContents(..) => return None, - LayoutBox::BlockLevel(block_level_box) => match &*block_level_box.borrow() { - BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(_) - if box_style.position.is_absolutely_positioned() => - { - // If the outer type of its original display changed from block to inline, - // a block-level abspos needs to be placed in an inline formatting context, - // see [`BlockContainerBuilder::handle_absolutely_positioned_element()`]. - if box_style.original_display.outside() == DisplayOutside::Inline { - return None; - } - DirtyRootBoxTreeNode::AbsolutelyPositionedBlockLevelBox( - block_level_box.clone(), - ) - }, - _ => return None, - }, - LayoutBox::InlineLevel(inline_level_box) => { - let InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index) = - inline_level_box - else { - return None; - }; - DirtyRootBoxTreeNode::AbsolutelyPositionedInlineLevelBox( - inline_level_box.clone(), - *text_offset_index, - ) - }, - LayoutBox::FlexLevel(flex_level_box) => match &*flex_level_box.borrow() { - FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_) - if box_style.position.is_absolutely_positioned() => - { - DirtyRootBoxTreeNode::AbsolutelyPositionedFlexLevelBox( - flex_level_box.clone(), - ) - }, - _ => return None, - }, - LayoutBox::TableLevelBox(..) => return None, - LayoutBox::TaffyItemBox(taffy_level_box) => { - match &taffy_level_box.borrow().taffy_level_box { - TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(_) - if box_style.position.is_absolutely_positioned() => - { - DirtyRootBoxTreeNode::AbsolutelyPositionedTaffyLevelBox( - taffy_level_box.clone(), - ) - }, - _ => return None, - } - }, - }; - - Some(Self { - node: potential_dirty_root_node, - box_tree_node, - primary_style: primary_style.clone(), - display_inside, - }) - } - - #[servo_tracing::instrument(name = "Box Tree Update From Dirty Root", skip_all)] - fn update_from_dirty_root(&self, context: &LayoutContext) { - let node = self.node.to_threadsafe(); - let contents = Contents::for_element(node, context); - - let info = NodeAndStyleInfo::new(node, self.primary_style.clone()); - - let build_new_box = |old_parent| { - let mut out_of_flow_absolutely_positioned_box = - AbsolutelyPositionedBox::construct(context, &info, self.display_inside, contents); - if let Some(old_parent) = old_parent { - out_of_flow_absolutely_positioned_box - .context - .base - .parent_box - .replace(old_parent); - } - out_of_flow_absolutely_positioned_box - }; - - match &self.box_tree_node { - DirtyRootBoxTreeNode::AbsolutelyPositionedBlockLevelBox(block_level_box) => { - let mut block_level_box = block_level_box.borrow_mut(); - let old_parent = block_level_box.with_base(|base| base.parent_box.clone()); - *block_level_box = BlockLevelBox::OutOfFlowAbsolutelyPositionedBox( - ArcRefCell::new(build_new_box(old_parent)), - ); - }, - DirtyRootBoxTreeNode::AbsolutelyPositionedInlineLevelBox( - inline_level_box, - text_offset_index, - ) => match inline_level_box { - InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, offset_in_text) => { - let mut positioned_box = positioned_box.borrow_mut(); - let old_parent = positioned_box.context.base.parent_box.clone(); - *positioned_box = build_new_box(old_parent); - assert_eq!( - *offset_in_text, *text_offset_index, - "The offset of the dirty root shouldn't have changed" - ); - }, - _ => unreachable!("The dirty root should be absolutely positioned"), - }, - DirtyRootBoxTreeNode::AbsolutelyPositionedFlexLevelBox(flex_level_box) => { - let mut flex_level_box = flex_level_box.borrow_mut(); - let old_parent = flex_level_box.with_base(|base| base.parent_box.clone()); - *flex_level_box = FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(ArcRefCell::new( - build_new_box(old_parent), - )); - }, - DirtyRootBoxTreeNode::AbsolutelyPositionedTaffyLevelBox(taffy_level_box) => { - let mut taffy_level_box = taffy_level_box.borrow_mut(); - let old_parent = taffy_level_box.with_base(|base| base.parent_box.clone()); - taffy_level_box.taffy_level_box = - TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(ArcRefCell::new( - build_new_box(old_parent), - )); - }, - } - } -} diff --git a/components/layout/formatting_contexts.rs b/components/layout/formatting_contexts.rs index be3bb84786e..a2edd73cba1 100644 --- a/components/layout/formatting_contexts.rs +++ b/components/layout/formatting_contexts.rs @@ -26,7 +26,7 @@ use crate::replaced::ReplacedContents; use crate::sizing::{ self, ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, LazySize, }; -use crate::style_ext::{AspectRatio, DisplayInside, LayoutStyle}; +use crate::style_ext::{AspectRatio, Display, DisplayInside, LayoutStyle}; use crate::table::Table; use crate::taffy::TaffyContainer; use crate::{ @@ -41,6 +41,9 @@ pub(crate) struct IndependentFormattingContext { // Private so that code outside of this module cannot match variants. // It should go through methods instead. contents: IndependentFormattingContextContents, + /// Data that was originally propagated down to this [`IndependentFormattingContext`] + /// during creation. This is used during incremental layout. + propagated_data: PropagatedBoxTreeData, } #[derive(Debug, MallocSizeOf)] @@ -75,11 +78,45 @@ impl Baselines { } impl IndependentFormattingContext { - pub(crate) fn new(base: LayoutBoxBase, contents: IndependentFormattingContextContents) -> Self { - Self { base, contents } + pub(crate) fn new( + base: LayoutBoxBase, + contents: IndependentFormattingContextContents, + propagated_data: PropagatedBoxTreeData, + ) -> Self { + Self { + base, + contents, + propagated_data, + } } - pub fn construct( + pub(crate) fn rebuild( + &mut self, + layout_context: &LayoutContext, + node_and_style_info: &NodeAndStyleInfo, + ) { + let contents = Contents::for_element(node_and_style_info.node, layout_context); + let display = match Display::from(node_and_style_info.style.get_box().display) { + Display::None | Display::Contents => { + unreachable!("Should never try to rebuild IndependentFormattingContext with no box") + }, + Display::GeneratingBox(display) => display.used_value_for_contents(&contents), + }; + self.contents = Self::construct_contents( + layout_context, + node_and_style_info, + &mut self.base.base_fragment_info, + display.display_inside(), + contents, + self.propagated_data, + ); + + self.base.clear_fragments_and_fragment_cache(); + *self.base.cached_inline_content_size.borrow_mut() = None; + self.base.repair_style(&node_and_style_info.style); + } + + pub(crate) fn construct( context: &LayoutContext, node_and_style_info: &NodeAndStyleInfo, display_inside: DisplayInside, @@ -87,7 +124,29 @@ impl IndependentFormattingContext { propagated_data: PropagatedBoxTreeData, ) -> Self { let mut base_fragment_info: BaseFragmentInfo = node_and_style_info.into(); + let contents = Self::construct_contents( + context, + node_and_style_info, + &mut base_fragment_info, + display_inside, + contents, + propagated_data, + ); + Self { + base: LayoutBoxBase::new(base_fragment_info, node_and_style_info.style.clone()), + contents, + propagated_data, + } + } + fn construct_contents( + context: &LayoutContext, + node_and_style_info: &NodeAndStyleInfo, + base_fragment_info: &mut BaseFragmentInfo, + display_inside: DisplayInside, + contents: Contents, + propagated_data: PropagatedBoxTreeData, + ) -> IndependentFormattingContextContents { let non_replaced_contents = match contents { Contents::Replaced(contents) => { base_fragment_info.flags.insert(FragmentFlags::IS_REPLACED); @@ -116,12 +175,10 @@ impl IndependentFormattingContext { ArcRefCell::new(IndependentFormattingContext::new( widget_base, widget_contents, + propagated_data, )) }); - return Self { - base: LayoutBoxBase::new(base_fragment_info, node_and_style_info.style.clone()), - contents: IndependentFormattingContextContents::Replaced(contents, widget), - }; + return IndependentFormattingContextContents::Replaced(contents, widget); }, Contents::Widget(non_replaced_contents) => { base_fragment_info.flags.insert(FragmentFlags::IS_WIDGET); @@ -129,7 +186,8 @@ impl IndependentFormattingContext { }, Contents::NonReplaced(non_replaced_contents) => non_replaced_contents, }; - let contents = match display_inside { + + match display_inside { DisplayInside::Flow { is_list_item } | DisplayInside::FlowRoot { is_list_item } => { IndependentFormattingContextContents::Flow(BlockFormattingContext::construct( context, @@ -173,10 +231,6 @@ impl IndependentFormattingContext { propagated_data, )) }, - }; - Self { - base: LayoutBoxBase::new(base_fragment_info, node_and_style_info.style.clone()), - contents, } } diff --git a/components/layout/layout_box_base.rs b/components/layout/layout_box_base.rs index 123c3f04385..aab9ebd0fb8 100644 --- a/components/layout/layout_box_base.rs +++ b/components/layout/layout_box_base.rs @@ -92,6 +92,11 @@ impl LayoutBoxBase { self.fragments.borrow_mut().clear(); } + pub(crate) fn clear_fragments_and_fragment_cache(&self) { + self.fragments.borrow_mut().clear(); + *self.cached_layout_result.borrow_mut() = None; + } + pub(crate) fn repair_style(&mut self, new_style: &Arc) { self.style = new_style.clone(); for fragment in self.fragments.borrow_mut().iter_mut() { @@ -111,8 +116,7 @@ impl LayoutBoxBase { element_damage: LayoutDamage, damage_from_children: LayoutDamage, ) -> LayoutDamage { - self.clear_fragments(); - *self.cached_layout_result.borrow_mut() = None; + self.clear_fragments_and_fragment_cache(); if !element_damage.is_empty() || damage_from_children.contains(LayoutDamage::RECOMPUTE_INLINE_CONTENT_SIZES) diff --git a/components/layout/layout_impl.rs b/components/layout/layout_impl.rs index 4ba66b4b385..3d862058d83 100644 --- a/components/layout/layout_impl.rs +++ b/components/layout/layout_impl.rs @@ -22,8 +22,8 @@ use fonts::{FontContext, FontContextWebFontMethods, WebFontDocumentContext}; use fonts_traits::StylesheetWebFontLoadFinishedCallback; use layout_api::wrapper_traits::LayoutNode; use layout_api::{ - BoxAreaType, CSSPixelRectIterator, IFrameSizes, Layout, LayoutConfig, LayoutDamage, - LayoutFactory, OffsetParentResponse, PhysicalSides, PropertyRegistration, QueryMsg, ReflowGoal, + BoxAreaType, CSSPixelRectIterator, IFrameSizes, Layout, LayoutConfig, LayoutFactory, + OffsetParentResponse, PhysicalSides, PropertyRegistration, QueryMsg, ReflowGoal, ReflowPhasesRun, ReflowRequest, ReflowRequestRestyle, ReflowResult, RegisterPropertyError, ScrollContainerQueryFlags, ScrollContainerResponse, TrustedNodeAddress, }; @@ -93,7 +93,7 @@ use crate::query::{ process_resolved_font_style_query, process_resolved_style_request, process_scroll_container_query, }; -use crate::traversal::{RecalcStyle, compute_damage_and_repair_style}; +use crate::traversal::{RecalcStyle, compute_damage_and_rebuild_box_tree}; use crate::{BoxTree, FragmentTree}; // This mutex is necessary due to syncronisation issues between two different types of thread-local storage @@ -1106,11 +1106,26 @@ impl LayoutThread { Default::default() }; - let damage = compute_damage_and_repair_style( - &layout_context.style_context, - root_node.to_threadsafe(), - damage_from_environment, - ); + let mut box_tree = self.box_tree.borrow_mut(); + let damage = { + let box_tree = &mut *box_tree; + let mut compute_damage_and_build_box_tree = || { + compute_damage_and_rebuild_box_tree( + box_tree, + &layout_context, + dirty_root, + root_node, + damage_from_environment, + ) + }; + + if let Some(pool) = rayon_pool { + pool.install(compute_damage_and_build_box_tree) + } else { + compute_damage_and_build_box_tree() + } + }; + if damage.contains(RestyleDamage::RECALCULATE_OVERFLOW) { self.need_overflow_calculation.set(true); } @@ -1120,31 +1135,12 @@ impl LayoutThread { if damage.contains(RestyleDamage::REPAINT) { self.need_new_display_list.set(true); } - if !damage.contains(RestyleDamage::RELAYOUT) { layout_context.style_context.stylist.rule_tree().maybe_gc(); return (ReflowPhasesRun::empty(), IFrameSizes::default()); } - let mut box_tree = self.box_tree.borrow_mut(); - let box_tree = &mut *box_tree; - let layout_damage: LayoutDamage = damage.into(); - if box_tree.is_none() || layout_damage.has_box_damage() { - let mut build_box_tree = || { - if !BoxTree::update(recalc_style_traversal.context(), dirty_root) { - *box_tree = Some(Arc::new(BoxTree::construct( - recalc_style_traversal.context(), - root_node, - ))); - } - }; - if let Some(pool) = rayon_pool { - pool.install(build_box_tree) - } else { - build_box_tree() - }; - } - + let box_tree = &*box_tree; let viewport_size = self.stylist.device().au_viewport_size(); let run_layout = || { box_tree diff --git a/components/layout/lib.rs b/components/layout/lib.rs index 9d7c45e1e1c..ddf5f09e327 100644 --- a/components/layout/lib.rs +++ b/components/layout/lib.rs @@ -157,7 +157,7 @@ impl<'a> From<&'_ DefiniteContainingBlock<'a>> for ContainingBlock<'a> { /// Data that is propagated from ancestors to descendants during [`crate::flow::BoxTree`] /// construction. This allows data to flow in the reverse direction of the typical layout /// propoagation, but only during `BoxTree` construction. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, MallocSizeOf)] struct PropagatedBoxTreeData { allow_percentage_column_in_tables: bool, } diff --git a/components/layout/table/construct.rs b/components/layout/table/construct.rs index e7e36e63d98..4b2fc959a19 100644 --- a/components/layout/table/construct.rs +++ b/components/layout/table/construct.rs @@ -121,6 +121,7 @@ impl Table { let ifc = IndependentFormattingContext::new( LayoutBoxBase::new((&table_info).into(), table_style), IndependentFormattingContextContents::Table(table), + propagated_data, ); (table_info, ifc) @@ -882,7 +883,11 @@ impl<'dom> TraversalHandler<'dom> for TableBuilderTraversal<'_, 'dom> { ); let base = LayoutBoxBase::new(info.into(), info.style.clone()); ArcRefCell::new(TableCaption { - context: IndependentFormattingContext::new(base, contents), + context: IndependentFormattingContext::new( + base, + contents, + self.current_propagated_data, + ), }) }); diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs index db19f33c24d..383a0db75c4 100644 --- a/components/layout/traversal.rs +++ b/components/layout/traversal.rs @@ -3,17 +3,19 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::Cell; +use std::sync::Arc; use bitflags::Flags; use layout_api::LayoutDamage; use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode}; -use script::layout_dom::ServoThreadSafeLayoutNode; +use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode}; use style::context::{SharedStyleContext, StyleContext}; use style::data::ElementData; use style::dom::{NodeInfo, TElement, TNode}; use style::selector_parser::RestyleDamage; use style::traversal::{DomTraversal, PerLevelTraversalData, recalc_style_at}; +use crate::BoxTree; use crate::context::LayoutContext; use crate::dom::{DOMLayoutData, NodeExt}; @@ -95,16 +97,85 @@ where } #[servo_tracing::instrument(skip_all)] -pub(crate) fn compute_damage_and_repair_style( - context: &SharedStyleContext, - node: ServoThreadSafeLayoutNode<'_>, +pub(crate) fn compute_damage_and_rebuild_box_tree( + box_tree: &mut Option>, + layout_context: &LayoutContext, + dirty_root: ServoLayoutNode<'_>, + root_node: ServoLayoutNode<'_>, damage_from_environment: RestyleDamage, ) -> RestyleDamage { - compute_damage_and_repair_style_inner(context, node, damage_from_environment) + let restyle_damage = compute_damage_and_rebuild_box_tree_inner( + layout_context, + dirty_root.to_threadsafe(), + damage_from_environment, + ); + + let layout_damage: LayoutDamage = restyle_damage.into(); + if box_tree.is_none() { + *box_tree = Some(Arc::new(BoxTree::construct(layout_context, root_node))); + return restyle_damage; + } + + // There are two cases where we need to do more work: + // + // 1. Fragment tree layout needs to run again, in which case we should invalidate all + // fragments to the root of the DOM. + // 2. Box tree reconstruction needs to run at the dirty root, in which case we need to + // find an appropriate place to run box tree reconstruction and *also* invalidate all + // fragments to the root of the DOM. + if !restyle_damage.contains(RestyleDamage::RELAYOUT) { + return restyle_damage; + } + + // If the damage traversal indicated that the dirty root needs a new box, walk up the + // tree to find an appropriate place to run box tree reconstruction. + let mut needs_box_tree_rebuild = layout_damage.needs_new_box(); + + let mut damage_for_ancestors = LayoutDamage::RECOMPUTE_INLINE_CONTENT_SIZES; + let mut maybe_parent_node = dirty_root.traversal_parent(); + while let Some(parent_node) = maybe_parent_node { + let threadsafe_parent_node = parent_node.as_node().to_threadsafe(); + + // If we need box tree reconstruction, try it here. + if needs_box_tree_rebuild && + threadsafe_parent_node + .rebuild_box_tree_from_independent_formatting_context(layout_context) + { + needs_box_tree_rebuild = false; + } + + if needs_box_tree_rebuild { + // We have not yet found a place to run box tree reconstruction, so clear this + // node's boxes to ensure that they are invalidated for the reconstruction we + // will run later. + threadsafe_parent_node.unset_all_boxes(); + } else { + // Reconstruction has already run or was not necessary, so we just need to + // ensure that fragment tree layout does not reuse any cached fragments. + let new_damage_for_ancestors = Cell::new(LayoutDamage::empty()); + threadsafe_parent_node.with_layout_box_base_including_pseudos(|base| { + new_damage_for_ancestors.set( + new_damage_for_ancestors.get() | + base.add_damage(Default::default(), damage_for_ancestors), + ); + }); + damage_for_ancestors = new_damage_for_ancestors.get(); + } + + maybe_parent_node = parent_node.traversal_parent(); + } + + // We could not find a place in the middle of the tree to run box tree reconstruction, + // so just rebuild the whole tree. + if needs_box_tree_rebuild { + *box_tree = Some(Arc::new(BoxTree::construct(layout_context, root_node))); + } + + restyle_damage } -pub(crate) fn compute_damage_and_repair_style_inner( - context: &SharedStyleContext, +pub(crate) fn compute_damage_and_rebuild_box_tree_inner( + layout_context: &LayoutContext, node: ServoThreadSafeLayoutNode<'_>, damage_from_parent: RestyleDamage, ) -> RestyleDamage { @@ -132,11 +203,11 @@ pub(crate) fn compute_damage_and_repair_style_inner( // box damage. let mut damage_for_children = element_and_parent_damage; damage_for_children.truncate(); - let rebuild_children = element_damage.contains(LayoutDamage::rebuild_box_tree()) || - (damage_from_parent.contains(LayoutDamage::rebuild_box_tree()) && + let rebuild_children = element_damage.contains(LayoutDamage::box_damage()) || + (damage_from_parent.contains(LayoutDamage::box_damage()) && !node.isolates_damage_for_damage_propagation()); if rebuild_children { - damage_for_children.insert(LayoutDamage::rebuild_box_tree()); + damage_for_children.insert(LayoutDamage::box_damage()); } else if damage_for_children.contains(RestyleDamage::RELAYOUT) && !element_damage.contains(RestyleDamage::RELAYOUT) && node.isolates_damage_for_damage_propagation() @@ -150,8 +221,11 @@ pub(crate) fn compute_damage_and_repair_style_inner( let mut damage_from_children = RestyleDamage::empty(); for child in node.children() { if child.is_element() { - damage_from_children |= - compute_damage_and_repair_style_inner(context, child, damage_for_children); + damage_from_children |= compute_damage_and_rebuild_box_tree_inner( + layout_context, + child, + damage_for_children, + ); } } @@ -160,17 +234,32 @@ pub(crate) fn compute_damage_and_repair_style_inner( let mut layout_damage_for_parent = element_and_parent_damage | (damage_from_children & RestyleDamage::RELAYOUT); - if damage_from_children.contains(LayoutDamage::recollect_box_tree_children()) || - element_and_parent_damage.contains(LayoutDamage::recollect_box_tree_children()) - { - // In this case, this node, an ancestor, or a descendant needs to be completely - // rebuilt. That means that this box is no longer valid and also needs to be rebuilt - // (perhaps some of its children do not though). In this case, unset all existing - // boxes for the node and ensure that the appropriate rebuild-type damage propagates - // up the tree. - node.unset_all_boxes(); - layout_damage_for_parent - .insert(LayoutDamage::recollect_box_tree_children() | RestyleDamage::RELAYOUT); + let element_or_ancestors_need_rebuild = + element_and_parent_damage.contains(LayoutDamage::descendant_has_box_damage()); + let descendant_needs_rebuild = + damage_from_children.contains(LayoutDamage::descendant_has_box_damage()); + if element_or_ancestors_need_rebuild || descendant_needs_rebuild { + if element_or_ancestors_need_rebuild || + !node.rebuild_box_tree_from_independent_formatting_context(layout_context) + { + // In this case: + // - this node or an ancestor needs to be completely rebuilt. + // - a descendant needs to be rebuilt, but we are still propagating the rebuild + // damage to an independent formatting context. + // + // This means that this box is no longer valid and also needs to be rebuilt + // (perhaps some of its descendants do not though). In this case, unset all existing + // boxes for the node and ensure that the appropriate rebuild-type damage + // propagates up the tree. + node.unset_all_boxes(); + layout_damage_for_parent + .insert(LayoutDamage::descendant_has_box_damage() | RestyleDamage::RELAYOUT); + } else { + // In this case, we have rebuilt the box tree from this point and we do not + // have to propagate rebuild box tree damage up the tree any further. + layout_damage_for_parent + .insert(RestyleDamage::RELAYOUT | LayoutDamage::recompute_inline_content_sizes()); + } } else { // In this case, this node's boxes are preserved! It's possible that we still need // to run fragment tree layout in this subtree due to an ancestor, this node, or a @@ -191,7 +280,7 @@ pub(crate) fn compute_damage_and_repair_style_inner( // update any preserved layout data structures' style references, if *this* // element's style has changed. if !element_damage.is_empty() { - node.repair_style(context); + node.repair_style(&layout_context.style_context); } } diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index bcba2c010cc..ead85cf5d81 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -379,7 +379,7 @@ impl Element { doc.note_node_with_dirty_descendants(self.upcast()); restyle .damage - .insert(LayoutDamage::recollect_box_tree_children()); + .insert(LayoutDamage::descendant_has_box_damage()); }, NodeDamage::Other => { doc.note_node_with_dirty_descendants(self.upcast()); diff --git a/components/script/layout_dom/element.rs b/components/script/layout_dom/element.rs index c9c0a971b57..142ae8b6a82 100644 --- a/components/script/layout_dom/element.rs +++ b/components/script/layout_dom/element.rs @@ -731,9 +731,9 @@ impl<'dom> style::dom::TElement for ServoLayoutElement<'dom> { }; if box_tree_needs_rebuild() { - RestyleDamage::from_bits_retain(LayoutDamage::REBUILD_BOX.bits()) + RestyleDamage::from_bits_retain(LayoutDamage::BOX_DAMAGE.bits()) } else if text_shaping_needs_recollect() { - RestyleDamage::from_bits_retain(LayoutDamage::RECOLLECT_BOX_TREE_CHILDREN.bits()) + RestyleDamage::from_bits_retain(LayoutDamage::DESCENDANT_HAS_BOX_DAMAGE.bits()) } else { // This element needs to be laid out again, but does not have any damage to // its box. In the future, we will distinguish between types of damage to the diff --git a/components/shared/layout/layout_damage.rs b/components/shared/layout/layout_damage.rs index ef03c176a26..c54bef02382 100644 --- a/components/shared/layout/layout_damage.rs +++ b/components/shared/layout/layout_damage.rs @@ -10,33 +10,34 @@ bitflags! { /// of `RestyleDamage` from stylo, which only uses the 4 lower bits. #[derive(Clone, Copy, Default, Eq, PartialEq)] pub struct LayoutDamage: u16 { - /// Recollect the box children for this element, because some of the them will be - /// rebuilt. - const RECOLLECT_BOX_TREE_CHILDREN = 0b0111_1111_1111 << 4; /// Clear the cached inline content sizes and recompute them during the next layout. const RECOMPUTE_INLINE_CONTENT_SIZES = 0b1000_0000_0000 << 4; - /// Rebuild the entire box for this element, which means that every part of layout - /// needs to happen again. - const REBUILD_BOX = 0b1111_1111_1111 << 4; + /// Rebuild this box and all of its ancestors. Do not rebuild any children. This + /// is used when a box's content (such as text content) changes or a descendant + /// has box damage ([`Self::BOX_DAMAGE`]). + const DESCENDANT_HAS_BOX_DAMAGE = 0b0111_1111_1111 << 4; + /// Rebuild this box, all of its ancestors and all of its descendants. This is the + /// most a box can be damaged. + const BOX_DAMAGE = 0b1111_1111_1111 << 4; } } impl LayoutDamage { - pub fn recollect_box_tree_children() -> RestyleDamage { - RestyleDamage::from_bits_retain(LayoutDamage::RECOLLECT_BOX_TREE_CHILDREN.bits()) + pub fn descendant_has_box_damage() -> RestyleDamage { + RestyleDamage::from_bits_retain(LayoutDamage::DESCENDANT_HAS_BOX_DAMAGE.bits()) + } + + pub fn box_damage() -> RestyleDamage { + RestyleDamage::from_bits_retain(LayoutDamage::BOX_DAMAGE.bits()) + } + + pub fn needs_new_box(&self) -> bool { + self.contains(Self::DESCENDANT_HAS_BOX_DAMAGE) } pub fn recompute_inline_content_sizes() -> RestyleDamage { RestyleDamage::from_bits_retain(LayoutDamage::RECOMPUTE_INLINE_CONTENT_SIZES.bits()) } - - pub fn rebuild_box_tree() -> RestyleDamage { - RestyleDamage::from_bits_retain(LayoutDamage::REBUILD_BOX.bits()) - } - - pub fn has_box_damage(&self) -> bool { - self.intersects(Self::REBUILD_BOX) - } } impl From for LayoutDamage { @@ -53,9 +54,9 @@ impl From for RestyleDamage { impl std::fmt::Debug for LayoutDamage { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if self.contains(Self::REBUILD_BOX) { + if self.contains(Self::BOX_DAMAGE) { f.write_str("REBUILD_BOX") - } else if self.contains(Self::RECOLLECT_BOX_TREE_CHILDREN) { + } else if self.contains(Self::DESCENDANT_HAS_BOX_DAMAGE) { f.write_str("RECOLLECT_BOX_TREE_CHILDREN") } else { f.write_str("EMPTY")