layout: Add a repaint-only incremental layout mode (#36978)

This change adds the simplest kind of incremental layout. When Servo
detects that all style changes only require a repaint, only run stacking
context tree and WebRender display list generation. This means that
these kind of restyles do not need a re-layout. Instead, the existing
box and fragment trees will be used and the styles of damaged nodes will
be updated in their box and fragment tree nodes.

This requires a new style repair DOM traversal for nodes that have had
their style damaged. In addition, careful accounting of all the places
where we store style must happen in order ot update those styles.

Testing: This is covered by existing WPT tests as it should not change
observable behavior.

We have created a test case which shows a 50% speedup when run
in Servo, even though there still a long way to go to match the speed
of other browsers:
https://gist.github.com/mrobinson/44ec87d028c0198917a7715a06dd98a0

Co-authored-by: Oriol Brufau <obrufau@igalia.com>
Signed-off-by: Martin Robinson <mrobinson@igalia.com>

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-12 19:03:50 +02:00 committed by GitHub
parent 51fa6cdcb6
commit 8808f9a468
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 467 additions and 72 deletions

40
Cargo.lock generated
View file

@ -1065,7 +1065,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]
@ -2032,7 +2032,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
dependencies = [
"libc",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -2559,7 +2559,7 @@ dependencies = [
"gobject-sys",
"libc",
"system-deps",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -4001,7 +4001,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi 0.5.0",
"libc",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -4263,7 +4263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
"windows-targets 0.48.5",
]
[[package]]
@ -6191,7 +6191,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -6524,7 +6524,7 @@ dependencies = [
[[package]]
name = "selectors"
version = "0.28.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea"
dependencies = [
"bitflags 2.9.0",
"cssparser",
@ -6819,7 +6819,7 @@ dependencies = [
[[package]]
name = "servo_arc"
version = "0.4.1"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea"
dependencies = [
"serde",
"stable_deref_trait",
@ -7280,7 +7280,7 @@ dependencies = [
[[package]]
name = "stylo"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea"
dependencies = [
"app_units",
"arrayvec",
@ -7338,7 +7338,7 @@ dependencies = [
[[package]]
name = "stylo_atoms"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea"
dependencies = [
"string_cache",
"string_cache_codegen",
@ -7347,12 +7347,12 @@ dependencies = [
[[package]]
name = "stylo_config"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea"
[[package]]
name = "stylo_derive"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea"
dependencies = [
"darling",
"proc-macro2",
@ -7364,7 +7364,7 @@ dependencies = [
[[package]]
name = "stylo_dom"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea"
dependencies = [
"bitflags 2.9.0",
"stylo_malloc_size_of",
@ -7373,7 +7373,7 @@ dependencies = [
[[package]]
name = "stylo_malloc_size_of"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea"
dependencies = [
"app_units",
"cssparser",
@ -7390,12 +7390,12 @@ dependencies = [
[[package]]
name = "stylo_static_prefs"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea"
[[package]]
name = "stylo_traits"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea"
dependencies = [
"app_units",
"bitflags 2.9.0",
@ -7559,7 +7559,7 @@ dependencies = [
"getrandom",
"once_cell",
"rustix",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -7778,7 +7778,7 @@ dependencies = [
[[package]]
name = "to_shmem"
version = "0.2.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea"
dependencies = [
"cssparser",
"servo_arc",
@ -7791,7 +7791,7 @@ dependencies = [
[[package]]
name = "to_shmem_derive"
version = "0.1.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#7edd19e2f09570c6734161b4bb7d47859c8699bf"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#bc815af4b5ae01768eaef64d21cebe6d66be06ea"
dependencies = [
"darling",
"proc-macro2",
@ -8916,7 +8916,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]

View file

@ -471,18 +471,16 @@ impl Fragment {
Fragment::AbsoluteOrFixedPositioned(_) => {},
Fragment::Positioning(positioning_fragment) => {
let positioning_fragment = positioning_fragment.borrow();
if let Some(style) = positioning_fragment.style.as_ref() {
let rect = positioning_fragment
.rect
.translate(containing_block.origin.to_vector());
self.maybe_push_hit_test_for_style_and_tag(
builder,
style,
positioning_fragment.base.tag,
rect,
Cursor::Default,
);
}
let rect = positioning_fragment
.rect
.translate(containing_block.origin.to_vector());
self.maybe_push_hit_test_for_style_and_tag(
builder,
&positioning_fragment.style,
positioning_fragment.base.tag,
rect,
Cursor::Default,
);
},
Fragment::Image(image) => {
let image = image.borrow();

View file

@ -19,11 +19,11 @@ use script_layout_interface::{
GenericLayoutDataTrait, LayoutElementType, LayoutNodeType as ScriptLayoutNodeType,
};
use servo_arc::Arc as ServoArc;
use style::context::SharedStyleContext;
use style::properties::ComputedValues;
use style::selector_parser::PseudoElement;
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::flexbox::FlexLevelBox;
use crate::flow::BlockLevelBox;
use crate::flow::inline::{InlineItem, SharedInlineStyles};
@ -102,6 +102,41 @@ impl LayoutBox {
LayoutBox::TableLevelBox(table_box) => table_box.fragments(),
}
}
fn repair_style(
&self,
context: &SharedStyleContext,
node: &ServoLayoutNode,
new_style: &ServoArc<ComputedValues>,
) {
match self {
LayoutBox::DisplayContents(inline_shared_styles) => {
*inline_shared_styles.style.borrow_mut() = new_style.clone();
*inline_shared_styles.selected.borrow_mut() = node.to_threadsafe().selected_style();
},
LayoutBox::BlockLevel(block_level_box) => {
block_level_box
.borrow_mut()
.repair_style(context, node, new_style);
},
LayoutBox::InlineLevel(inline_items) => {
for inline_item in inline_items {
inline_item
.borrow_mut()
.repair_style(context, node, new_style);
}
},
LayoutBox::FlexLevel(flex_level_box) => {
flex_level_box.borrow_mut().repair_style(context, new_style)
},
LayoutBox::TableLevelBox(table_level_box) => {
table_level_box.repair_style(context, new_style)
},
LayoutBox::TaffyItemBox(taffy_item_box) => {
taffy_item_box.borrow_mut().repair_style(context, new_style)
},
}
}
}
/// A wrapper for [`InnerDOMLayoutData`]. This is necessary to give the entire data
@ -167,7 +202,7 @@ pub(crate) trait NodeExt<'dom> {
fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)>;
fn as_video(&self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)>;
fn as_typeless_object_with_data_attribute(&self) -> Option<String>;
fn style(&self, context: &LayoutContext) -> ServoArc<ComputedValues>;
fn style(&self, context: &SharedStyleContext) -> ServoArc<ComputedValues>;
fn layout_data_mut(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData>;
fn layout_data(&self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>>;
@ -180,6 +215,8 @@ pub(crate) trait NodeExt<'dom> {
fn fragments_for_pseudo(&self, pseudo_element: Option<PseudoElement>) -> Vec<Fragment>;
fn invalidate_cached_fragment(&self);
fn repair_style(&self, context: &SharedStyleContext);
}
impl<'dom> NodeExt<'dom> for ServoLayoutNode<'dom> {
@ -253,8 +290,8 @@ impl<'dom> NodeExt<'dom> for ServoLayoutNode<'dom> {
.map(|string| string.to_owned())
}
fn style(&self, context: &LayoutContext) -> ServoArc<ComputedValues> {
self.to_threadsafe().style(context.shared_context())
fn style(&self, context: &SharedStyleContext) -> ServoArc<ComputedValues> {
self.to_threadsafe().style(context)
}
fn layout_data_mut(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData> {
@ -339,4 +376,30 @@ impl<'dom> NodeExt<'dom> for ServoLayoutNode<'dom> {
})
.unwrap_or_default()
}
fn repair_style(&self, context: &SharedStyleContext) {
let data = self.layout_data_mut();
if let Some(layout_object) = &*data.self_box.borrow() {
let style = self.to_threadsafe().style(context);
layout_object.repair_style(context, self, &style);
}
if let Some(layout_object) = &*data.pseudo_before_box.borrow() {
if let Some(node) = self.to_threadsafe().with_pseudo(PseudoElement::Before) {
layout_object.repair_style(context, self, &node.style(context));
}
}
if let Some(layout_object) = &*data.pseudo_after_box.borrow() {
if let Some(node) = self.to_threadsafe().with_pseudo(PseudoElement::After) {
layout_object.repair_style(context, self, &node.style(context));
}
}
if let Some(layout_object) = &*data.pseudo_marker_box.borrow() {
if let Some(node) = self.to_threadsafe().with_pseudo(PseudoElement::Marker) {
layout_object.repair_style(context, self, &node.style(context));
}
}
}
}

View file

@ -212,7 +212,10 @@ fn traverse_children_of<'dom>(
);
if is_text_input_element || is_textarea_element {
let info = NodeAndStyleInfo::new(parent_element, parent_element.style(context));
let info = NodeAndStyleInfo::new(
parent_element,
parent_element.style(context.shared_context()),
);
let node_text_content = parent_element.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
@ -231,7 +234,7 @@ fn traverse_children_of<'dom>(
if !is_text_input_element && !is_textarea_element {
for child in iter_child_nodes(parent_element) {
if child.is_text_node() {
let info = NodeAndStyleInfo::new(child, child.style(context));
let info = NodeAndStyleInfo::new(child, child.style(context.shared_context()));
handler.handle_text(&info, child.to_threadsafe().node_text_content());
} else if child.is_element() {
traverse_element(child, context, handler);
@ -252,7 +255,7 @@ fn traverse_element<'dom>(
element.unset_pseudo_element_box(PseudoElement::Marker);
let replaced = ReplacedContents::for_element(element, context);
let style = element.style(context);
let style = element.style(context.shared_context());
match Display::from(style.get_box().display) {
Display::None => element.unset_all_boxes(),
Display::Contents => {

View file

@ -5,6 +5,7 @@
use geom::{FlexAxis, MainStartCrossStart};
use malloc_size_of_derive::MallocSizeOf;
use servo_arc::Arc as ServoArc;
use style::context::SharedStyleContext;
use style::logical_geometry::WritingMode;
use style::properties::ComputedValues;
use style::properties::longhands::align_items::computed_value::T as AlignItems;
@ -136,6 +137,11 @@ impl FlexContainer {
config: FlexContainerConfig::new(&info.style),
}
}
pub(crate) fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) {
self.config = FlexContainerConfig::new(new_style);
self.style = new_style.clone();
}
}
#[derive(Debug, MallocSizeOf)]
@ -145,6 +151,22 @@ pub(crate) enum FlexLevelBox {
}
impl FlexLevelBox {
pub(crate) fn repair_style(
&mut self,
context: &SharedStyleContext,
new_style: &ServoArc<ComputedValues>,
) {
match self {
FlexLevelBox::FlexItem(flex_item_box) => flex_item_box
.independent_formatting_context
.repair_style(context, new_style),
FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box
.borrow_mut()
.context
.repair_style(context, new_style),
}
}
pub(crate) fn invalidate_cached_fragment(&self) {
match self {
FlexLevelBox::FlexItem(flex_item_box) => flex_item_box

View file

@ -7,7 +7,6 @@ use std::char::{ToLowercase, ToUppercase};
use icu_segmenter::WordSegmenter;
use itertools::izip;
use servo_arc::Arc;
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
use style::values::specified::text::TextTransformCase;
use unicode_bidi::Level;
@ -158,7 +157,7 @@ impl InlineFormattingContextBuilder {
independent_formatting_context: IndependentFormattingContext,
) -> ArcRefCell<InlineItem> {
let inline_level_box = ArcRefCell::new(InlineItem::Atomic(
Arc::new(independent_formatting_context),
ArcRefCell::new(independent_formatting_context),
self.current_text_offset,
Level::ltr(), /* This will be assigned later if necessary. */
));
@ -189,7 +188,8 @@ impl InlineFormattingContextBuilder {
}
pub(crate) fn push_float_box(&mut self, float_box: FloatBox) -> ArcRefCell<InlineItem> {
let inline_level_box = ArcRefCell::new(InlineItem::OutOfFlowFloatBox(Arc::new(float_box)));
let inline_level_box =
ArcRefCell::new(InlineItem::OutOfFlowFloatBox(ArcRefCell::new(float_box)));
self.inline_items.push(inline_level_box.clone());
self.contains_floats = true;
inline_level_box

View file

@ -7,6 +7,10 @@ use std::vec::IntoIter;
use app_units::Au;
use fonts::FontMetrics;
use malloc_size_of_derive::MallocSizeOf;
use script::layout_dom::ServoLayoutNode;
use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
use servo_arc::Arc as ServoArc;
use style::properties::ComputedValues;
use super::{
InlineContainerState, InlineContainerStateFlags, SharedInlineStyles,
@ -66,6 +70,16 @@ impl InlineBox {
pub(crate) fn layout_style(&self) -> LayoutStyle {
LayoutStyle::Default(&self.base.style)
}
pub(crate) fn repair_style(
&mut self,
node: &ServoLayoutNode,
new_style: &ServoArc<ComputedValues>,
) {
self.base.repair_style(new_style);
*self.shared_inline_styles.style.borrow_mut() = new_style.clone();
*self.shared_inline_styles.selected.borrow_mut() = node.to_threadsafe().selected_style();
}
}
#[derive(Debug, Default, MallocSizeOf)]

View file

@ -90,12 +90,13 @@ use line::{
use line_breaker::LineBreaker;
use malloc_size_of_derive::MallocSizeOf;
use range::Range;
use script::layout_dom::ServoLayoutNode;
use servo_arc::Arc;
use style::Zero;
use style::computed_values::text_wrap_mode::T as TextWrapMode;
use style::computed_values::vertical_align::T as VerticalAlign;
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
use style::context::QuirksMode;
use style::context::{QuirksMode, SharedStyleContext};
use style::properties::ComputedValues;
use style::properties::style_structs::InheritedText;
use style::values::generics::box_::VerticalAlignKeyword;
@ -210,15 +211,41 @@ pub(crate) enum InlineItem {
ArcRefCell<AbsolutelyPositionedBox>,
usize, /* offset_in_text */
),
OutOfFlowFloatBox(#[conditional_malloc_size_of] Arc<FloatBox>),
OutOfFlowFloatBox(ArcRefCell<FloatBox>),
Atomic(
#[conditional_malloc_size_of] Arc<IndependentFormattingContext>,
ArcRefCell<IndependentFormattingContext>,
usize, /* offset_in_text */
Level, /* bidi_level */
),
}
impl InlineItem {
pub(crate) fn repair_style(
&self,
context: &SharedStyleContext,
node: &ServoLayoutNode,
new_style: &Arc<ComputedValues>,
) {
match self {
InlineItem::StartInlineBox(inline_box) => {
inline_box.borrow_mut().repair_style(node, new_style);
},
InlineItem::EndInlineBox => {},
// TextRun holds a handle the `InlineSharedStyles` which is updated when repairing inline box
// and `display: contents` styles.
InlineItem::TextRun(..) => {},
InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => positioned_box
.borrow_mut()
.context
.repair_style(context, new_style),
InlineItem::OutOfFlowFloatBox(float_box) => float_box
.borrow_mut()
.contents
.repair_style(context, new_style),
InlineItem::Atomic(atomic, ..) => atomic.borrow_mut().repair_style(context, new_style),
}
}
pub(crate) fn invalidate_cached_fragment(&self) {
match self {
InlineItem::StartInlineBox(inline_box) => {
@ -232,11 +259,14 @@ impl InlineItem {
.base
.invalidate_cached_fragment();
},
InlineItem::OutOfFlowFloatBox(float_box) => {
float_box.contents.base.invalidate_cached_fragment()
},
InlineItem::OutOfFlowFloatBox(float_box) => float_box
.borrow()
.contents
.base
.invalidate_cached_fragment(),
InlineItem::Atomic(independent_formatting_context, ..) => {
independent_formatting_context
.borrow()
.base
.invalidate_cached_fragment();
},
@ -252,9 +282,11 @@ impl InlineItem {
InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => {
positioned_box.borrow().context.base.fragments()
},
InlineItem::OutOfFlowFloatBox(float_box) => float_box.contents.base.fragments(),
InlineItem::OutOfFlowFloatBox(float_box) => {
float_box.borrow().contents.base.fragments()
},
InlineItem::Atomic(independent_formatting_context, ..) => {
independent_formatting_context.base.fragments()
independent_formatting_context.borrow().base.fragments()
},
}
}
@ -978,6 +1010,7 @@ impl InlineFormattingContextLayout<'_> {
.as_physical(Some(self.containing_block));
self.fragments
.push(Fragment::Positioning(PositioningFragment::new_anonymous(
self.root_nesting_level.style.clone(),
physical_line_rect,
fragments,
)));
@ -1770,7 +1803,7 @@ impl InlineFormattingContext {
InlineItem::EndInlineBox => layout.finish_inline_box(),
InlineItem::TextRun(run) => run.borrow().layout_into_line_items(&mut layout),
InlineItem::Atomic(atomic_formatting_context, offset_in_text, bidi_level) => {
atomic_formatting_context.layout_into_line_items(
atomic_formatting_context.borrow().layout_into_line_items(
&mut layout,
*offset_in_text,
*bidi_level,
@ -1785,7 +1818,7 @@ impl InlineFormattingContext {
));
},
InlineItem::OutOfFlowFloatBox(float_box) => {
float_box.layout_into_line_items(&mut layout);
float_box.borrow().layout_into_line_items(&mut layout);
},
}
}
@ -2448,7 +2481,7 @@ impl<'layout_data> ContentSizesComputation<'layout_data> {
let InlineContentSizesResult {
sizes: outer,
depends_on_block_constraints,
} = atomic.outer_inline_content_sizes(
} = atomic.borrow().outer_inline_content_sizes(
self.layout_context,
&self.constraint_space.into(),
&LogicalVec2::zero(),

View file

@ -9,9 +9,11 @@ use app_units::{Au, MAX_AU};
use inline::InlineFormattingContext;
use malloc_size_of_derive::MallocSizeOf;
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
use script::layout_dom::ServoLayoutNode;
use servo_arc::Arc;
use style::Zero;
use style::computed_values::clear::T as StyleClear;
use style::context::SharedStyleContext;
use style::logical_geometry::Direction;
use style::properties::ComputedValues;
use style::servo::selector_parser::PseudoElement;
@ -21,6 +23,7 @@ use style::values::specified::{Display, TextAlignKeyword};
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::dom::NodeExt;
use crate::flow::float::{
Clear, ContainingBlockPositionInfo, FloatBox, FloatSide, PlacementAmongFloats,
SequentialLayoutState,
@ -91,6 +94,36 @@ pub(crate) enum BlockLevelBox {
}
impl BlockLevelBox {
pub(crate) fn repair_style(
&mut self,
context: &SharedStyleContext,
node: &ServoLayoutNode,
new_style: &Arc<ComputedValues>,
) {
self.with_base_mut(|base| {
base.repair_style(new_style);
});
match self {
BlockLevelBox::Independent(independent_formatting_context) => {
independent_formatting_context.repair_style(context, new_style)
},
BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box
.borrow_mut()
.context
.repair_style(context, new_style),
BlockLevelBox::OutOfFlowFloatBox(float_box) => {
float_box.contents.repair_style(context, new_style)
},
BlockLevelBox::OutsideMarker(outside_marker) => {
outside_marker.repair_style(context, node, new_style)
},
BlockLevelBox::SameFormattingContextBlock { base, .. } => {
base.repair_style(new_style);
},
}
}
pub(crate) fn invalidate_cached_fragment(&self) {
self.with_base(LayoutBoxBase::invalidate_cached_fragment);
}
@ -113,6 +146,20 @@ impl BlockLevelBox {
}
}
pub(crate) fn with_base_mut<T>(&mut self, callback: impl Fn(&mut LayoutBoxBase) -> T) -> T {
match self {
BlockLevelBox::Independent(independent_formatting_context) => {
callback(&mut independent_formatting_context.base)
},
BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => {
callback(&mut positioned_box.borrow_mut().context.base)
},
BlockLevelBox::OutOfFlowFloatBox(float_box) => callback(&mut float_box.contents.base),
BlockLevelBox::OutsideMarker(outside_marker) => callback(&mut outside_marker.base),
BlockLevelBox::SameFormattingContextBlock { base, .. } => callback(base),
}
}
fn contains_floats(&self) -> bool {
match self {
BlockLevelBox::SameFormattingContextBlock {
@ -360,6 +407,16 @@ impl OutsideMarker {
None,
)))
}
fn repair_style(
&mut self,
context: &SharedStyleContext,
node: &ServoLayoutNode,
new_style: &Arc<ComputedValues>,
) {
self.list_item_style = node.style(context);
self.base.repair_style(new_style);
}
}
impl BlockFormattingContext {

View file

@ -59,7 +59,7 @@ impl BoxTree {
// > none, user agents must instead apply the overflow-* values of the first such child
// > element to the viewport. The element from which the value is propagated must then have a
// > used overflow value of visible.
let root_style = root_element.style(context);
let root_style = root_element.style(context.shared_context());
let mut viewport_overflow_x = root_style.clone_overflow_x();
let mut viewport_overflow_y = root_style.clone_overflow_y();
@ -76,7 +76,7 @@ impl BoxTree {
continue;
}
let style = child.style(context);
let style = child.style(context.shared_context());
if !style.get_box().display.is_none() {
viewport_overflow_x = style.clone_overflow_x();
viewport_overflow_y = style.clone_overflow_y();
@ -293,7 +293,7 @@ fn construct_for_root_element(
context: &LayoutContext,
root_element: ServoLayoutNode<'_>,
) -> Vec<ArcRefCell<BlockLevelBox>> {
let info = NodeAndStyleInfo::new(root_element, root_element.style(context));
let info = NodeAndStyleInfo::new(root_element, root_element.style(context.shared_context()));
let box_style = info.style.get_box();
let display_inside = match Display::from(box_style.display) {

View file

@ -6,6 +6,7 @@ use app_units::Au;
use malloc_size_of_derive::MallocSizeOf;
use script::layout_dom::ServoLayoutElement;
use servo_arc::Arc;
use style::context::SharedStyleContext;
use style::properties::ComputedValues;
use style::selector_parser::PseudoElement;
@ -217,6 +218,20 @@ impl IndependentFormattingContext {
},
}
}
pub(crate) fn repair_style(
&mut self,
context: &SharedStyleContext,
new_style: &Arc<ComputedValues>,
) {
self.base.repair_style(new_style);
match &mut self.contents {
IndependentFormattingContextContents::NonReplaced(content) => {
content.repair_style(context, new_style);
},
IndependentFormattingContextContents::Replaced(..) => {},
}
}
}
impl IndependentNonReplacedContents {
@ -334,6 +349,19 @@ impl IndependentNonReplacedContents {
pub(crate) fn is_table(&self) -> bool {
matches!(self, Self::Table(_))
}
fn repair_style(&mut self, context: &SharedStyleContext, new_style: &Arc<ComputedValues>) {
match self {
IndependentNonReplacedContents::Flow(..) => {},
IndependentNonReplacedContents::Flex(flex_container) => {
flex_container.repair_style(new_style)
},
IndependentNonReplacedContents::Grid(taffy_container) => {
taffy_container.repair_style(new_style)
},
IndependentNonReplacedContents::Table(table) => table.repair_style(context, new_style),
}
}
}
impl ComputeInlineContentSizes for IndependentNonReplacedContents {

View file

@ -304,6 +304,25 @@ impl Fragment {
_ => None,
}
}
pub(crate) fn repair_style(&self, style: &ServoArc<ComputedValues>) {
match self {
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
box_fragment.borrow_mut().style = style.clone()
},
Fragment::Positioning(positioning_fragment) => {
positioning_fragment.borrow_mut().style = style.clone();
},
Fragment::AbsoluteOrFixedPositioned(positioned_fragment) => {
if let Some(ref fragment) = positioned_fragment.borrow().fragment {
fragment.repair_style(style);
}
},
Fragment::Text(..) => unreachable!("Should never try to repair style of TextFragment"),
Fragment::Image(image_fragment) => image_fragment.borrow_mut().style = style.clone(),
Fragment::IFrame(iframe_fragment) => iframe_fragment.borrow_mut().style = style.clone(),
}
}
}
impl TextFragment {

View file

@ -24,8 +24,8 @@ pub(crate) struct PositioningFragment {
/// The scrollable overflow of this anonymous fragment's children.
pub scrollable_overflow: PhysicalRect<Au>,
/// If this fragment was created with a style, the style of the fragment.
pub style: Option<ServoArc<ComputedValues>>,
/// The style of the fragment.
pub style: ServoArc<ComputedValues>,
/// This [`PositioningFragment`]'s containing block rectangle in coordinates relative to
/// the initial containing block, but not taking into account any transforms.
@ -33,8 +33,12 @@ pub(crate) struct PositioningFragment {
}
impl PositioningFragment {
pub fn new_anonymous(rect: PhysicalRect<Au>, children: Vec<Fragment>) -> ArcRefCell<Self> {
Self::new_with_base_fragment(BaseFragment::anonymous(), None, rect, children)
pub fn new_anonymous(
style: ServoArc<ComputedValues>,
rect: PhysicalRect<Au>,
children: Vec<Fragment>,
) -> ArcRefCell<Self> {
Self::new_with_base_fragment(BaseFragment::anonymous(), style, rect, children)
}
pub fn new_empty(
@ -42,12 +46,12 @@ impl PositioningFragment {
rect: PhysicalRect<Au>,
style: ServoArc<ComputedValues>,
) -> ArcRefCell<Self> {
Self::new_with_base_fragment(base_fragment_info.into(), Some(style), rect, Vec::new())
Self::new_with_base_fragment(base_fragment_info.into(), style, rect, Vec::new())
}
fn new_with_base_fragment(
base: BaseFragment,
style: Option<ServoArc<ComputedValues>>,
style: ServoArc<ComputedValues>,
rect: PhysicalRect<Au>,
children: Vec<Fragment>,
) -> ArcRefCell<Self> {

View file

@ -89,6 +89,13 @@ impl LayoutBoxBase {
pub(crate) fn clear_fragments(&self) {
self.fragments.borrow_mut().clear();
}
pub(crate) fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.style = new_style.clone();
for fragment in self.fragments.borrow_mut().iter_mut() {
fragment.repair_style(new_style);
}
}
}
impl Debug for LayoutBoxBase {

View file

@ -56,7 +56,7 @@ use style::media_queries::{Device, MediaList, MediaType};
use style::properties::style_structs::Font;
use style::properties::{ComputedValues, PropertyId};
use style::queries::values::PrefersColorScheme;
use style::selector_parser::{PseudoElement, SnapshotMap};
use style::selector_parser::{PseudoElement, RestyleDamage, SnapshotMap};
use style::servo::media_queries::FontMetricsProvider;
use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards};
use style::stylesheets::{
@ -83,7 +83,7 @@ use crate::query::{
process_content_boxes_request, process_node_scroll_area_request, process_offset_parent_query,
process_resolved_font_style_query, process_resolved_style_request, process_text_index_request,
};
use crate::traversal::RecalcStyle;
use crate::traversal::{RecalcStyle, compute_damage_and_repair_style};
use crate::{BoxTree, FragmentTree};
// This mutex is necessary due to syncronisation issues between two different types of thread-local storage
@ -765,6 +765,12 @@ impl LayoutThread {
driver::traverse_dom(&recalc_style_traversal, token, rayon_pool).as_node();
let root_node = root_element.as_node();
let damage = compute_damage_and_repair_style(layout_context.shared_context(), root_node);
if damage == RestyleDamage::REPAINT {
layout_context.style_context.stylist.rule_tree().maybe_gc();
return;
}
let mut box_tree = self.box_tree.borrow_mut();
let box_tree = &mut *box_tree;
let mut build_box_tree = || {

View file

@ -2867,6 +2867,7 @@ impl TableSlotCell {
block: vertical_align_offset,
};
let vertical_align_fragment = PositioningFragment::new_anonymous(
self.base.style.clone(),
vertical_align_fragment_rect.as_physical(None),
layout.layout.fragments,
);

View file

@ -76,9 +76,12 @@ pub(crate) use construct::AnonymousTableContent;
pub use construct::TableBuilder;
use euclid::{Point2D, Size2D, UnknownUnit, Vector2D};
use malloc_size_of_derive::MallocSizeOf;
use script::layout_dom::ServoLayoutElement;
use servo_arc::Arc;
use style::context::SharedStyleContext;
use style::properties::ComputedValues;
use style::properties::style_structs::Font;
use style::selector_parser::PseudoElement;
use style_traits::dom::OpaqueNode;
use super::flow::BlockFormattingContext;
@ -191,6 +194,19 @@ impl Table {
),
}
}
pub(crate) fn repair_style(
&mut self,
context: &SharedStyleContext,
new_style: &Arc<ComputedValues>,
) {
self.style = new_style.clone();
self.grid_style = context.stylist.style_for_anonymous::<ServoLayoutElement>(
&context.guards,
&PseudoElement::ServoTableGrid,
new_style,
);
}
}
type TableSlotCoordinates = Point2D<usize, UnknownUnit>;
@ -232,6 +248,10 @@ impl TableSlotCell {
pub fn node_id(&self) -> usize {
self.base.base_fragment_info.tag.map_or(0, |tag| tag.node.0)
}
fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.base.repair_style(new_style);
}
}
/// A single table slot. It may be an actual cell, or a reference
@ -294,6 +314,13 @@ pub struct TableTrack {
shared_background_style: SharedStyle,
}
impl TableTrack {
fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.base.repair_style(new_style);
self.shared_background_style = SharedStyle::new(new_style.clone());
}
}
#[derive(Debug, MallocSizeOf, PartialEq)]
pub enum TableTrackGroupType {
HeaderGroup,
@ -323,6 +350,11 @@ impl TableTrackGroup {
pub(super) fn is_empty(&self) -> bool {
self.track_range.is_empty()
}
fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.base.repair_style(new_style);
self.shared_background_style = SharedStyle::new(new_style.clone());
}
}
#[derive(Debug, MallocSizeOf)]
@ -389,4 +421,22 @@ impl TableLevelBox {
TableLevelBox::Track(track) => track.borrow().base.fragments(),
}
}
pub(crate) fn repair_style(
&self,
context: &SharedStyleContext<'_>,
new_style: &Arc<ComputedValues>,
) {
match self {
TableLevelBox::Caption(caption) => caption
.borrow_mut()
.context
.repair_style(context, new_style),
TableLevelBox::Cell(cell) => cell.borrow_mut().repair_style(new_style),
TableLevelBox::TrackGroup(track_group) => {
track_group.borrow_mut().repair_style(new_style);
},
TableLevelBox::Track(track) => track.borrow_mut().repair_style(new_style),
}
}
}

View file

@ -8,6 +8,7 @@ use std::fmt;
use app_units::Au;
use malloc_size_of_derive::MallocSizeOf;
use servo_arc::Arc;
use style::context::SharedStyleContext;
use style::properties::ComputedValues;
use stylo_taffy::TaffyStyloStyle;
@ -68,6 +69,10 @@ impl TaffyContainer {
style: info.style.clone(),
}
}
pub(crate) fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.style = new_style.clone();
}
}
#[derive(MallocSizeOf)]
@ -143,6 +148,23 @@ impl TaffyItemBox {
},
}
}
pub(crate) fn repair_style(
&mut self,
context: &SharedStyleContext,
new_style: &Arc<ComputedValues>,
) {
self.style = new_style.clone();
match &mut self.taffy_level_box {
TaffyItemBoxInner::InFlowBox(independent_formatting_context) => {
independent_formatting_context.repair_style(context, new_style)
},
TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box
.borrow_mut()
.context
.repair_style(context, new_style),
}
}
}
/// Details from Taffy grid layout that will be stored

View file

@ -2,14 +2,18 @@
* 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 script::layout_dom::ServoLayoutNode;
use script_layout_interface::wrapper_traits::LayoutNode;
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 style::values::computed::Display;
use crate::context::LayoutContext;
use crate::dom::DOMLayoutData;
use crate::dom::{DOMLayoutData, NodeExt};
use crate::dom_traversal::iter_child_nodes;
pub struct RecalcStyle<'a> {
context: &'a LayoutContext<'a>,
@ -40,14 +44,33 @@ where
) where
F: FnMut(E::ConcreteNode),
{
if node.is_text_node() {
return;
}
let had_style_data = node.style_data().is_some();
unsafe {
node.initialize_style_and_layout_data::<DOMLayoutData>();
if !node.is_text_node() {
let el = node.as_element().unwrap();
let mut data = el.mutate_data().unwrap();
recalc_style_at(self, traversal_data, context, el, &mut data, note_child);
el.unset_dirty_descendants();
}
}
let element = node.as_element().unwrap();
let mut element_data = element.mutate_data().unwrap();
if !had_style_data {
element_data.damage = RestyleDamage::reconstruct();
}
recalc_style_at(
self,
traversal_data,
context,
element,
&mut element_data,
note_child,
);
unsafe {
element.unset_dirty_descendants();
}
}
@ -68,3 +91,48 @@ where
&self.context.style_context
}
}
pub(crate) fn compute_damage_and_repair_style(
context: &SharedStyleContext,
node: ServoLayoutNode<'_>,
) -> RestyleDamage {
compute_damage_and_repair_style_inner(context, node, RestyleDamage::empty())
}
pub(crate) fn compute_damage_and_repair_style_inner(
context: &SharedStyleContext,
node: ServoLayoutNode<'_>,
parent_restyle_damage: RestyleDamage,
) -> RestyleDamage {
let original_damage;
let damage = {
let mut element_data = node
.style_data()
.expect("Should not run `compute_damage` before styling.")
.element_data
.borrow_mut();
if let Some(ref style) = element_data.styles.primary {
if style.get_box().display == Display::None {
return parent_restyle_damage;
}
}
original_damage = std::mem::take(&mut element_data.damage);
element_data.damage |= parent_restyle_damage;
element_data.damage
};
let mut propagated_damage = damage;
for child in iter_child_nodes(node) {
if child.is_element() {
propagated_damage |= compute_damage_and_repair_style_inner(context, child, damage);
}
}
if propagated_damage == RestyleDamage::REPAINT && original_damage == RestyleDamage::REPAINT {
node.repair_style(context);
}
propagated_damage
}

View file

@ -359,7 +359,7 @@ impl Element {
if damage == NodeDamage::OtherNodeDamage {
doc.note_node_with_dirty_descendants(self.upcast());
restyle.damage = RestyleDamage::rebuild_and_reflow();
restyle.damage = RestyleDamage::reconstruct();
}
}