diff --git a/components/layout/dom.rs b/components/layout/dom.rs index 87ce8a03a1e..56daa77dc21 100644 --- a/components/layout/dom.rs +++ b/components/layout/dom.rs @@ -12,7 +12,7 @@ use layout_api::wrapper_traits::{ LayoutDataTrait, LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode, }; use layout_api::{ - GenericLayoutDataTrait, LayoutElementType, LayoutNodeType as ScriptLayoutNodeType, + GenericLayoutDataTrait, LayoutDamage, LayoutElementType, LayoutNodeType as ScriptLayoutNodeType, }; use malloc_size_of_derive::MallocSizeOf; use net_traits::image_cache::Image; @@ -20,7 +20,7 @@ use script::layout_dom::ServoLayoutNode; use servo_arc::Arc as ServoArc; use style::context::SharedStyleContext; use style::properties::ComputedValues; -use style::selector_parser::PseudoElement; +use style::selector_parser::{PseudoElement, RestyleDamage}; use crate::cell::ArcRefCell; use crate::flexbox::FlexLevelBox; @@ -160,7 +160,6 @@ pub struct BoxSlot<'dom> { /// A mutable reference to a `LayoutBox` stored in a DOM element. impl BoxSlot<'_> { pub(crate) fn new(slot: ArcRefCell>) -> Self { - *slot.borrow_mut() = None; let slot = Some(slot); Self { slot, @@ -216,6 +215,7 @@ pub(crate) trait NodeExt<'dom> { fn invalidate_cached_fragment(&self); fn repair_style(&self, context: &SharedStyleContext); + fn take_restyle_damage(&self) -> LayoutDamage; } impl<'dom> NodeExt<'dom> for ServoLayoutNode<'dom> { @@ -404,4 +404,12 @@ impl<'dom> NodeExt<'dom> for ServoLayoutNode<'dom> { } } } + + fn take_restyle_damage(&self) -> LayoutDamage { + let damage = self + .style_data() + .map(|style_data| std::mem::take(&mut style_data.element_data.borrow_mut().damage)) + .unwrap_or_else(RestyleDamage::reconstruct); + LayoutDamage::from_bits_retain(damage.bits()) + } } diff --git a/components/layout/dom_traversal.rs b/components/layout/dom_traversal.rs index 0c74315b059..4b2dfbfaa78 100644 --- a/components/layout/dom_traversal.rs +++ b/components/layout/dom_traversal.rs @@ -8,7 +8,7 @@ use std::iter::FusedIterator; use fonts::ByteIndex; use html5ever::{LocalName, local_name}; use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode}; -use layout_api::{LayoutElementType, LayoutNodeType}; +use layout_api::{LayoutDamage, LayoutElementType, LayoutNodeType}; use range::Range; use script::layout_dom::ServoLayoutNode; use selectors::Element as SelectorsElement; @@ -34,26 +34,20 @@ pub(crate) struct NodeAndStyleInfo<'dom> { pub node: ServoLayoutNode<'dom>, pub pseudo_element_type: Option, pub style: ServoArc, + pub damage: LayoutDamage, } impl<'dom> NodeAndStyleInfo<'dom> { - fn new_with_pseudo( + pub(crate) fn new( node: ServoLayoutNode<'dom>, - pseudo_element_type: PseudoElement, style: ServoArc, + damage: LayoutDamage, ) -> Self { - Self { - node, - pseudo_element_type: Some(pseudo_element_type), - style, - } - } - - pub(crate) fn new(node: ServoLayoutNode<'dom>, style: ServoArc) -> Self { Self { node, pseudo_element_type: None, style, + damage, } } @@ -76,6 +70,7 @@ impl<'dom> NodeAndStyleInfo<'dom> { node: self.node, pseudo_element_type: Some(pseudo_element_type), style, + damage: self.damage, }) } @@ -193,16 +188,14 @@ pub(super) trait TraversalHandler<'dom> { } fn traverse_children_of<'dom>( - parent_element: ServoLayoutNode<'dom>, + parent_element_info: &NodeAndStyleInfo<'dom>, context: &LayoutContext, handler: &mut impl TraversalHandler<'dom>, ) { - traverse_eager_pseudo_element(PseudoElement::Before, parent_element, context, handler); + traverse_eager_pseudo_element(PseudoElement::Before, parent_element_info, context, handler); - if parent_element.is_text_input() { - let info = - NodeAndStyleInfo::new(parent_element, parent_element.style(&context.style_context)); - let node_text_content = parent_element.to_threadsafe().node_text_content(); + if parent_element_info.node.is_text_input() { + let node_text_content = parent_element_info.node.to_threadsafe().node_text_content(); if node_text_content.is_empty() { // The addition of zero-width space here forces the text input to have an inline formatting // context that might otherwise be trimmed if there's no text. This is important to ensure @@ -211,14 +204,18 @@ fn traverse_children_of<'dom>( // // This is also used to ensure that the caret will still be rendered when the input is empty. // TODO: Is there a less hacky way to do this? - handler.handle_text(&info, "\u{200B}".into()); + handler.handle_text(parent_element_info, "\u{200B}".into()); } else { - handler.handle_text(&info, node_text_content); + handler.handle_text(parent_element_info, node_text_content); } } else { - for child in iter_child_nodes(parent_element) { + for child in iter_child_nodes(parent_element_info.node) { if child.is_text_node() { - let info = NodeAndStyleInfo::new(child, child.style(&context.style_context)); + let info = NodeAndStyleInfo::new( + child, + child.style(&context.style_context), + child.take_restyle_damage(), + ); handler.handle_text(&info, child.to_threadsafe().node_text_content()); } else if child.is_element() { traverse_element(child, context, handler); @@ -226,7 +223,7 @@ fn traverse_children_of<'dom>( } } - traverse_eager_pseudo_element(PseudoElement::After, parent_element, context, handler); + traverse_eager_pseudo_element(PseudoElement::After, parent_element_info, context, handler); } fn traverse_element<'dom>( @@ -240,7 +237,10 @@ fn traverse_element<'dom>( let replaced = ReplacedContents::for_element(element, context); let style = element.style(&context.style_context); - match Display::from(style.get_box().display) { + let damage = element.take_restyle_damage(); + let info = NodeAndStyleInfo::new(element, style, damage); + + match Display::from(info.style.get_box().display) { Display::None => element.unset_all_boxes(), Display::Contents => { if replaced.is_some() { @@ -248,14 +248,13 @@ fn traverse_element<'dom>( // element.unset_all_boxes() } else { - let shared_inline_styles: SharedInlineStyles = - (&NodeAndStyleInfo::new(element, style)).into(); + let shared_inline_styles: SharedInlineStyles = (&info).into(); element .element_box_slot() .set(LayoutBox::DisplayContents(shared_inline_styles.clone())); handler.enter_display_contents(shared_inline_styles); - traverse_children_of(element, context, handler); + traverse_children_of(&info, context, handler); handler.leave_display_contents(); } }, @@ -274,7 +273,6 @@ fn traverse_element<'dom>( }; let display = display.used_value_for_contents(&contents); let box_slot = element.element_box_slot(); - let info = NodeAndStyleInfo::new(element, style); handler.handle_element(&info, display, contents, box_slot); }, } @@ -282,45 +280,45 @@ fn traverse_element<'dom>( fn traverse_eager_pseudo_element<'dom>( pseudo_element_type: PseudoElement, - node: ServoLayoutNode<'dom>, + node_info: &NodeAndStyleInfo<'dom>, context: &LayoutContext, handler: &mut impl TraversalHandler<'dom>, ) { assert!(pseudo_element_type.is_eager()); // First clear any old contents from the node. - node.unset_pseudo_element_box(pseudo_element_type); + node_info.node.unset_pseudo_element_box(pseudo_element_type); - let Some(element) = node.to_threadsafe().as_element() else { + // If this node doesn't have this eager pseudo-element, exit early. This depends on + // the style applied to the element. + let Some(pseudo_element_info) = node_info.pseudo(context, pseudo_element_type) else { return; }; - let Some(pseudo_element) = element.with_pseudo(pseudo_element_type) else { - return; - }; - - let style = pseudo_element.style(&context.style_context); - if style.ineffective_content_property() { + if pseudo_element_info.style.ineffective_content_property() { return; } - let info = NodeAndStyleInfo::new_with_pseudo(node, pseudo_element_type, style); - match Display::from(info.style.get_box().display) { + match Display::from(pseudo_element_info.style.get_box().display) { Display::None => {}, Display::Contents => { - let items = generate_pseudo_element_content(&info.style, node, context); - let box_slot = node.pseudo_element_box_slot(pseudo_element_type); - let shared_inline_styles: SharedInlineStyles = (&info).into(); + let items = generate_pseudo_element_content(&pseudo_element_info, context); + let box_slot = pseudo_element_info + .node + .pseudo_element_box_slot(pseudo_element_type); + let shared_inline_styles: SharedInlineStyles = (&pseudo_element_info).into(); box_slot.set(LayoutBox::DisplayContents(shared_inline_styles.clone())); handler.enter_display_contents(shared_inline_styles); - traverse_pseudo_element_contents(&info, context, handler, items); + traverse_pseudo_element_contents(&pseudo_element_info, context, handler, items); handler.leave_display_contents(); }, Display::GeneratingBox(display) => { - let items = generate_pseudo_element_content(&info.style, node, context); - let box_slot = node.pseudo_element_box_slot(pseudo_element_type); + let items = generate_pseudo_element_content(&pseudo_element_info, context); + let box_slot = pseudo_element_info + .node + .pseudo_element_box_slot(pseudo_element_type); let contents = NonReplacedContents::OfPseudoElement(items).into(); - handler.handle_element(&info, display, contents, box_slot); + handler.handle_element(&pseudo_element_info, display, contents, box_slot); }, } } @@ -385,7 +383,7 @@ impl NonReplacedContents { ) { match self { NonReplacedContents::OfElement | NonReplacedContents::OfTextControl => { - traverse_children_of(info.node, context, handler) + traverse_children_of(info, context, handler) }, NonReplacedContents::OfPseudoElement(items) => { traverse_pseudo_element_contents(info, context, handler, items) @@ -407,11 +405,10 @@ where /// fn generate_pseudo_element_content( - pseudo_element_style: &ComputedValues, - element: ServoLayoutNode<'_>, + pseudo_element_info: &NodeAndStyleInfo, context: &LayoutContext, ) -> Vec { - match &pseudo_element_style.get_counters().content { + match &pseudo_element_info.style.get_counters().content { Content::Items(items) => { let mut vec = vec![]; for item in items.items.iter() { @@ -420,7 +417,8 @@ fn generate_pseudo_element_content( vec.push(PseudoElementContentItem::Text(s.to_string())); }, ContentItem::Attr(attr) => { - let element = element + let element = pseudo_element_info + .node .to_threadsafe() .as_element() .expect("Expected an element"); @@ -452,14 +450,14 @@ fn generate_pseudo_element_content( }, ContentItem::Image(image) => { if let Some(replaced_content) = - ReplacedContents::from_image(element, context, image) + ReplacedContents::from_image(pseudo_element_info.node, context, image) { vec.push(PseudoElementContentItem::Replaced(replaced_content)); } }, ContentItem::OpenQuote | ContentItem::CloseQuote => { // TODO(xiaochengh): calculate quote depth - let maybe_quote = match &pseudo_element_style.get_list().quotes { + let maybe_quote = match &pseudo_element_info.style.get_list().quotes { Quotes::QuoteList(quote_list) => { quote_list.0.first().map(|quote_pair| { get_quote_from_pair( @@ -470,7 +468,7 @@ fn generate_pseudo_element_content( }) }, Quotes::Auto => { - let lang = &pseudo_element_style.get_font()._x_lang; + let lang = &pseudo_element_info.style.get_font()._x_lang; let quotes = quotes_for_lang(lang.0.as_ref(), 0); Some(get_quote_from_pair(item, "es.opening, "es.closing)) }, diff --git a/components/layout/flow/construct.rs b/components/layout/flow/construct.rs index 830ab080bf1..71dbdcbc19b 100644 --- a/components/layout/flow/construct.rs +++ b/components/layout/flow/construct.rs @@ -689,6 +689,22 @@ impl<'dom> BlockContainerBuilder<'dom, '_> { impl BlockLevelJob<'_> { fn finish(self, context: &LayoutContext) -> ArcRefCell { let info = &self.info; + + // If this `BlockLevelBox` is undamaged and it has been laid out before, reuse + // the old one, while being sure to clear the layout cache. + if !info.damage.has_box_damage() { + if let Some(block_level_box) = match self.box_slot.slot.as_ref() { + Some(box_slot) => match &*box_slot.borrow() { + Some(LayoutBox::BlockLevel(block_level_box)) => Some(block_level_box.clone()), + _ => None, + }, + None => None, + } { + block_level_box.borrow().invalidate_cached_fragment(); + return block_level_box; + } + } + let block_level_box = match self.kind { BlockLevelCreator::SameFormattingContextBlock(intermediate_block_container) => { let contents = intermediate_block_container.finish(context, info); diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs index 72db9db8fea..e34d09d44c1 100644 --- a/components/layout/flow/root.rs +++ b/components/layout/flow/root.rs @@ -140,7 +140,11 @@ fn construct_for_root_element( context: &LayoutContext, root_element: ServoLayoutNode<'_>, ) -> Vec> { - let info = NodeAndStyleInfo::new(root_element, root_element.style(&context.style_context)); + let info = NodeAndStyleInfo::new( + root_element, + root_element.style(&context.style_context), + root_element.take_restyle_damage(), + ); let box_style = info.style.get_box(); let display_inside = match Display::from(box_style.display) { @@ -378,7 +382,13 @@ impl<'dom> IncrementalBoxTreeUpdate<'dom> { fn update_from_dirty_root(&self, context: &LayoutContext) { let contents = ReplacedContents::for_element(self.node, context) .map_or_else(|| NonReplacedContents::OfElement.into(), Contents::Replaced); - let info = NodeAndStyleInfo::new(self.node, self.primary_style.clone()); + + let info = NodeAndStyleInfo::new( + self.node, + self.primary_style.clone(), + self.node.take_restyle_damage(), + ); + let out_of_flow_absolutely_positioned_box = ArcRefCell::new( AbsolutelyPositionedBox::construct(context, &info, self.display_inside, contents), ); @@ -428,6 +438,14 @@ impl<'dom> IncrementalBoxTreeUpdate<'dom> { let mut invalidate_start_point = self.node; while let Some(parent_node) = invalidate_start_point.parent_node() { parent_node.invalidate_cached_fragment(); + + // Box tree reconstruction doesn't need to involve these ancestors, so their + // damage isn't useful for us. + // + // TODO: This isn't going to be good enough for incremental fragment tree + // reconstruction, as fragment tree damage might extend further up the tree. + parent_node.take_restyle_damage(); + invalidate_start_point = parent_node; } } diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs index d65afbe093f..ff2a3c155d5 100644 --- a/components/layout/traversal.rs +++ b/components/layout/traversal.rs @@ -2,6 +2,7 @@ * 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 layout_api::LayoutDamage; use layout_api::wrapper_traits::LayoutNode; use script::layout_dom::ServoLayoutNode; use style::context::{SharedStyleContext, StyleContext}; @@ -103,39 +104,48 @@ pub(crate) fn compute_damage_and_repair_style( pub(crate) fn compute_damage_and_repair_style_inner( context: &SharedStyleContext, node: ServoLayoutNode<'_>, - parent_restyle_damage: RestyleDamage, + damage_from_parent: RestyleDamage, ) -> RestyleDamage { - let element_damage; + let mut element_damage; + let element_data = &node + .style_data() + .expect("Should not run `compute_damage` before styling.") + .element_data; { - let mut element_data = node - .style_data() - .expect("Should not run `compute_damage` before styling.") - .element_data - .borrow_mut(); - - element_damage = std::mem::take(&mut element_data.damage); + let mut element_data = element_data.borrow_mut(); + element_data.damage.insert(damage_from_parent); + element_damage = element_data.damage; if let Some(ref style) = element_data.styles.primary { if style.get_box().display == Display::None { - return element_damage | parent_restyle_damage; + return element_damage; } } } - let element_and_parent_damage = element_damage | parent_restyle_damage; + // If we are reconstructing this node, then all of the children should be reconstructed as well. + let damage_for_children = element_damage | damage_from_parent; let mut damage_from_children = RestyleDamage::empty(); for child in iter_child_nodes(node) { if child.is_element() { damage_from_children |= - compute_damage_and_repair_style_inner(context, child, element_and_parent_damage); + compute_damage_and_repair_style_inner(context, child, damage_for_children); } } - let damage_for_parent = damage_from_children | element_and_parent_damage; - if !damage_for_parent.contains(RestyleDamage::RELAYOUT) && !element_damage.is_empty() { + if element_damage != RestyleDamage::reconstruct() && !element_damage.is_empty() { node.repair_style(context); } - damage_for_parent + // If one of our children needed to be reconstructed, we need to recollect children + // during box tree construction. + if damage_from_children.contains(LayoutDamage::recollect_box_tree_children()) { + element_damage.insert(LayoutDamage::recollect_box_tree_children()); + element_data.borrow_mut().damage.insert(element_damage); + } + + // Only propagate up layout phases from children, as other types of damage are + // incorporated into `element_damage` above. + element_damage | (damage_from_children & RestyleDamage::RELAYOUT) } diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 03fd4a14210..1488cdbd351 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -936,7 +936,7 @@ impl Document { // FIXME(emilio): This is very inefficient, ideally the flag above would // be enough and incremental layout could figure out from there. - node.dirty(NodeDamage::Other); + node.dirty(NodeDamage::ContentOrHeritage); } /// Remove any existing association between the provided id and any elements in this document. diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index c8e8c51e402..881f8c04edd 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -23,6 +23,7 @@ use html5ever::{LocalName, Namespace, Prefix, QualName, local_name, namespace_pr use js::jsapi::Heap; use js::jsval::JSVal; use js::rust::HandleObject; +use layout_api::LayoutDamage; use net_traits::ReferrerPolicy; use net_traits::request::CorsSettings; use selectors::Element as SelectorsElement; @@ -358,9 +359,18 @@ impl Element { // NodeStyleDamaged, but I'm preserving existing behavior. restyle.hint.insert(RestyleHint::RESTYLE_SELF); - if damage == NodeDamage::Other { - doc.note_node_with_dirty_descendants(self.upcast()); - restyle.damage = RestyleDamage::reconstruct(); + match damage { + NodeDamage::Style => {}, + NodeDamage::ContentOrHeritage => { + doc.note_node_with_dirty_descendants(self.upcast()); + restyle + .damage + .insert(LayoutDamage::recollect_box_tree_children()); + }, + NodeDamage::Other => { + doc.note_node_with_dirty_descendants(self.upcast()); + restyle.damage.insert(RestyleDamage::reconstruct()); + }, } } diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index a176cbd86e5..11d06386cdb 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -3927,6 +3927,9 @@ impl VirtualMethods for Node { pub(crate) enum NodeDamage { /// The node's `style` attribute changed. Style, + /// The node's content or heritage changed, such as the addition or removal of + /// children. + ContentOrHeritage, /// Other parts of a node changed; attributes, text content, etc. Other, } diff --git a/components/shared/layout/layout_damage.rs b/components/shared/layout/layout_damage.rs index 0b1ebe7931c..559177fd378 100644 --- a/components/shared/layout/layout_damage.rs +++ b/components/shared/layout/layout_damage.rs @@ -3,14 +3,29 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use bitflags::bitflags; +use style::selector_parser::RestyleDamage; bitflags! { /// Individual layout actions that may be necessary after restyling. This is an extension /// of `RestyleDamage` from stylo, which only uses the 4 lower bits. - #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[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 = 0b011111111111 << 4; /// Rebuild the entire box for this element, which means that every part of layout /// needs to happena again. const REBUILD_BOX = 0b111111111111 << 4; } } + +impl LayoutDamage { + pub fn recollect_box_tree_children() -> RestyleDamage { + RestyleDamage::from_bits_retain(LayoutDamage::RECOLLECT_BOX_TREE_CHILDREN.bits()) | + RestyleDamage::RELAYOUT + } + + pub fn has_box_damage(&self) -> bool { + self.intersects(Self::REBUILD_BOX) + } +}