This commit is contained in:
JoeDow 2025-06-04 15:18:21 +08:00 committed by GitHub
commit 3b0c0d8cce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 1068 additions and 295 deletions

24
Cargo.lock generated
View file

@ -6607,7 +6607,7 @@ dependencies = [
[[package]]
name = "selectors"
version = "0.28.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
source = "git+https://github.com/coding-joedow/stylo?branch=main#9a87b429580c4b8504b6259b8f9c674d1cb10fe6"
dependencies = [
"bitflags 2.9.1",
"cssparser",
@ -6902,7 +6902,7 @@ dependencies = [
[[package]]
name = "servo_arc"
version = "0.4.1"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
source = "git+https://github.com/coding-joedow/stylo?branch=main#9a87b429580c4b8504b6259b8f9c674d1cb10fe6"
dependencies = [
"serde",
"stable_deref_trait",
@ -7363,7 +7363,7 @@ dependencies = [
[[package]]
name = "stylo"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
source = "git+https://github.com/coding-joedow/stylo?branch=main#9a87b429580c4b8504b6259b8f9c674d1cb10fe6"
dependencies = [
"app_units",
"arrayvec",
@ -7421,7 +7421,7 @@ dependencies = [
[[package]]
name = "stylo_atoms"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
source = "git+https://github.com/coding-joedow/stylo?branch=main#9a87b429580c4b8504b6259b8f9c674d1cb10fe6"
dependencies = [
"string_cache",
"string_cache_codegen",
@ -7430,12 +7430,12 @@ dependencies = [
[[package]]
name = "stylo_config"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
source = "git+https://github.com/coding-joedow/stylo?branch=main#9a87b429580c4b8504b6259b8f9c674d1cb10fe6"
[[package]]
name = "stylo_derive"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
source = "git+https://github.com/coding-joedow/stylo?branch=main#9a87b429580c4b8504b6259b8f9c674d1cb10fe6"
dependencies = [
"darling",
"proc-macro2",
@ -7447,7 +7447,7 @@ dependencies = [
[[package]]
name = "stylo_dom"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
source = "git+https://github.com/coding-joedow/stylo?branch=main#9a87b429580c4b8504b6259b8f9c674d1cb10fe6"
dependencies = [
"bitflags 2.9.1",
"stylo_malloc_size_of",
@ -7456,7 +7456,7 @@ dependencies = [
[[package]]
name = "stylo_malloc_size_of"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
source = "git+https://github.com/coding-joedow/stylo?branch=main#9a87b429580c4b8504b6259b8f9c674d1cb10fe6"
dependencies = [
"app_units",
"cssparser",
@ -7473,12 +7473,12 @@ dependencies = [
[[package]]
name = "stylo_static_prefs"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
source = "git+https://github.com/coding-joedow/stylo?branch=main#9a87b429580c4b8504b6259b8f9c674d1cb10fe6"
[[package]]
name = "stylo_traits"
version = "0.3.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
source = "git+https://github.com/coding-joedow/stylo?branch=main#9a87b429580c4b8504b6259b8f9c674d1cb10fe6"
dependencies = [
"app_units",
"bitflags 2.9.1",
@ -7887,7 +7887,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "to_shmem"
version = "0.2.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
source = "git+https://github.com/coding-joedow/stylo?branch=main#9a87b429580c4b8504b6259b8f9c674d1cb10fe6"
dependencies = [
"cssparser",
"servo_arc",
@ -7900,7 +7900,7 @@ dependencies = [
[[package]]
name = "to_shmem_derive"
version = "0.1.0"
source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620"
source = "git+https://github.com/coding-joedow/stylo?branch=main#9a87b429580c4b8504b6259b8f9c674d1cb10fe6"
dependencies = [
"darling",
"proc-macro2",

View file

@ -119,7 +119,7 @@ rustls-pemfile = "2.0"
rustls-pki-types = "1.12"
script_layout_interface = { path = "components/shared/script_layout" }
script_traits = { path = "components/shared/script" }
selectors = { git = "https://github.com/servo/stylo", branch = "2025-05-01" }
selectors = { git = "https://github.com/coding-joedow/stylo", branch = "main" }
serde = "1.0.219"
serde_bytes = "0.11"
serde_json = "1.0"
@ -127,7 +127,7 @@ servo-media = { git = "https://github.com/servo/media" }
servo-media-dummy = { git = "https://github.com/servo/media" }
servo-media-gstreamer = { git = "https://github.com/servo/media" }
servo-tracing = { path = "components/servo_tracing" }
servo_arc = { git = "https://github.com/servo/stylo", branch = "2025-05-01" }
servo_arc = { git = "https://github.com/coding-joedow/stylo", branch = "main" }
smallbitvec = "2.6.0"
smallvec = "1.15"
snapshot = { path = "./components/shared/snapshot" }
@ -136,12 +136,12 @@ string_cache = "0.8"
string_cache_codegen = "0.5"
strum = "0.26"
strum_macros = "0.26"
stylo = { git = "https://github.com/servo/stylo", branch = "2025-05-01" }
stylo_atoms = { git = "https://github.com/servo/stylo", branch = "2025-05-01" }
stylo_config = { git = "https://github.com/servo/stylo", branch = "2025-05-01" }
stylo_dom = { git = "https://github.com/servo/stylo", branch = "2025-05-01" }
stylo_malloc_size_of = { git = "https://github.com/servo/stylo", branch = "2025-05-01" }
stylo_traits = { git = "https://github.com/servo/stylo", branch = "2025-05-01" }
stylo = { git = "https://github.com/coding-joedow/stylo", branch = "main" }
stylo_atoms = { git = "https://github.com/coding-joedow/stylo", branch = "main" }
stylo_config = { git = "https://github.com/coding-joedow/stylo", branch = "main" }
stylo_dom = { git = "https://github.com/coding-joedow/stylo", branch = "main" }
stylo_malloc_size_of = { git = "https://github.com/coding-joedow/stylo", branch = "main" }
stylo_traits = { git = "https://github.com/coding-joedow/stylo", branch = "main" }
surfman = { git = "https://github.com/servo/surfman", rev = "f7688b4585f9e0b5d4bf8ee8e4a91e82349610b1", features = ["chains"] }
syn = { version = "2", default-features = false, features = ["clone-impls", "derive", "parsing"] }
synstructure = "0.13"

View file

@ -21,6 +21,10 @@ impl<T> ArcRefCell<T> {
value: Arc::new(AtomicRefCell::new(value)),
}
}
pub fn ptr_eq(this: &Self, other: &Self) -> bool {
Arc::ptr_eq(&this.value, &other.value)
}
}
impl<T> Clone for ArcRefCell<T> {

View file

@ -20,7 +20,7 @@ use script_layout_interface::{
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<Option<LayoutBox>>) -> 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 clear_restyle_damage(&self);
}
impl<'dom> NodeExt<'dom> for ServoLayoutNode<'dom> {
@ -404,4 +404,10 @@ impl<'dom> NodeExt<'dom> for ServoLayoutNode<'dom> {
}
}
}
fn clear_restyle_damage(&self) {
if let Some(data) = self.style_data() {
data.element_data.borrow_mut().damage = RestyleDamage::empty();
}
}
}

View file

@ -17,7 +17,7 @@ use selectors::Element as SelectorsElement;
use servo_arc::Arc as ServoArc;
use style::dom::{NodeInfo, TElement, TNode, TShadowRoot};
use style::properties::ComputedValues;
use style::selector_parser::PseudoElement;
use style::selector_parser::{PseudoElement, RestyleDamage};
use style::values::generics::counters::{Content, ContentItem};
use style::values::specified::Quotes;
@ -94,6 +94,24 @@ impl<'dom> NodeAndStyleInfo<'dom> {
pub(crate) fn get_selection_range(&self) -> Option<Range<ByteIndex>> {
self.node.to_threadsafe().selection()
}
pub(crate) fn get_restyle_damage(&self) -> RestyleDamage {
self.node.style_data().unwrap().element_data.borrow().damage
}
pub(crate) fn is_anonymous(&self) -> bool {
if matches!(
self.pseudo_element_type,
Some(PseudoElement::ServoAnonymousBox) |
Some(PseudoElement::ServoAnonymousTable) |
Some(PseudoElement::ServoAnonymousTableCell) |
Some(PseudoElement::ServoAnonymousTableRow)
) {
return true;
}
false
}
}
impl<'dom> From<&NodeAndStyleInfo<'dom>> for BaseFragmentInfo {
@ -108,13 +126,7 @@ impl<'dom> From<&NodeAndStyleInfo<'dom>> for BaseFragmentInfo {
// TODO(mrobinson): It seems that anonymous boxes should take part in hit testing in some
// cases, but currently this means that the order of hit test results isn't as expected for
// some WPT tests. This needs more investigation.
if matches!(
pseudo,
Some(PseudoElement::ServoAnonymousBox) |
Some(PseudoElement::ServoAnonymousTable) |
Some(PseudoElement::ServoAnonymousTableCell) |
Some(PseudoElement::ServoAnonymousTableRow)
) {
if info.is_anonymous() {
return Self::anonymous();
}
@ -198,6 +210,12 @@ pub(super) trait TraversalHandler<'dom> {
/// Notify the handler that we have finished a `display: contents` element.
fn leave_display_contents(&mut self) {}
/// Returns whether the pseudo element box should be cleared before traversed
/// by this handler.
fn need_clear_pseudo_element_box(&self, _node: &ServoLayoutNode<'dom>) -> bool {
true
}
}
fn traverse_children_of<'dom>(
@ -246,7 +264,9 @@ fn traverse_element<'dom>(
) {
// Clear any existing pseudo-element box slot, because markers are not handled like
// `::before`` and `::after`. They are processed during box tree creation.
element.unset_pseudo_element_box(PseudoElement::Marker);
if handler.need_clear_pseudo_element_box(&element) {
element.unset_pseudo_element_box(PseudoElement::Marker);
}
let replaced = ReplacedContents::for_element(element, context);
let style = element.style(context.shared_context());
@ -288,6 +308,14 @@ fn traverse_element<'dom>(
handler.handle_element(&info, display, contents, box_slot);
},
}
// Currently, we do not support storing the restyle damage caused by their
// styles changes for pseudo-element and list-marker seperately, which is
// stored at the originating element's style data. Clearing restyle damage
// for the originating element at here for keeping consistent with current
// restyle damage detecting strategy: when the originating element's box can
// be kept unchanged, the pseudo-element and list-marker will be kept unchanged.
element.clear_restyle_damage();
}
fn traverse_eager_pseudo_element<'dom>(
@ -299,7 +327,9 @@ fn traverse_eager_pseudo_element<'dom>(
assert!(pseudo_element_type.is_eager());
// First clear any old contents from the node.
node.unset_pseudo_element_box(pseudo_element_type);
if handler.need_clear_pseudo_element_box(&node) {
node.unset_pseudo_element_box(pseudo_element_type);
}
let Some(element) = node.to_threadsafe().as_element() else {
return;

View file

@ -142,6 +142,12 @@ impl FlexContainer {
self.config = FlexContainerConfig::new(new_style);
self.style = new_style.clone();
}
pub(crate) fn invalidate_subtree_caches(&self) {
for flex_level_box in &self.children {
flex_level_box.borrow().invalidate_subtree_caches();
}
}
}
#[derive(Debug, MallocSizeOf)]
@ -182,6 +188,17 @@ impl FlexLevelBox {
}
}
pub(crate) fn invalidate_subtree_caches(&self) {
match self {
FlexLevelBox::FlexItem(flex_item_box) => flex_item_box
.independent_formatting_context
.invalidate_subtree_caches(),
FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => {
positioned_box.borrow().context.invalidate_subtree_caches()
},
}
}
pub(crate) fn fragments(&self) -> Vec<Fragment> {
match self {
FlexLevelBox::FlexItem(flex_item_box) => flex_item_box

View file

@ -4,12 +4,16 @@
use std::borrow::Cow;
use std::convert::TryFrom;
use std::mem;
use atomic_refcell::AtomicRef;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use script::layout_dom::ServoLayoutNode;
use script_layout_interface::wrapper_traits::LayoutNode;
use servo_arc::Arc;
use style::properties::ComputedValues;
use style::properties::longhands::list_style_position::computed_value::T as ListStylePosition;
use style::selector_parser::PseudoElement;
use style::selector_parser::{PseudoElement, RestyleDamage};
use style::str::char_is_whitespace;
use super::OutsideMarker;
@ -56,9 +60,36 @@ impl BlockFormattingContext {
contains_floats,
}
}
pub(crate) fn repair(
&mut self,
context: &LayoutContext,
info: &NodeAndStyleInfo<'_>,
contents: NonReplacedContents,
propagated_data: PropagatedBoxTreeData,
is_list_item: bool,
) {
self.contents
.repair(context, info, contents, propagated_data, is_list_item);
self.contains_floats = self.contents.contains_floats();
}
}
struct BlockLevelJob<'dom> {
enum BlockLevelJob<'dom> {
Copy(ArcRefCell<BlockLevelBox>),
Repair(BlockLevelRepairJob<'dom>),
Create(BlockLevelCreateJob<'dom>),
}
struct BlockLevelRepairJob<'dom> {
info: NodeAndStyleInfo<'dom>,
repaired_box: ArcRefCell<BlockLevelBox>,
propagated_data: PropagatedBoxTreeData,
display_inside: DisplayInside,
contents: Contents,
}
struct BlockLevelCreateJob<'dom> {
info: NodeAndStyleInfo<'dom>,
box_slot: BoxSlot<'dom>,
propagated_data: PropagatedBoxTreeData,
@ -104,6 +135,20 @@ enum IntermediateBlockContainer {
},
}
/// Some childern node of a given DOM node do not have `REBUILD_BOX` restyle damage.
/// Thus, their boxes do not need to be rebuilt and can be retrieved from the
/// previously built children boxes of the given DOM node's repaired block container.
///
/// This helper struct used to try to find the matched boxes in previously built
/// children boxes for a given children node. For block level node, this helper
/// is not very useful. It is more useful to find the inline formatting context
/// that the inline level node participates in, and the anonymous box that wraps
/// the inline formatting context.
struct PreviouslyBuiltChildernMatcher {
block_level_boxes: Vec<ArcRefCell<BlockLevelBox>>,
block_level_boxes_cursor: usize,
}
/// A builder for a block container.
///
/// This builder starts from the first child of a given DOM node
@ -115,7 +160,9 @@ pub(crate) struct BlockContainerBuilder<'dom, 'style> {
/// content designator, and the block container style.
info: &'style NodeAndStyleInfo<'dom>,
/// The list of block-level boxes to be built for the final block container.
/// The list of block-level boxes to be repaired or copied from previous
/// block container building result, or newly built, for the final block
/// container.
///
/// Contains all the block-level jobs we found traversing the tree
/// so far, if this is empty at the end of the traversal and the ongoing
@ -156,6 +203,10 @@ pub(crate) struct BlockContainerBuilder<'dom, 'style> {
/// ancestors that have been processed. This `Vec` allows passing those into new
/// [`InlineFormattingContext`]s that we create.
display_contents_shared_styles: Vec<SharedInlineStyles>,
/// The [`PreviouslyBuiltChildrenMatcher`] if we need to repair the current block
/// container, otherwise None.
previously_built_children_matcher: Option<PreviouslyBuiltChildernMatcher>,
}
impl BlockContainer {
@ -166,26 +217,74 @@ impl BlockContainer {
propagated_data: PropagatedBoxTreeData,
is_list_item: bool,
) -> BlockContainer {
let mut builder = BlockContainerBuilder::new(context, info, propagated_data);
let builder = BlockContainerBuilder::new(context, info, propagated_data);
builder.build(contents, is_list_item)
}
if is_list_item {
if let Some((marker_info, marker_contents)) = crate::lists::make_marker(context, info) {
match marker_info.style.clone_list_style_position() {
ListStylePosition::Inside => {
builder.handle_list_item_marker_inside(&marker_info, info, marker_contents)
},
ListStylePosition::Outside => builder.handle_list_item_marker_outside(
&marker_info,
info,
marker_contents,
info.style.clone(),
),
}
}
pub fn repair(
&mut self,
context: &LayoutContext,
info: &NodeAndStyleInfo<'_>,
contents: NonReplacedContents,
propagated_data: PropagatedBoxTreeData,
is_list_item: bool,
) {
let builder = BlockContainerBuilder::new_for_repair(context, info, propagated_data, self);
let _ = mem::replace(self, builder.build(contents, is_list_item));
}
}
impl PreviouslyBuiltChildernMatcher {
fn new(block_container: &mut BlockContainer) -> Self {
let previously_built_block_level_boxes = match block_container {
BlockContainer::BlockLevelBoxes(boxes) => mem::take(boxes),
BlockContainer::InlineFormattingContext(_) => vec![],
};
Self {
block_level_boxes: previously_built_block_level_boxes,
block_level_boxes_cursor: 0,
}
}
fn match_and_advance(
&mut self,
info: &NodeAndStyleInfo<'_>,
) -> Option<ArcRefCell<BlockLevelBox>> {
if info.is_anonymous() {
return self.find_first_anonymous_box_and_advance();
}
contents.traverse(context, info, &mut builder);
builder.finish()
let data = info.node.layout_data_mut();
let layout_box = data.for_pseudo(info.pseudo_element_type);
let previously_bound_box = match &*AtomicRef::filter_map(layout_box, Option::as_ref)? {
LayoutBox::BlockLevel(block_level_box) => block_level_box.clone(),
_ => return None,
};
// Skip the mismatch boxes, which were produced by the removed node or
// the node has `REBUILD_BOX` restyle damage, to find the box produced by
// current given node.
loop {
let curr = self.next()?;
if ArcRefCell::ptr_eq(&curr, &previously_bound_box) {
return Some(curr);
}
}
}
fn find_first_anonymous_box_and_advance(&mut self) -> Option<ArcRefCell<BlockLevelBox>> {
unreachable!("Unexpected situations, and unimplemented");
}
fn next(&mut self) -> Option<ArcRefCell<BlockLevelBox>> {
if self.block_level_boxes_cursor < self.block_level_boxes.len() {
self.block_level_boxes_cursor += 1;
return Some(self.block_level_boxes[self.block_level_boxes_cursor - 1].clone());
}
None
}
}
@ -205,9 +304,48 @@ impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> {
anonymous_table_content: Vec::new(),
inline_formatting_context_builder: None,
display_contents_shared_styles: Vec::new(),
previously_built_children_matcher: None,
}
}
fn new_for_repair(
context: &'style LayoutContext,
info: &'style NodeAndStyleInfo<'dom>,
propagated_data: PropagatedBoxTreeData,
repaired_block_container: &mut BlockContainer,
) -> Self {
let mut builder = Self::new(context, info, propagated_data);
builder.previously_built_children_matcher = Some(PreviouslyBuiltChildernMatcher::new(
repaired_block_container,
));
builder
}
fn build(mut self, contents: NonReplacedContents, is_list_item: bool) -> BlockContainer {
if is_list_item {
if let Some((marker_info, marker_contents)) =
crate::lists::make_marker(self.context, self.info)
{
match marker_info.style.clone_list_style_position() {
ListStylePosition::Inside => self.handle_list_item_marker_inside(
&marker_info,
self.info,
marker_contents,
),
ListStylePosition::Outside => self.handle_list_item_marker_outside(
&marker_info,
self.info,
marker_contents,
self.info.style.clone(),
),
}
}
}
contents.traverse(self.context, self.info, &mut self);
self.finish()
}
fn currently_processing_inline_box(&self) -> bool {
self.inline_formatting_context_builder
.as_ref()
@ -300,12 +438,13 @@ impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> {
self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
}
self.block_level_boxes.push(BlockLevelJob {
info: table_info,
box_slot: BoxSlot::dummy(),
kind: BlockLevelCreator::AnonymousTable { table_block },
propagated_data: self.propagated_data,
});
self.block_level_boxes
.push(BlockLevelJob::Create(BlockLevelCreateJob {
info: table_info,
box_slot: BoxSlot::dummy(),
kind: BlockLevelCreator::AnonymousTable { table_block },
propagated_data: self.propagated_data,
}));
}
// If the last element in the anonymous table content is whitespace, that
@ -400,9 +539,34 @@ impl<'dom> TraversalHandler<'dom> for BlockContainerBuilder<'dom, '_> {
builder.leave_display_contents();
}
}
fn need_clear_pseudo_element_box(&self, node: &ServoLayoutNode<'dom>) -> bool {
let damage = node.style_data().unwrap().element_data.borrow().damage;
if damage.contains(RestyleDamage::REBUILD_BOX) {
return true;
}
false
}
}
impl<'dom> BlockContainerBuilder<'dom, '_> {
fn try_reuse_block_level_box(
&mut self,
info: &NodeAndStyleInfo<'dom>,
) -> Option<ArcRefCell<BlockLevelBox>> {
let previously_built_children_matcher = self.previously_built_children_matcher.as_mut()?;
if info
.get_restyle_damage()
.contains(RestyleDamage::REBUILD_BOX)
{
return None;
}
previously_built_children_matcher.match_and_advance(info)
}
fn handle_list_item_marker_inside(
&mut self,
marker_info: &NodeAndStyleInfo<'dom>,
@ -435,6 +599,57 @@ impl<'dom> BlockContainerBuilder<'dom, '_> {
container_info: &NodeAndStyleInfo<'dom>,
contents: Vec<crate::dom_traversal::PseudoElementContentItem>,
list_item_style: Arc<ComputedValues>,
) {
// TODO: We do not currently support saving box slots for ::marker pseudo-elements
// that are part nested in ::before and ::after pseudo elements. For now, just
// always create new box for it.
if container_info.pseudo_element_type.is_some() {
self.create_list_item_marker_outside(
marker_info,
container_info,
contents,
list_item_style,
);
return;
}
let Some(reused_block_level_box) = self.try_reuse_block_level_box(marker_info) else {
self.create_list_item_marker_outside(
marker_info,
container_info,
contents,
list_item_style,
);
return;
};
if marker_info
.get_restyle_damage()
.contains(RestyleDamage::REPAIR_BOX)
{
self.block_level_boxes
.push(BlockLevelJob::Repair(BlockLevelRepairJob {
info: marker_info.clone(),
repaired_box: reused_block_level_box,
propagated_data: self.propagated_data,
contents: NonReplacedContents::OfPseudoElement(contents).into(),
display_inside: DisplayInside::Flow {
is_list_item: false,
},
}));
return;
}
self.block_level_boxes
.push(BlockLevelJob::Copy(reused_block_level_box));
}
fn create_list_item_marker_outside(
&mut self,
marker_info: &NodeAndStyleInfo<'dom>,
container_info: &NodeAndStyleInfo<'dom>,
contents: Vec<crate::dom_traversal::PseudoElementContentItem>,
list_item_style: Arc<ComputedValues>,
) {
// TODO: We do not currently support saving box slots for ::marker pseudo-elements
// that are part nested in ::before and ::after pseudo elements. For now, just
@ -446,15 +661,16 @@ impl<'dom> BlockContainerBuilder<'dom, '_> {
.pseudo_element_box_slot(PseudoElement::Marker),
};
self.block_level_boxes.push(BlockLevelJob {
info: marker_info.clone(),
box_slot,
kind: BlockLevelCreator::OutsideMarker {
contents,
list_item_style,
},
propagated_data: self.propagated_data,
});
self.block_level_boxes
.push(BlockLevelJob::Create(BlockLevelCreateJob {
info: marker_info.clone(),
box_slot,
kind: BlockLevelCreator::OutsideMarker {
contents,
list_item_style,
},
propagated_data: self.propagated_data,
}));
}
fn handle_inline_level_element(
@ -464,12 +680,12 @@ impl<'dom> BlockContainerBuilder<'dom, '_> {
contents: Contents,
box_slot: BoxSlot<'dom>,
) {
let propagated_data = self.propagated_data;
let (DisplayInside::Flow { is_list_item }, false) =
(display_inside, contents.is_replaced())
else {
// If this inline element is an atomic, handle it and return.
let context = self.context;
let propagated_data = self.propagated_data;
let atomic = self.ensure_inline_formatting_context_builder().push_atomic(
IndependentFormattingContext::construct(
context,
@ -546,6 +762,39 @@ impl<'dom> BlockContainerBuilder<'dom, '_> {
self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
}
if let Some(reused_block_level_box) = self.try_reuse_block_level_box(info) {
if info
.get_restyle_damage()
.contains(RestyleDamage::REPAIR_BOX)
{
self.block_level_boxes
.push(BlockLevelJob::Repair(BlockLevelRepairJob {
info: info.clone(),
repaired_box: reused_block_level_box,
propagated_data: self.propagated_data,
contents,
display_inside,
}));
} else {
self.block_level_boxes
.push(BlockLevelJob::Copy(reused_block_level_box));
}
} else {
self.create_block_level_box(info, display_inside, contents, box_slot);
}
// Any block also counts as the first line for the purposes of text indent. Even if
// they don't actually indent.
self.have_already_seen_first_line_for_text_indent = true;
}
fn create_block_level_box(
&mut self,
info: &NodeAndStyleInfo<'dom>,
display_inside: DisplayInside,
contents: Contents,
box_slot: BoxSlot<'dom>,
) {
let propagated_data = self.propagated_data;
let kind = match contents {
Contents::NonReplaced(contents) => match display_inside {
@ -577,16 +826,13 @@ impl<'dom> BlockContainerBuilder<'dom, '_> {
}
},
};
self.block_level_boxes.push(BlockLevelJob {
info: info.clone(),
box_slot,
kind,
propagated_data,
});
// Any block also counts as the first line for the purposes of text indent. Even if
// they don't actually indent.
self.have_already_seen_first_line_for_text_indent = true;
self.block_level_boxes
.push(BlockLevelJob::Create(BlockLevelCreateJob {
info: info.clone(),
box_slot,
kind,
propagated_data,
}));
}
fn handle_absolutely_positioned_element(
@ -610,16 +856,37 @@ impl<'dom> BlockContainerBuilder<'dom, '_> {
}
}
if let Some(reused_block_level_box) = self.try_reuse_block_level_box(info) {
if info
.get_restyle_damage()
.contains(RestyleDamage::REPAIR_BOX)
{
self.block_level_boxes
.push(BlockLevelJob::Repair(BlockLevelRepairJob {
info: info.clone(),
repaired_box: reused_block_level_box,
propagated_data: self.propagated_data,
contents,
display_inside,
}));
} else {
self.block_level_boxes
.push(BlockLevelJob::Copy(reused_block_level_box));
}
return;
}
let kind = BlockLevelCreator::OutOfFlowAbsolutelyPositionedBox {
contents,
display_inside,
};
self.block_level_boxes.push(BlockLevelJob {
info: info.clone(),
box_slot,
kind,
propagated_data: self.propagated_data,
});
self.block_level_boxes
.push(BlockLevelJob::Create(BlockLevelCreateJob {
info: info.clone(),
box_slot,
kind,
propagated_data: self.propagated_data,
}));
}
fn handle_float_element(
@ -643,16 +910,37 @@ impl<'dom> BlockContainerBuilder<'dom, '_> {
}
}
if let Some(reused_block_level_box) = self.try_reuse_block_level_box(info) {
if info
.get_restyle_damage()
.contains(RestyleDamage::REPAIR_BOX)
{
self.block_level_boxes
.push(BlockLevelJob::Repair(BlockLevelRepairJob {
info: info.clone(),
repaired_box: reused_block_level_box,
propagated_data: self.propagated_data,
contents,
display_inside,
}));
} else {
self.block_level_boxes
.push(BlockLevelJob::Copy(reused_block_level_box));
}
return;
}
let kind = BlockLevelCreator::OutOfFlowFloatBox {
contents,
display_inside,
};
self.block_level_boxes.push(BlockLevelJob {
info: info.clone(),
box_slot,
kind,
propagated_data: self.propagated_data,
});
self.block_level_boxes
.push(BlockLevelJob::Create(BlockLevelCreateJob {
info: info.clone(),
box_slot,
kind,
propagated_data: self.propagated_data,
}));
}
fn push_block_level_job_for_inline_formatting_context(
@ -669,23 +957,118 @@ impl<'dom> BlockContainerBuilder<'dom, '_> {
})
.clone();
self.block_level_boxes.push(BlockLevelJob {
info,
// FIXME(nox): We should be storing this somewhere.
box_slot: BoxSlot::dummy(),
kind: BlockLevelCreator::SameFormattingContextBlock(
IntermediateBlockContainer::InlineFormattingContext(
BlockContainer::InlineFormattingContext(inline_formatting_context),
self.block_level_boxes
.push(BlockLevelJob::Create(BlockLevelCreateJob {
info,
// FIXME(nox): We should be storing this somewhere.
box_slot: BoxSlot::dummy(),
kind: BlockLevelCreator::SameFormattingContextBlock(
IntermediateBlockContainer::InlineFormattingContext(
BlockContainer::InlineFormattingContext(inline_formatting_context),
),
),
),
propagated_data: self.propagated_data,
});
propagated_data: self.propagated_data,
}));
self.have_already_seen_first_line_for_text_indent = true;
}
}
impl BlockLevelJob<'_> {
impl BlockLevelBox {
pub(crate) fn repair(
&mut self,
context: &LayoutContext,
info: &NodeAndStyleInfo<'_>,
repaired_contents: Contents,
display_inside: DisplayInside,
propagated_data: PropagatedBoxTreeData,
) {
match self {
BlockLevelBox::SameFormattingContextBlock {
base,
contents,
contains_floats,
} => match display_inside {
DisplayInside::Flow { is_list_item } | DisplayInside::FlowRoot { is_list_item } => {
contents.repair(
context,
info,
repaired_contents
.try_into()
.expect("Expect NonReplacedContents, but got ReplacedContents!"),
propagated_data,
is_list_item,
);
// Currently, the incremental layout is not fully ready. When a box is preserved
// during incremental box tree update, its cached layout result is also preserved.
// So, we have to invalidate all caches here to ensure that the layout is recomputed
// correctly.
base.invalidate_all_caches();
base.repair_style(&info.style);
*contains_floats = contents.contains_floats();
},
_ => unreachable!("Expect flow display inside"),
},
BlockLevelBox::Independent(independent_formatting_context) => {
independent_formatting_context.repair(
context,
info,
repaired_contents,
display_inside,
propagated_data,
)
},
BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => {
positioned_box.borrow_mut().context.repair(
context,
info,
repaired_contents,
display_inside,
PropagatedBoxTreeData::default(),
)
},
BlockLevelBox::OutOfFlowFloatBox(float_box) => float_box.contents.repair(
context,
info,
repaired_contents,
display_inside,
propagated_data,
),
BlockLevelBox::OutsideMarker(marker) => {
marker.block_container.repair(
context,
info,
repaired_contents
.try_into()
.expect("Expect NonReplacedContents, but got ReplacedContents!"),
propagated_data,
false,
);
marker.repair_style(context.shared_context(), &info.node, &info.style);
// Currently, the incremental layout is not fully ready. When a box is preserved
// during incremental box tree update, its cached layout result is also preserved.
// So, we have to invalidate all caches here to ensure that the layout is recomputed
// correctly.
marker.invalidate_all_caches();
},
};
}
}
impl BlockLevelRepairJob<'_> {
fn finish(self, context: &LayoutContext) -> ArcRefCell<BlockLevelBox> {
self.repaired_box.borrow_mut().repair(
context,
&self.info,
self.contents,
self.display_inside,
self.propagated_data,
);
self.repaired_box
}
}
impl BlockLevelCreateJob<'_> {
fn finish(self, context: &LayoutContext) -> ArcRefCell<BlockLevelBox> {
let info = &self.info;
let block_level_box = match self.kind {
@ -758,6 +1141,19 @@ impl BlockLevelJob<'_> {
}
}
impl BlockLevelJob<'_> {
fn finish(self, context: &LayoutContext) -> ArcRefCell<BlockLevelBox> {
match self {
BlockLevelJob::Copy(copied_box) => {
copied_box.borrow().invalidate_subtree_caches();
copied_box
},
BlockLevelJob::Repair(repair_job) => repair_job.finish(context),
BlockLevelJob::Create(create_job) => create_job.finish(context),
}
}
}
impl IntermediateBlockContainer {
fn finish(self, context: &LayoutContext, info: &NodeAndStyleInfo<'_>) -> BlockContainer {
match self {

View file

@ -278,6 +278,26 @@ impl InlineItem {
}
}
pub(crate) fn invalidate_subtree_caches(&self) {
match self {
InlineItem::StartInlineBox(inline_box) => {
inline_box.borrow().base.invalidate_all_caches()
},
InlineItem::EndInlineBox | InlineItem::TextRun(..) => {},
InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, ..) => {
positioned_box.borrow().context.invalidate_subtree_caches();
},
InlineItem::OutOfFlowFloatBox(float_box) => {
float_box.borrow().contents.invalidate_subtree_caches()
},
InlineItem::Atomic(independent_formatting_context, ..) => {
independent_formatting_context
.borrow()
.invalidate_subtree_caches();
},
}
}
pub(crate) fn fragments(&self) -> Vec<Fragment> {
match self {
InlineItem::StartInlineBox(inline_box) => inline_box.borrow().base.fragments(),
@ -1713,6 +1733,12 @@ impl InlineFormattingContext {
*self.shared_inline_styles.selected.borrow_mut() = node.to_threadsafe().selected_style();
}
pub(crate) fn invalidate_subtree_caches(&self) {
for inline_item in &self.inline_items {
inline_item.borrow().invalidate_subtree_caches();
}
}
pub(super) fn layout(
&self,
layout_context: &LayoutContext,

View file

@ -87,6 +87,19 @@ impl BlockContainer {
},
}
}
pub(crate) fn invalidate_subtree_caches(&self) {
match self {
BlockContainer::BlockLevelBoxes(block_level_boxes) => {
for block_level_box in block_level_boxes {
block_level_box.borrow().invalidate_subtree_caches();
}
},
BlockContainer::InlineFormattingContext(inline_formating_context) => {
inline_formating_context.invalidate_subtree_caches();
},
}
}
}
#[derive(Debug, MallocSizeOf)]
@ -138,6 +151,25 @@ impl BlockLevelBox {
self.with_base(LayoutBoxBase::invalidate_cached_fragment);
}
pub(crate) fn invalidate_subtree_caches(&self) {
match self {
BlockLevelBox::Independent(independent_formatting_context) => {
independent_formatting_context.invalidate_subtree_caches()
},
BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => {
positioned_box.borrow().context.invalidate_subtree_caches()
},
BlockLevelBox::OutOfFlowFloatBox(float_box) => {
float_box.contents.invalidate_subtree_caches()
},
BlockLevelBox::OutsideMarker(outside_marker) => outside_marker.invalidate_all_caches(),
BlockLevelBox::SameFormattingContextBlock { base, contents, .. } => {
base.invalidate_all_caches();
contents.invalidate_subtree_caches();
},
}
}
pub(crate) fn fragments(&self) -> Vec<Fragment> {
self.with_base(LayoutBoxBase::fragments)
}
@ -427,6 +459,10 @@ impl OutsideMarker {
self.list_item_style = node.style(context);
self.base.repair_style(new_style);
}
fn invalidate_all_caches(&self) {
self.base.invalidate_all_caches();
}
}
impl BlockFormattingContext {

View file

@ -12,10 +12,10 @@ use script::layout_dom::ServoLayoutNode;
use script_layout_interface::wrapper_traits::{
LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
};
use script_layout_interface::{LayoutElementType, LayoutNodeType};
use servo_arc::Arc;
use style::dom::{NodeInfo, TNode};
use style::properties::ComputedValues;
use style::selector_parser::RestyleDamage;
use style::values::computed::Overflow;
use style_traits::CSSPixel;
@ -32,8 +32,8 @@ use crate::fragment_tree::FragmentTree;
use crate::geom::{LogicalVec2, PhysicalSize};
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
use crate::replaced::ReplacedContents;
use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside};
use crate::taffy::{TaffyItemBox, TaffyItemBoxInner};
use crate::style_ext::{Display, DisplayInside};
use crate::taffy::TaffyItemBoxInner;
use crate::{DefiniteContainingBlock, PropagatedBoxTreeData};
#[derive(MallocSizeOf)]
@ -109,185 +109,29 @@ impl BoxTree {
/// 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.
/// loops over its parent. Currently, the only valid candidates for now
/// are absolutely positioned boxes and their originating nodes must have
/// restyle damage that is `REPAIR_BOX` rather than `REBUILD_BOX`. In the
/// future, this may be extended to other types of boxes if the new restyle
/// damage types for incremental layout is ready.
///
/// 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 fn update(context: &LayoutContext, mut dirty_node: ServoLayoutNode<'_>) -> bool {
#[allow(clippy::enum_variant_names)]
enum UpdatePoint {
AbsolutelyPositionedBlockLevelBox(ArcRefCell<BlockLevelBox>),
AbsolutelyPositionedInlineLevelBox(ArcRefCell<InlineItem>, usize),
AbsolutelyPositionedFlexLevelBox(ArcRefCell<FlexLevelBox>),
AbsolutelyPositionedTaffyLevelBox(ArcRefCell<TaffyItemBox>),
}
fn update_point(
node: ServoLayoutNode<'_>,
) -> Option<(Arc<ComputedValues>, DisplayInside, UpdatePoint)> {
if !node.is_element() {
return None;
}
if 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 element_data = node.style_data()?.element_data.borrow();
if !element_data.styles.pseudos.is_empty() {
return None;
}
let layout_data = NodeExt::layout_data(&node)?;
if layout_data.pseudo_before_box.borrow().is_some() {
return None;
}
if layout_data.pseudo_after_box.borrow().is_some() {
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 update_point =
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() =>
{
UpdatePoint::AbsolutelyPositionedBlockLevelBox(block_level_box.clone())
},
_ => return None,
},
LayoutBox::InlineLevel(inline_level_items) => {
let inline_level_box = inline_level_items.first()?;
let InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index) =
&*inline_level_box.borrow()
else {
return None;
};
UpdatePoint::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() =>
{
UpdatePoint::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() =>
{
UpdatePoint::AbsolutelyPositionedTaffyLevelBox(taffy_level_box.clone())
},
_ => return None,
},
};
Some((primary_style.clone(), display_inside, update_point))
}
loop {
let Some((primary_style, display_inside, update_point)) = update_point(dirty_node)
else {
dirty_node = match dirty_node.parent_node() {
Some(parent) => parent,
None => return false,
};
continue;
};
let contents = ReplacedContents::for_element(dirty_node, context)
.map_or_else(|| NonReplacedContents::OfElement.into(), Contents::Replaced);
let info = NodeAndStyleInfo::new(dirty_node, Arc::clone(&primary_style));
let out_of_flow_absolutely_positioned_box = ArcRefCell::new(
AbsolutelyPositionedBox::construct(context, &info, display_inside, contents),
);
match update_point {
UpdatePoint::AbsolutelyPositionedBlockLevelBox(block_level_box) => {
*block_level_box.borrow_mut() = BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(
out_of_flow_absolutely_positioned_box,
);
},
UpdatePoint::AbsolutelyPositionedInlineLevelBox(
inline_level_box,
text_offset_index,
) => {
*inline_level_box.borrow_mut() = InlineItem::OutOfFlowAbsolutelyPositionedBox(
out_of_flow_absolutely_positioned_box,
text_offset_index,
);
},
UpdatePoint::AbsolutelyPositionedFlexLevelBox(flex_level_box) => {
*flex_level_box.borrow_mut() = FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(
out_of_flow_absolutely_positioned_box,
);
},
UpdatePoint::AbsolutelyPositionedTaffyLevelBox(taffy_level_box) => {
taffy_level_box.borrow_mut().taffy_level_box =
TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(
out_of_flow_absolutely_positioned_box,
);
},
}
break;
}
// We are going to rebuild the box tree from the update point downward, but this update
// point is an absolute, which means that it needs to be laid out again in the containing
// block for absolutes, which is established by one of its ancestors. In addition,
// absolutes, when laid out, can produce more absolutes (either fixed or absolutely
// positioned) elements, so there may be yet more layout that has to happen in this
// ancestor.
//
// We do not know which ancestor is the one that established the containing block for this
// update point, so just invalidate the fragment cache of all ancestors, meaning that even
// though the box tree is preserved, the fragment tree from the root to the update point and
// all of its descendants will need to be rebuilt. This isn't as bad as it seems, because
// siblings and siblings of ancestors of this path through the tree will still have cached
// fragments.
//
// TODO: Do better. This is still a very crude way to do incremental layout.
while let Some(parent_node) = dirty_node.parent_node() {
parent_node.invalidate_cached_fragment();
dirty_node = parent_node;
}
true
/// There are various reasons why only absolutely positioned boxes can be
/// selected as the update point:
/// * it needs a bit of trick to incrementally update the `contains_floats`
/// for a block formatting context and `SameFormattingContext`, which make
/// the incremental update less eligible.
/// * the propagation of box tree data towards node descendants is hard to do
/// incrementally with our current representation of boxes. To support
/// incremental update, we have to store it at the `LayoutBoxBase`.
/// * We have to clear all layout caches for ancestors of the update point
/// when the incremental layout is not fully ready. However, it is really hard
/// to do that incrementally because a box does not hold a reference to its
/// parent box with current representation of boxes. The anonymous ancestor
/// boxes can not be visited. Thus, select the absolutely positioned boxes
/// as the update point, this is not a problem.
pub fn update(context: &LayoutContext, dirty_node: ServoLayoutNode<'_>) -> bool {
let mut helper = BoxTreeUpdateHelper::new(context, dirty_node);
helper.try_update_box_tree()
}
}
@ -295,6 +139,8 @@ fn construct_for_root_element(
context: &LayoutContext,
root_element: ServoLayoutNode<'_>,
) -> Vec<ArcRefCell<BlockLevelBox>> {
root_element.clear_restyle_damage();
let info = NodeAndStyleInfo::new(root_element, root_element.style(context.shared_context()));
let box_style = info.style.get_box();
@ -400,3 +246,201 @@ impl BoxTree {
)
}
}
struct BoxTreeUpdateHelper<'style, 'dom> {
context: &'style LayoutContext<'style>,
dirty_node: ServoLayoutNode<'dom>,
primary_style: Option<Arc<ComputedValues>>,
display_inside: Option<DisplayInside>,
}
enum UpdatePoint {
DoNotNeedUpdateTree,
RootElementPrimaryBox(ArcRefCell<BlockLevelBox>),
AbsolutelyPositionedBox(ArcRefCell<AbsolutelyPositionedBox>),
}
impl<'style, 'dom> BoxTreeUpdateHelper<'style, 'dom> {
fn new(context: &'style LayoutContext<'style>, dirty_node: ServoLayoutNode<'dom>) -> Self {
Self {
context,
dirty_node,
primary_style: None,
display_inside: None,
}
}
fn try_update_box_tree(&mut self) -> bool {
loop {
let Some(update_point) = self.update_point() else {
self.dirty_node = match self.dirty_node.parent_node() {
Some(parent) => parent,
None => return false,
};
continue;
};
match update_point {
UpdatePoint::DoNotNeedUpdateTree => {},
_ => {
self.update_box_tree(update_point);
self.invalidate_layout_cache();
self.clear_ancestor_restyle_damage();
},
};
break;
}
true
}
fn update_point(&mut self) -> Option<UpdatePoint> {
if !self.dirty_node.is_element() {
return None;
}
let element_data = self.dirty_node.style_data()?.element_data.borrow();
if !element_data.damage.contains(RestyleDamage::REPAIR_BOX) {
return Some(UpdatePoint::DoNotNeedUpdateTree);
}
if element_data.damage.contains(RestyleDamage::REBUILD_BOX) {
return None;
}
let primary_style = element_data.styles.primary();
let box_style = primary_style.get_box();
let display_inside = match Display::from(box_style.display) {
Display::GeneratingBox(generating_box) => generating_box.display_inside(),
_ => return None,
};
let layout_data = NodeExt::layout_data(&self.dirty_node)?;
let layout_box = &*AtomicRef::filter_map(layout_data.self_box.borrow(), Option::as_ref)?;
if self.dirty_node.to_threadsafe().as_element()?.is_root() {
match layout_box {
LayoutBox::BlockLevel(block_level_box) => {
self.primary_style = Some(primary_style.clone());
self.display_inside = Some(display_inside);
return Some(UpdatePoint::RootElementPrimaryBox(block_level_box.clone()));
},
// Unreachable because the style crate adjusts the computed values:
// https://drafts.csswg.org/css-display-3/#transformations
// “'display' of 'contents' computes to 'block' on the root element”
_ => unreachable!("Root element should only has display: block"),
}
}
if !box_style.position.is_absolutely_positioned() {
return None;
}
let update_point = match layout_box {
LayoutBox::DisplayContents(..) => return None,
LayoutBox::BlockLevel(block_level_box) => match &*block_level_box.borrow() {
BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => {
UpdatePoint::AbsolutelyPositionedBox(positioned_box.clone())
},
_ => return None,
},
LayoutBox::InlineLevel(inline_level_items) => {
let inline_level_item = inline_level_items.first()?;
match &*inline_level_item.borrow() {
InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, _) => {
UpdatePoint::AbsolutelyPositionedBox(positioned_box.clone())
},
_ => return None,
}
},
LayoutBox::FlexLevel(flex_level_box) => match &*flex_level_box.borrow() {
FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => {
UpdatePoint::AbsolutelyPositionedBox(positioned_box.clone())
},
_ => return None,
},
LayoutBox::TableLevelBox(..) => return None,
LayoutBox::TaffyItemBox(taffy_item_box) => {
match &taffy_item_box.borrow().taffy_level_box {
TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(positioned_box) => {
UpdatePoint::AbsolutelyPositionedBox(positioned_box.clone())
},
_ => return None,
}
},
};
self.primary_style = Some(primary_style.clone());
self.display_inside = Some(display_inside);
Some(update_point)
}
/// We are going to repair the box tree from the update point downward, but this update
/// point is an absolute, which means that it needs to be laid out again in the containing
/// block for absolutes, which is established by on if its ancestors. In addition,
/// absolutes, when laid out, can produce more absolutes (either fixed or absolutely
/// positioned) elements, so there may be yet more layout that has to happen in this
/// ancestor.
///
/// We do not know which ancestor is the one that established the containing block for this
/// update point, so just invalidate the fragment cache of all ancestors, meaning that even
/// though the box tree is preserved, the fragment tree from the root to the update point and
/// all of its descendants will need to be rebuilt. This isn't as bad as it seems, because
/// slibings and slibings of ancestors of this path through the tree will still have cached
/// fragments.
///
/// TODO: Do better. This is still a very crude way to do incremental layout.
fn invalidate_layout_cache(&self) {
let mut invalidation_start_point = self.dirty_node;
while let Some(parent_node) = invalidation_start_point.parent_node() {
parent_node.invalidate_cached_fragment();
invalidation_start_point = parent_node;
}
}
/// We have already propagate up some restyle damage from descendants to
/// the ancestors of update point, but we are just going to traverse the
/// subtree from the update point rather than the root element during
/// incremental update. Thus, clear all ancestor's restyle damage now.
fn clear_ancestor_restyle_damage(&self) {
let mut inclusive_ancestor = Some(self.dirty_node);
while let Some(node) = inclusive_ancestor {
node.clear_restyle_damage();
inclusive_ancestor = node.parent_node();
}
}
fn update_box_tree(&self, update_point: UpdatePoint) {
let context = self.context;
let display_inside = self.display_inside.unwrap();
let info = NodeAndStyleInfo::new(self.dirty_node, self.primary_style.clone().unwrap());
let contents = ReplacedContents::for_element(self.dirty_node, context)
.map_or_else(|| NonReplacedContents::OfElement.into(), Contents::Replaced);
match update_point {
UpdatePoint::DoNotNeedUpdateTree => unreachable!("Should have been filtered out"),
UpdatePoint::RootElementPrimaryBox(block_level_box) => {
block_level_box.borrow_mut().repair(
context,
&info,
contents,
display_inside,
PropagatedBoxTreeData::default(),
);
},
UpdatePoint::AbsolutelyPositionedBox(positioned_box) => {
positioned_box.borrow_mut().context.repair(
context,
&info,
contents,
display_inside,
PropagatedBoxTreeData::default(),
);
},
}
}
}

View file

@ -2,6 +2,8 @@
* 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 std::mem;
use app_units::Au;
use malloc_size_of_derive::MallocSizeOf;
use script::layout_dom::{ServoLayoutElement, ServoLayoutNode};
@ -11,7 +13,7 @@ use style::properties::ComputedValues;
use style::selector_parser::PseudoElement;
use crate::context::LayoutContext;
use crate::dom_traversal::{Contents, NodeAndStyleInfo};
use crate::dom_traversal::{Contents, NodeAndStyleInfo, NonReplacedContents};
use crate::flexbox::FlexContainer;
use crate::flow::BlockFormattingContext;
use crate::fragment_tree::{BaseFragmentInfo, FragmentFlags};
@ -143,6 +145,39 @@ impl IndependentFormattingContext {
}
}
pub fn repair(
&mut self,
context: &LayoutContext,
node_and_style_info: &NodeAndStyleInfo,
contents: Contents,
display_inside: DisplayInside,
propagated_data: PropagatedBoxTreeData,
) {
match &mut self.contents {
IndependentFormattingContextContents::NonReplaced(
independent_non_replaced_contents,
) => {
independent_non_replaced_contents.repair(
context,
node_and_style_info,
contents
.try_into()
.expect("Expect NonReplacedContents, but got ReplacedContents!"),
display_inside,
propagated_data,
);
},
IndependentFormattingContextContents::Replaced(..) => {},
}
// Currently, the incremental layout is not fully ready. When a box is preserved
// during incremental box tree update, its cached layout result is also preserved.
// So, we have to invalidate all caches here to ensure that the layout is recomputed
// correctly.
self.base.invalidate_all_caches();
self.base.repair_style(&node_and_style_info.style);
}
pub fn is_replaced(&self) -> bool {
matches!(
self.contents,
@ -234,6 +269,16 @@ impl IndependentFormattingContext {
IndependentFormattingContextContents::Replaced(..) => {},
}
}
pub(crate) fn invalidate_subtree_caches(&self) {
self.base.invalidate_all_caches();
match &self.contents {
IndependentFormattingContextContents::NonReplaced(content) => {
content.invalidate_subtree_caches();
},
IndependentFormattingContextContents::Replaced(..) => {},
}
}
}
impl IndependentNonReplacedContents {
@ -376,6 +421,86 @@ impl IndependentNonReplacedContents {
IndependentNonReplacedContents::Table(table) => table.repair_style(context, new_style),
}
}
fn repair(
&mut self,
context: &LayoutContext,
node_and_style_info: &NodeAndStyleInfo,
non_replaced_contents: NonReplacedContents,
display_inside: DisplayInside,
propagated_data: PropagatedBoxTreeData,
) {
match self {
IndependentNonReplacedContents::Flow(block_formatting_context) => {
match display_inside {
DisplayInside::Flow { is_list_item } |
DisplayInside::FlowRoot { is_list_item } => {
block_formatting_context.repair(
context,
node_and_style_info,
non_replaced_contents,
propagated_data,
is_list_item,
);
},
_ => unreachable!("Expect flow display inside mode"),
}
},
IndependentNonReplacedContents::Flex(old_flex_container) => {
let new_flex_container = FlexContainer::construct(
context,
node_and_style_info,
non_replaced_contents,
propagated_data,
);
let _ = mem::replace(old_flex_container, new_flex_container);
},
IndependentNonReplacedContents::Grid(old_taffy_container) => {
let new_taffy_container = TaffyContainer::construct(
context,
node_and_style_info,
non_replaced_contents,
propagated_data,
);
let _ = mem::replace(old_taffy_container, new_taffy_container);
},
IndependentNonReplacedContents::Table(old_table) => {
let table_grid_style = context
.shared_context()
.stylist
.style_for_anonymous::<ServoLayoutElement>(
&context.shared_context().guards,
&PseudoElement::ServoTableGrid,
&node_and_style_info.style,
);
let new_table = Table::construct(
context,
node_and_style_info,
table_grid_style,
non_replaced_contents,
propagated_data,
);
let _ = mem::replace(old_table, new_table);
},
}
}
fn invalidate_subtree_caches(&self) {
match self {
IndependentNonReplacedContents::Flow(block_formatting_context) => {
block_formatting_context
.contents
.invalidate_subtree_caches();
},
IndependentNonReplacedContents::Flex(flex_container) => {
flex_container.invalidate_subtree_caches()
},
IndependentNonReplacedContents::Grid(taffy_container) => {
taffy_container.invalidate_subtree_caches()
},
IndependentNonReplacedContents::Table(table) => table.invalidate_subtree_caches(),
}
}
}
impl ComputeInlineContentSizes for IndependentNonReplacedContents {

View file

@ -74,6 +74,12 @@ impl LayoutBoxBase {
let _ = self.cached_layout_result.borrow_mut().take();
}
pub(crate) fn invalidate_all_caches(&self) {
let _ = self.cached_inline_content_size.borrow_mut().take();
self.invalidate_cached_fragment();
self.clear_fragments();
}
pub(crate) fn fragments(&self) -> Vec<Fragment> {
self.fragments.borrow().clone()
}

View file

@ -778,7 +778,7 @@ impl LayoutThread {
compute_damage_and_repair_style(layout_context.shared_context(), root_node);
if viewport_changed {
damage = RestyleDamage::REBUILD_BOX;
} else if !damage.contains(RestyleDamage::REBUILD_BOX) {
} else if !damage.will_change_box_subtree() {
layout_context.style_context.stylist.rule_tree().maybe_gc();
return damage;
}

View file

@ -207,6 +207,35 @@ impl Table {
new_style,
);
}
pub(crate) fn invalidate_subtree_caches(&self) {
for caption in &self.captions {
caption.borrow().context.invalidate_subtree_caches();
}
for column_group in &self.column_groups {
column_group.borrow().base.invalidate_all_caches();
}
for row_group in &self.row_groups {
row_group.borrow().base.invalidate_all_caches();
}
for column in &self.columns {
column.borrow().base.invalidate_all_caches();
}
for row in &self.rows {
row.borrow().base.invalidate_all_caches();
}
for slot_cell_row in &self.slots {
for slot in slot_cell_row {
match slot {
TableSlot::Cell(cell) => {
cell.borrow().base.invalidate_all_caches();
cell.borrow().contents.contents.invalidate_subtree_caches();
},
TableSlot::Spanned(..) | TableSlot::Empty => {},
}
}
}
}
}
type TableSlotCoordinates = Point2D<usize, UnknownUnit>;

View file

@ -73,6 +73,12 @@ impl TaffyContainer {
pub(crate) fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
self.style = new_style.clone();
}
pub(crate) fn invalidate_subtree_caches(&self) {
for taffy_level_box in &self.children {
taffy_level_box.borrow_mut().invalidate_subtree_caches();
}
}
}
#[derive(MallocSizeOf)]
@ -138,6 +144,21 @@ impl TaffyItemBox {
}
}
pub(crate) fn invalidate_subtree_caches(&mut self) {
self.taffy_layout = Default::default();
self.positioning_context = PositioningContext::default();
self.child_fragments.clear();
match self.taffy_level_box {
TaffyItemBoxInner::InFlowBox(ref independent_formatting_context) => {
independent_formatting_context.invalidate_subtree_caches();
},
TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(ref positioned_box) => {
positioned_box.borrow().context.invalidate_subtree_caches();
},
}
}
pub(crate) fn fragments(&self) -> Vec<Fragment> {
match self.taffy_level_box {
TaffyItemBoxInner::InFlowBox(ref independent_formatting_context) => {

View file

@ -99,6 +99,17 @@ pub(crate) fn compute_damage_and_repair_style(
compute_damage_and_repair_style_inner(context, node, RestyleDamage::empty())
}
fn need_repair_style_before_box_tree_update(damage: RestyleDamage) -> bool {
// Repair the style at the node's layout objects only when itself has
// restyle damage but it's boxes will be kept unchanged, otherwise,
// the style will be repaired during box tree update.
if damage.is_empty() || damage.will_change_box_subtree() {
return false;
}
true
}
pub(crate) fn compute_damage_and_repair_style_inner(
context: &SharedStyleContext,
node: ServoLayoutNode<'_>,
@ -106,7 +117,6 @@ pub(crate) fn compute_damage_and_repair_style_inner(
) -> RestyleDamage {
let original_damage;
let damage;
{
let mut element_data = node
.style_data()
@ -114,28 +124,42 @@ pub(crate) fn compute_damage_and_repair_style_inner(
.element_data
.borrow_mut();
original_damage = std::mem::take(&mut element_data.damage);
original_damage = element_data.damage;
// The damage that can cause the box subtree to change will be cleaned
// after incremental box tree update.
if !element_data.damage.will_change_box_subtree() {
let _ = 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 damage;
return damage.propagate_up_damage();
}
}
}
};
let mut propagated_damage = damage.propagate_up_damage();
let propagated_down_damage = damage.propagate_down_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);
propagated_damage |=
compute_damage_and_repair_style_inner(context, child, propagated_down_damage);
}
}
if !propagated_damage.contains(RestyleDamage::REBUILD_BOX) &&
!original_damage.contains(RestyleDamage::REBUILD_BOX)
if need_repair_style_before_box_tree_update(propagated_damage) &&
need_repair_style_before_box_tree_update(original_damage)
{
node.repair_style(context);
}
if propagated_damage.contains(RestyleDamage::REPAIR_BOX) {
let mut element_data = node.style_data().unwrap().element_data.borrow_mut();
element_data.damage |= RestyleDamage::REPAIR_BOX;
}
propagated_damage
}

View file

@ -940,7 +940,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::OtherNodeDamage);
node.dirty(NodeDamage::NodeContentOrHeritageDamaged);
}
/// Remove any existing association between the provided id and any elements in this document.

View file

@ -360,8 +360,9 @@ impl Element {
// NodeStyleDamaged, but I'm preserving existing behavior.
restyle.hint.insert(RestyleHint::RESTYLE_SELF);
if damage == NodeDamage::OtherNodeDamage {
doc.note_node_with_dirty_descendants(self.upcast());
if damage == NodeDamage::NodeContentOrHeritageDamaged {
restyle.damage = RestyleDamage::repair();
} else if damage == NodeDamage::OtherNodeDamage {
restyle.damage = RestyleDamage::reconstruct();
}
}

View file

@ -3933,9 +3933,17 @@ impl VirtualMethods for Node {
/// A summary of the changes that happened to a node.
#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
// FIXME(joedow): The enum-variant-name-threshold's default is 3, so add the
// `NodeContentOrHeritageDamaged` variant leads to the lint being triggered, reference:
// https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names
//
// Allow this lint temperarily, and its fix will be done in a follow-up PR.
#[allow(clippy::enum_variant_names)]
pub(crate) enum NodeDamage {
/// The node's `style` attribute changed.
NodeStyleDamaged,
/// The node's content or heritage changed: children removed or added, text content changed.
NodeContentOrHeritageDamaged,
/// Other parts of a node changed; attributes, text content, etc.
OtherNodeDamage,
}