From 856ffa6ecb12153011ce9358f4eddbdfe4d2e782 Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Wed, 21 May 2025 17:52:11 +0200 Subject: [PATCH] layout: When there is no restyle damage, do not re-layout (#37048) When the computed restyle damage is empty, do not do a layout. Instead, just rebuild the display list. In the future, even that can be omitted, but that requires changes to the compositor. These kind of relayouts commonly happen when the cursor is moving around the page and no style rules cause changes to :hover. Testing: This is covered existing WPT tests and should only have performance impacts. Unfortunately there are currently no performance tests. Signed-off-by: Martin Robinson Co-authored-by: Oriol Brufau --- components/layout/layout_impl.rs | 2 +- components/layout/traversal.rs | 12 +++++++----- components/script/dom/htmlcanvaselement.rs | 8 ++++++-- components/script/dom/htmlinputelement.rs | 4 ++++ components/script/dom/htmltablecellelement.rs | 16 ++++++++++++++++ components/script/dom/htmltablecolelement.rs | 14 +++++++++++++- 6 files changed, 47 insertions(+), 9 deletions(-) diff --git a/components/layout/layout_impl.rs b/components/layout/layout_impl.rs index b490b4a0506..8162ed1dd0b 100644 --- a/components/layout/layout_impl.rs +++ b/components/layout/layout_impl.rs @@ -773,7 +773,7 @@ impl LayoutThread { let root_node = root_element.as_node(); let damage = compute_damage_and_repair_style(layout_context.shared_context(), root_node); - if damage == RestyleDamage::REPAINT { + if damage.is_empty() || damage == RestyleDamage::REPAINT { layout_context.style_context.stylist.rule_tree().maybe_gc(); return false; } diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs index d05deb24bfa..faf25dc170d 100644 --- a/components/layout/traversal.rs +++ b/components/layout/traversal.rs @@ -105,7 +105,9 @@ pub(crate) fn compute_damage_and_repair_style_inner( parent_restyle_damage: RestyleDamage, ) -> RestyleDamage { let original_damage; - let damage = { + let damage; + + { let mut element_data = node .style_data() .expect("Should not run `compute_damage` before styling.") @@ -113,14 +115,14 @@ pub(crate) fn compute_damage_and_repair_style_inner( .borrow_mut(); original_damage = std::mem::take(&mut element_data.damage); + damage = original_damage | parent_restyle_damage; + if let Some(ref style) = element_data.styles.primary { if style.get_box().display == Display::None { - return parent_restyle_damage; + return damage; } } - - original_damage | parent_restyle_damage - }; + } let mut propagated_damage = damage; for child in iter_child_nodes(node) { diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs index 27da9f2b537..499e91c127b 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -27,6 +27,7 @@ use servo_media::streams::registry::MediaStreamId; use snapshot::Snapshot; use style::attr::AttrValue; +use super::node::NodeDamage; pub(crate) use crate::canvas_context::*; use crate::conversions::Convert; use crate::dom::attr::Attr; @@ -687,8 +688,11 @@ impl VirtualMethods for HTMLCanvasElement { .unwrap() .attribute_mutated(attr, mutation, can_gc); match attr.local_name() { - &local_name!("width") | &local_name!("height") => self.recreate_contexts_after_resize(), - _ => (), + &local_name!("width") | &local_name!("height") => { + self.recreate_contexts_after_resize(); + self.upcast::().dirty(NodeDamage::OtherNodeDamage); + }, + _ => {}, }; } diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 3c78ba67772..c6a3e2227f5 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -520,6 +520,7 @@ impl HTMLInputElement { let mut value = textinput.single_line_content().clone(); self.sanitize_value(&mut value); textinput.set_content(value); + self.upcast::().dirty(NodeDamage::OtherNodeDamage); } fn does_minmaxlength_apply(&self) -> bool { @@ -2668,6 +2669,7 @@ impl VirtualMethods for HTMLInputElement { let mut value = textinput.single_line_content().clone(); self.sanitize_value(&mut value); textinput.set_content(value); + self.upcast::().dirty(NodeDamage::OtherNodeDamage); // Steps 7-9 if !previously_selectable && self.selection_api_applies() { @@ -2695,6 +2697,8 @@ impl VirtualMethods for HTMLInputElement { self.sanitize_value(&mut value); self.textinput.borrow_mut().set_content(value); self.update_placeholder_shown_state(); + + self.upcast::().dirty(NodeDamage::OtherNodeDamage); }, local_name!("name") if self.input_type() == InputType::Radio => { self.radio_group_updated( diff --git a/components/script/dom/htmltablecellelement.rs b/components/script/dom/htmltablecellelement.rs index 8b553923230..78f00132580 100644 --- a/components/script/dom/htmltablecellelement.rs +++ b/components/script/dom/htmltablecellelement.rs @@ -9,6 +9,9 @@ use style::attr::{AttrValue, LengthOrPercentageOrAuto}; use style::color::AbsoluteColor; use style::context::QuirksMode; +use super::attr::Attr; +use super::element::AttributeMutation; +use super::node::NodeDamage; use crate::dom::bindings::codegen::Bindings::HTMLTableCellElementBinding::HTMLTableCellElementMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use crate::dom::bindings::inheritance::Castable; @@ -174,6 +177,19 @@ impl VirtualMethods for HTMLTableCellElement { Some(self.upcast::() as &dyn VirtualMethods) } + fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) { + if let Some(super_type) = self.super_type() { + super_type.attribute_mutated(attr, mutation, can_gc); + } + + if matches!(*attr.local_name(), local_name!("colspan")) { + self.upcast::().dirty(NodeDamage::OtherNodeDamage); + } + if matches!(*attr.local_name(), local_name!("rowspan")) { + self.upcast::().dirty(NodeDamage::OtherNodeDamage); + } + } + fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue { match *local_name { local_name!("colspan") => { diff --git a/components/script/dom/htmltablecolelement.rs b/components/script/dom/htmltablecolelement.rs index 9e8eecf1147..70355f274fc 100644 --- a/components/script/dom/htmltablecolelement.rs +++ b/components/script/dom/htmltablecolelement.rs @@ -7,8 +7,10 @@ use html5ever::{LocalName, Prefix, local_name, ns}; use js::rust::HandleObject; use style::attr::{AttrValue, LengthOrPercentageOrAuto}; +use super::attr::Attr; use super::bindings::root::LayoutDom; -use super::element::Element; +use super::element::{AttributeMutation, Element}; +use super::node::NodeDamage; use crate::dom::bindings::codegen::Bindings::HTMLTableColElementBinding::HTMLTableColElementMethods; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::DomRoot; @@ -93,6 +95,16 @@ impl VirtualMethods for HTMLTableColElement { Some(self.upcast::() as &dyn VirtualMethods) } + fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) { + if let Some(super_type) = self.super_type() { + super_type.attribute_mutated(attr, mutation, can_gc); + } + + if matches!(*attr.local_name(), local_name!("span")) { + self.upcast::().dirty(NodeDamage::OtherNodeDamage); + } + } + fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue { match *local_name { local_name!("span") => {