use std::cell::Cell;
use std::sync::Arc;
use bitflags::Flags;
use layout_api::LayoutDamage;
use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode};
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 crate::BoxTree;
use crate::context::LayoutContext;
use crate::dom::{DOMLayoutData, NodeExt};
pub struct RecalcStyle<'a> {
context: &'a LayoutContext<'a>,
}
impl<'a> RecalcStyle<'a> {
pub(crate) fn new(context: &'a LayoutContext<'a>) -> Self {
RecalcStyle { context }
}
pub(crate) fn context(&self) -> &LayoutContext<'a> {
self.context
}
}
#[expect(unsafe_code)]
impl<'dom, E> DomTraversal<E> for RecalcStyle<'_>
where
E: TElement,
E::ConcreteNode: 'dom + LayoutNode<'dom>,
{
fn process_preorder<F>(
&self,
traversal_data: &PerLevelTraversalData,
context: &mut StyleContext<E>,
node: E::ConcreteNode,
note_child: F,
) 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>();
}
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();
}
}
#[inline]
fn needs_postorder_traversal() -> bool {
false
}
fn process_postorder(&self, _style_context: &mut StyleContext<E>, _node: E::ConcreteNode) {
panic!("this should never be called")
}
fn text_node_needs_traversal(node: E::ConcreteNode, parent_data: &ElementData) -> bool {
node.layout_data().is_none() || !parent_data.damage.is_empty()
}
fn shared_context(&self) -> &SharedStyleContext<'_> {
&self.context.style_context
}
}
#[servo_tracing::instrument(skip_all)]
pub(crate) fn compute_damage_and_rebuild_box_tree(
box_tree: &mut Option<Arc<BoxTree>>,
layout_context: &LayoutContext,
dirty_root: ServoLayoutNode<'_>,
root_node: ServoLayoutNode<'_>,
damage_from_environment: RestyleDamage,
) -> RestyleDamage {
let restyle_damage = compute_damage_and_rebuild_box_tree_inner(
layout_context,
dirty_root.to_threadsafe(),
damage_from_environment,
);
let layout_damage: LayoutDamage = restyle_damage.into();
if box_tree.is_none() {
*box_tree = Some(Arc::new(BoxTree::construct(layout_context, root_node)));
return restyle_damage;
}
if !restyle_damage.contains(RestyleDamage::RELAYOUT) {
return restyle_damage;
}
let mut needs_box_tree_rebuild = layout_damage.needs_new_box();
let mut damage_for_ancestors = LayoutDamage::RECOMPUTE_INLINE_CONTENT_SIZES;
let mut maybe_parent_node = dirty_root.traversal_parent();
while let Some(parent_node) = maybe_parent_node {
let threadsafe_parent_node = parent_node.as_node().to_threadsafe();
if needs_box_tree_rebuild &&
threadsafe_parent_node
.rebuild_box_tree_from_independent_formatting_context(layout_context)
{
needs_box_tree_rebuild = false;
}
if needs_box_tree_rebuild {
threadsafe_parent_node.unset_all_boxes();
} else {
let new_damage_for_ancestors = Cell::new(LayoutDamage::empty());
threadsafe_parent_node.with_layout_box_base_including_pseudos(|base| {
new_damage_for_ancestors.set(
new_damage_for_ancestors.get() |
base.add_damage(Default::default(), damage_for_ancestors),
);
});
damage_for_ancestors = new_damage_for_ancestors.get();
}
maybe_parent_node = parent_node.traversal_parent();
}
if needs_box_tree_rebuild {
*box_tree = Some(Arc::new(BoxTree::construct(layout_context, root_node)));
}
restyle_damage
}
pub(crate) fn compute_damage_and_rebuild_box_tree_inner(
layout_context: &LayoutContext,
node: ServoThreadSafeLayoutNode<'_>,
damage_from_parent: RestyleDamage,
) -> RestyleDamage {
let element_data = &node
.style_data()
.expect("Should not run `compute_damage` before styling.")
.element_data;
let (element_damage, is_display_none) = {
let mut element_data = element_data.borrow_mut();
(
std::mem::take(&mut element_data.damage),
element_data.styles.is_display_none(),
)
};
let mut element_and_parent_damage = element_damage | damage_from_parent;
if is_display_none {
node.unset_all_boxes();
return element_and_parent_damage;
}
let mut damage_for_children = element_and_parent_damage;
damage_for_children.truncate();
let rebuild_children = element_damage.contains(LayoutDamage::box_damage()) ||
(damage_from_parent.contains(LayoutDamage::box_damage()) &&
!node.isolates_damage_for_damage_propagation());
if rebuild_children {
damage_for_children.insert(LayoutDamage::box_damage());
} else if element_and_parent_damage.contains(RestyleDamage::RELAYOUT) &&
!element_damage.contains(RestyleDamage::RELAYOUT) &&
node.isolates_damage_for_damage_propagation()
{
damage_for_children.remove(RestyleDamage::RELAYOUT);
element_and_parent_damage.remove(RestyleDamage::RELAYOUT);
}
let mut damage_from_children = RestyleDamage::empty();
for child in node.children() {
if child.is_element() {
damage_from_children |= compute_damage_and_rebuild_box_tree_inner(
layout_context,
child,
damage_for_children,
);
}
}
let mut layout_damage_for_parent =
element_and_parent_damage | (damage_from_children & RestyleDamage::RELAYOUT);
let element_or_ancestors_need_rebuild =
element_and_parent_damage.contains(LayoutDamage::descendant_has_box_damage());
let descendant_needs_rebuild =
damage_from_children.contains(LayoutDamage::descendant_has_box_damage());
if element_or_ancestors_need_rebuild || descendant_needs_rebuild {
if damage_from_parent.contains(LayoutDamage::descendant_has_box_damage()) ||
!node.rebuild_box_tree_from_independent_formatting_context(layout_context)
{
node.unset_all_boxes();
layout_damage_for_parent
.insert(LayoutDamage::descendant_has_box_damage() | RestyleDamage::RELAYOUT);
} else {
layout_damage_for_parent.remove(LayoutDamage::box_damage());
layout_damage_for_parent
.insert(RestyleDamage::RELAYOUT | LayoutDamage::recompute_inline_content_sizes());
}
} else {
if (element_and_parent_damage | damage_from_children).contains(RestyleDamage::RELAYOUT) {
let extra_layout_damage_for_parent = Cell::new(LayoutDamage::empty());
node.with_layout_box_base_including_pseudos(|base| {
extra_layout_damage_for_parent.set(
extra_layout_damage_for_parent.get() |
base.add_damage(element_damage.into(), damage_from_children.into()),
);
});
layout_damage_for_parent.insert(extra_layout_damage_for_parent.get().into());
}
if !element_damage.is_empty() {
node.repair_style(&layout_context.style_context);
}
}
layout_damage_for_parent
}