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 <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Martin Robinson 2025-05-21 17:52:11 +02:00 committed by GitHub
parent 859a0ffbd5
commit 856ffa6ecb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 47 additions and 9 deletions

View file

@ -773,7 +773,7 @@ impl LayoutThread {
let root_node = root_element.as_node(); let root_node = root_element.as_node();
let damage = compute_damage_and_repair_style(layout_context.shared_context(), root_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(); layout_context.style_context.stylist.rule_tree().maybe_gc();
return false; return false;
} }

View file

@ -105,7 +105,9 @@ pub(crate) fn compute_damage_and_repair_style_inner(
parent_restyle_damage: RestyleDamage, parent_restyle_damage: RestyleDamage,
) -> RestyleDamage { ) -> RestyleDamage {
let original_damage; let original_damage;
let damage = { let damage;
{
let mut element_data = node let mut element_data = node
.style_data() .style_data()
.expect("Should not run `compute_damage` before styling.") .expect("Should not run `compute_damage` before styling.")
@ -113,14 +115,14 @@ pub(crate) fn compute_damage_and_repair_style_inner(
.borrow_mut(); .borrow_mut();
original_damage = std::mem::take(&mut element_data.damage); 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 let Some(ref style) = element_data.styles.primary {
if style.get_box().display == Display::None { if style.get_box().display == Display::None {
return parent_restyle_damage; return damage;
} }
} }
}
original_damage | parent_restyle_damage
};
let mut propagated_damage = damage; let mut propagated_damage = damage;
for child in iter_child_nodes(node) { for child in iter_child_nodes(node) {

View file

@ -27,6 +27,7 @@ use servo_media::streams::registry::MediaStreamId;
use snapshot::Snapshot; use snapshot::Snapshot;
use style::attr::AttrValue; use style::attr::AttrValue;
use super::node::NodeDamage;
pub(crate) use crate::canvas_context::*; pub(crate) use crate::canvas_context::*;
use crate::conversions::Convert; use crate::conversions::Convert;
use crate::dom::attr::Attr; use crate::dom::attr::Attr;
@ -687,8 +688,11 @@ impl VirtualMethods for HTMLCanvasElement {
.unwrap() .unwrap()
.attribute_mutated(attr, mutation, can_gc); .attribute_mutated(attr, mutation, can_gc);
match attr.local_name() { 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::<Node>().dirty(NodeDamage::OtherNodeDamage);
},
_ => {},
}; };
} }

View file

@ -520,6 +520,7 @@ impl HTMLInputElement {
let mut value = textinput.single_line_content().clone(); let mut value = textinput.single_line_content().clone();
self.sanitize_value(&mut value); self.sanitize_value(&mut value);
textinput.set_content(value); textinput.set_content(value);
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
} }
fn does_minmaxlength_apply(&self) -> bool { fn does_minmaxlength_apply(&self) -> bool {
@ -2668,6 +2669,7 @@ impl VirtualMethods for HTMLInputElement {
let mut value = textinput.single_line_content().clone(); let mut value = textinput.single_line_content().clone();
self.sanitize_value(&mut value); self.sanitize_value(&mut value);
textinput.set_content(value); textinput.set_content(value);
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
// Steps 7-9 // Steps 7-9
if !previously_selectable && self.selection_api_applies() { if !previously_selectable && self.selection_api_applies() {
@ -2695,6 +2697,8 @@ impl VirtualMethods for HTMLInputElement {
self.sanitize_value(&mut value); self.sanitize_value(&mut value);
self.textinput.borrow_mut().set_content(value); self.textinput.borrow_mut().set_content(value);
self.update_placeholder_shown_state(); self.update_placeholder_shown_state();
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
}, },
local_name!("name") if self.input_type() == InputType::Radio => { local_name!("name") if self.input_type() == InputType::Radio => {
self.radio_group_updated( self.radio_group_updated(

View file

@ -9,6 +9,9 @@ use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use style::color::AbsoluteColor; use style::color::AbsoluteColor;
use style::context::QuirksMode; 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::HTMLTableCellElementBinding::HTMLTableCellElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::inheritance::Castable;
@ -174,6 +177,19 @@ impl VirtualMethods for HTMLTableCellElement {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) Some(self.upcast::<HTMLElement>() 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::<Node>().dirty(NodeDamage::OtherNodeDamage);
}
if matches!(*attr.local_name(), local_name!("rowspan")) {
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
}
}
fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue { fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
match *local_name { match *local_name {
local_name!("colspan") => { local_name!("colspan") => {

View file

@ -7,8 +7,10 @@ use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject; use js::rust::HandleObject;
use style::attr::{AttrValue, LengthOrPercentageOrAuto}; use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use super::attr::Attr;
use super::bindings::root::LayoutDom; 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::codegen::Bindings::HTMLTableColElementBinding::HTMLTableColElementMethods;
use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::root::DomRoot;
@ -93,6 +95,16 @@ impl VirtualMethods for HTMLTableColElement {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) Some(self.upcast::<HTMLElement>() 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::<Node>().dirty(NodeDamage::OtherNodeDamage);
}
}
fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue { fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
match *local_name { match *local_name {
local_name!("span") => { local_name!("span") => {