use blitz_traits::node_id::NodeId;
#[cfg(feature = "svg")]
use markup5ever::local_name;
use crate::net::ResourceHandler;
use crate::node::{Node, NodeFlags};
use crate::tree::NodeTree;
use crate::{
BaseDocument, net::ImageHandler, node::ImageResourceData, node::Status, util::ImageLayerKind,
};
use style::matching::StyleChange;
use style::properties::ComputedValues;
use style::properties::generated::longhands::position::computed_value::T as Position;
use style::selector_parser::{PseudoElement, RestyleDamage};
use style::shared_lock::StylesheetGuards;
use style::url::ComputedUrl;
use style::values::generics::image::Image as StyloImage;
use style::values::specified::align::AlignFlags;
use style::values::specified::box_::DisplayInside;
use style::values::specified::box_::DisplayOutside;
use thin_vec::ThinVec;
pub(crate) const ONLY_RELAYOUT: RestyleDamage =
RestyleDamage::from_bits_retain(0b_0000_0000_0000_1000);
pub(crate) const REORDER_CHILDREN: RestyleDamage =
RestyleDamage::from_bits_retain(0b_0000_0000_0001_0000);
pub(crate) const CONSTRUCT_BOX: RestyleDamage =
RestyleDamage::from_bits_retain(0b_0000_0000_0010_0000);
pub(crate) const CONSTRUCT_FC: RestyleDamage =
RestyleDamage::from_bits_retain(0b_0000_0000_0100_0000);
pub(crate) const CONSTRUCT_DESCENDENT: RestyleDamage =
RestyleDamage::from_bits_retain(0b_0000_0000_1000_0000);
pub(crate) const ALL_DAMAGE: RestyleDamage =
RestyleDamage::from_bits_retain(0b_0000_0000_1110_1111);
impl BaseDocument {
pub(crate) fn propagate_damage_flags(&mut self, node_id: NodeId) -> RestyleDamage {
let own_damage = if let Some(data) = self.nodes[node_id]
.try_stylo_element_data_mut()
.and_then(|s| s.get_mut())
{
data.damage
} else {
return RestyleDamage::empty();
};
let mut damage = own_damage;
{
let node = &self.nodes[node_id];
if damage.is_empty() && !node.has_damaged_descendants() && !node.is_anonymous() {
return RestyleDamage::empty();
}
}
let mut damage_from_children = RestyleDamage::empty();
let children = std::mem::take(&mut self.nodes[node_id].children);
for child in children.iter() {
damage_from_children |= self.propagate_damage_flags(*child);
}
if let Some(before_id) = self.nodes[node_id].before() {
damage_from_children |= self.propagate_damage_flags(before_id);
}
if let Some(after_id) = self.nodes[node_id].after() {
damage_from_children |= self.propagate_damage_flags(after_id);
}
let reorder = damage_from_children.contains(REORDER_CHILDREN)
&& matches!(
self.nodes[node_id].display_constructed_as().inside(),
DisplayInside::Flex | DisplayInside::Grid
);
damage_from_children.remove(REORDER_CHILDREN);
damage |= damage_from_children;
self.nodes[node_id].children = children;
if damage.contains(CONSTRUCT_BOX) {
damage.insert(RestyleDamage::RELAYOUT);
}
let mut damage_for_parent = damage;
if reorder {
damage.insert(CONSTRUCT_BOX);
damage_for_parent.insert(CONSTRUCT_DESCENDENT);
}
#[cfg(feature = "svg")]
if !damage.is_empty()
&& self.nodes[node_id]
.data
.is_element_with_tag_name(&local_name!("svg"))
{
damage.insert(CONSTRUCT_BOX);
}
if !own_damage.is_empty() && !damage.intersects(CONSTRUCT_BOX | CONSTRUCT_FC) {
let anonymous_damage = self.refresh_anonymous_block_styles(node_id);
damage |= anonymous_damage;
damage_for_parent |= anonymous_damage;
}
let node = &mut self.nodes[node_id];
let mut invalidate_anonymous_ancestors = false;
if damage.intersects(ONLY_RELAYOUT | CONSTRUCT_BOX) {
node.invalidate_layout_cache();
invalidate_anonymous_ancestors = true;
damage.remove(ONLY_RELAYOUT);
}
node.set_damage(damage);
if invalidate_anonymous_ancestors {
let mut current = self.nodes[node_id].layout_parent.get();
while let Some(ancestor) = current.and_then(|id| self.nodes.get_mut(id)) {
if !ancestor.is_anonymous() {
break;
}
ancestor.invalidate_layout_cache();
current = ancestor.layout_parent.get();
}
}
damage_for_parent
}
fn refresh_anonymous_block_styles(&mut self, node_id: NodeId) -> RestyleDamage {
if self.nodes[node_id].anonymous_blocks.is_empty() {
return RestyleDamage::empty();
}
let style = {
let Some(parent_style) = self.nodes[node_id].primary_styles() else {
return RestyleDamage::empty();
};
let read_guard = self.guard.read();
let guards = StylesheetGuards::same(&read_guard);
self.stylist.style_for_anonymous::<&Node>(
&guards,
&PseudoElement::ServoAnonymousBox,
&parent_style,
)
};
let anonymous_blocks = std::mem::take(&mut self.nodes[node_id].anonymous_blocks);
let mut damage_for_parent = RestyleDamage::empty();
for &anonymous_id in &anonymous_blocks {
let (mut damage, changed) = {
let Some(node) = self.nodes.get_mut(anonymous_id) else {
continue;
};
let Some(mut data) = node.try_stylo_element_data_mut().and_then(|s| s.get_mut())
else {
continue;
};
let Some(old_style) = data.styles.primary.as_ref() else {
continue;
};
let diff = RestyleDamage::compute_style_difference::<&Node>(old_style, &style);
let changed = matches!(diff.change, StyleChange::Changed { .. });
if changed {
data.styles.primary = Some(style.clone());
data.set_restyled();
}
(data.damage | diff.damage, changed)
};
if changed && !damage.intersects(CONSTRUCT_BOX | CONSTRUCT_FC) {
damage |= self.refresh_anonymous_block_styles(anonymous_id);
}
if damage.contains(CONSTRUCT_BOX) {
damage.insert(RestyleDamage::RELAYOUT);
}
damage_for_parent |= damage;
let node = &mut self.nodes[anonymous_id];
if damage.intersects(ONLY_RELAYOUT | CONSTRUCT_BOX) {
node.invalidate_layout_cache();
damage.remove(ONLY_RELAYOUT);
}
node.set_damage(damage);
if !damage.is_empty() {
node.mark_damaged();
}
}
self.nodes[node_id].anonymous_blocks = anonymous_blocks;
damage_for_parent
}
pub(crate) fn mark_all_damaged(&mut self) {
for (_, node) in self.nodes.iter_mut() {
node.insert_damage(ALL_DAMAGE);
}
}
pub(crate) fn clear_damage_and_dirty_flags(&mut self, node_id: NodeId) {
let Some(node) = self.nodes.get_mut(node_id) else {
return;
};
let is_anonymous = node.is_anonymous();
let has_damage = node.damage().is_some_and(|d| !d.is_empty());
if !has_damage && !node.has_damaged_descendants() && !is_anonymous {
return;
}
let anonymous_blocks = std::mem::take(&mut node.anonymous_blocks);
let children = std::mem::take(&mut node.children);
let (before, after) = (node.before(), node.after());
for anon_id in anonymous_blocks.iter() {
self.clear_damage_and_dirty_flags(*anon_id);
}
if !is_anonymous {
for child in children.iter() {
self.clear_damage_and_dirty_flags(*child);
}
}
if let Some(before_id) = before {
self.clear_damage_and_dirty_flags(before_id);
}
if let Some(after_id) = after {
self.clear_damage_and_dirty_flags(after_id);
}
let node = &mut self.nodes[node_id];
node.anonymous_blocks = anonymous_blocks;
node.children = children;
node.clear_damage_mut();
node.unset_damaged_descendants();
node.unset_dirty_descendants();
}
}
pub(crate) fn compute_layout_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
let box_tree_needs_rebuild = || {
let old_box = old.get_box();
let new_box = new.get_box();
if old_box.display != new_box.display
|| old_box.float != new_box.float
|| old_box.position != new_box.position
|| old_box.contain != new_box.contain
|| old.clone_visibility() != new.clone_visibility()
{
return true;
}
if old.get_font() != new.get_font() {
return true;
}
if new_box.display.outside() == DisplayOutside::Block
&& new_box.display.inside() == DisplayInside::Flow
{
let alignment_establishes_new_block_formatting_context = |style: &ComputedValues| {
style.get_position().align_content.primary() != AlignFlags::NORMAL
};
let old_column = old.get_column();
let new_column = new.get_column();
if old_box.overflow_x.is_scrollable() != new_box.overflow_x.is_scrollable()
|| old_column.is_multicol() != new_column.is_multicol()
|| old_column.column_span != new_column.column_span
|| alignment_establishes_new_block_formatting_context(old)
!= alignment_establishes_new_block_formatting_context(new)
{
return true;
}
}
if old_box.display.is_list_item() {
let old_list = old.get_list();
let new_list = new.get_list();
if old_list.list_style_position != new_list.list_style_position
|| old_list.list_style_image != new_list.list_style_image
|| (new_list.list_style_image == StyloImage::None
&& old_list.list_style_type != new_list.list_style_type)
{
return true;
}
}
if new.is_pseudo_style() && old.get_counters().content != new.get_counters().content {
return true;
}
false
};
let text_shaping_needs_recollect = || {
if old.clone_direction() != new.clone_direction()
|| old.clone_unicode_bidi() != new.clone_unicode_bidi()
{
return true;
}
let old_text = old.get_inherited_text();
let new_text = new.get_inherited_text();
if !std::ptr::eq(old_text, new_text)
&& (old_text.white_space_collapse != new_text.white_space_collapse
|| old_text.text_transform != new_text.text_transform
|| old_text.word_break != new_text.word_break
|| old_text.line_break != new_text.line_break
|| old_text.overflow_wrap != new_text.overflow_wrap
|| old_text.letter_spacing != new_text.letter_spacing
|| old_text.word_spacing != new_text.word_spacing
|| old_text.text_rendering != new_text.text_rendering)
{
return true;
}
false
};
#[allow(
clippy::if_same_then_else,
reason = "these branches will soon be different"
)]
if box_tree_needs_rebuild() {
ALL_DAMAGE
} else if text_shaping_needs_recollect() {
ALL_DAMAGE
} else if old.get_position().order != new.get_position().order
&& !matches!(
new.get_box().position,
Position::Absolute | Position::Fixed
)
{
RestyleDamage::RELAYOUT | REORDER_CHILDREN
} else {
RestyleDamage::RELAYOUT
}
}
pub(crate) fn sort_layout_children_by_order(
nodes: &NodeTree,
layout_children: &mut ThinVec<NodeId>,
) {
if layout_children.iter().all(|id| nodes[*id].order() == 0) {
return;
}
layout_children.sort_by_cached_key(|id| nodes[*id].order());
}
impl BaseDocument {
pub(crate) fn invalidate_inline_contexts(&mut self) {
let scale = self.viewport.scale();
let font_ctx = &self.font_ctx;
let layout_ctx = &mut self.layout_ctx;
let mut anon_nodes = Vec::new();
for (_, node) in self.nodes.iter_mut() {
if !(node.flags.contains(NodeFlags::IS_IN_DOCUMENT)) {
continue;
}
let Some(element) = node.data.downcast_element_mut() else {
continue;
};
if element.inline_layout_data.is_some() {
if node.is_anonymous() {
anon_nodes.push(node.id);
} else {
node.insert_damage(ALL_DAMAGE);
}
} else if let Some(input) = element.text_input_data_mut() {
input.editor.set_scale(scale);
let mut font_ctx = font_ctx.lock().unwrap();
input.editor.refresh_layout(&mut font_ctx, layout_ctx);
node.insert_damage(ONLY_RELAYOUT);
}
}
for node_id in anon_nodes {
if let Some(parent_id) = *(self.nodes[node_id].layout_parent.get_mut()) {
self.nodes[parent_id].insert_damage(ALL_DAMAGE);
}
}
}
pub(crate) fn flush_pending_style_images(&mut self) {
let mut pending = std::mem::take(&mut self.pending_style_image_nodes);
pending.sort_unstable();
pending.dedup();
for node_id in pending {
if !self.nodes.contains_key(node_id) {
continue;
}
self.flush_image_layers_from_style(node_id, ImageLayerKind::Background);
self.flush_image_layers_from_style(node_id, ImageLayerKind::Mask);
}
}
fn flush_image_layers_from_style(&mut self, node_id: NodeId, kind: ImageLayerKind) {
let doc_id = self.id();
let node = self.nodes.get_mut(node_id).unwrap();
let style = {
let stylo_element_data = node.try_stylo_element_data().and_then(|s| s.get());
let primary_styles = stylo_element_data
.as_ref()
.and_then(|data| data.styles.get_primary());
let Some(style) = primary_styles else {
return;
};
style.clone()
};
let Some(elem) = node.data.downcast_element_mut() else {
return;
};
let (style_images, elem_images) = match kind {
ImageLayerKind::Background => (
&style.get_background().background_image.0,
&mut elem.background_images,
),
ImageLayerKind::Mask => (&style.get_svg().mask_image.0, &mut elem.mask_images),
};
let len = style_images.len();
elem_images.resize(len, None);
for idx in 0..len {
let style_image = &style_images[idx];
let new_image = match style_image {
StyloImage::Url(ComputedUrl::Valid(new_url)) => {
let old_image = elem_images[idx].as_ref();
let old_image_url = old_image.map(|data| &data.url);
if old_image_url.is_some_and(|old_url| **new_url == **old_url) {
continue;
}
let url_str = new_url.as_str();
if let Some(cached_image) = self.image_cache.get(url_str) {
#[cfg(feature = "tracing")]
tracing::info!("Loading image {url_str} from cache");
Some(ImageResourceData {
url: new_url.clone(),
status: Status::Ok,
image: cached_image.clone(),
})
} else if let Some(waiting_list) = self.pending_images.get_mut(url_str) {
#[cfg(feature = "tracing")]
tracing::info!("Image {url_str} already pending, queueing node {node_id}");
waiting_list.push((node_id, kind.image_type(idx)));
Some(ImageResourceData::new(new_url.clone()))
} else {
#[cfg(feature = "tracing")]
tracing::info!("Fetching image {url_str}");
self.pending_images
.insert(url_str.to_string(), vec![(node_id, kind.image_type(idx))]);
self.net_provider.fetch(
doc_id,
crate::net::stamped_request(
(**new_url).clone(),
self.abort_signal.as_ref(),
),
ResourceHandler::boxed(
self.tx.clone(),
doc_id,
None, self.shell_provider.clone(),
ImageHandler::new(kind.image_type(idx)),
),
);
Some(ImageResourceData::new(new_url.clone()))
}
}
_ => None,
};
elem_images[idx] = new_image;
}
}
}