1use blitz_traits::node_id::NodeId;
2use std::ops::Range;
3
4use crate::Node;
5use crate::net::ResourceHandler;
6use crate::node::NodeFlags;
7use crate::{
8 BaseDocument, net::ImageHandler, node::ImageResourceData, node::Status, util::ImageLayerKind,
9};
10use style::properties::ComputedValues;
11use style::properties::generated::longhands::position::computed_value::T as Position;
12use style::selector_parser::RestyleDamage;
13use style::url::ComputedUrl;
14use style::values::computed::Float;
15use style::values::generics::image::Image as StyloImage;
16use style::values::specified::align::AlignFlags;
17use style::values::specified::box_::DisplayInside;
18use style::values::specified::box_::DisplayOutside;
19use taffy::Rect;
20use thin_vec::ThinVec;
21
22pub(crate) const CONSTRUCT_BOX: RestyleDamage =
23 RestyleDamage::from_bits_retain(0b_0000_0000_0001_0000);
24pub(crate) const CONSTRUCT_FC: RestyleDamage =
25 RestyleDamage::from_bits_retain(0b_0000_0000_0010_0000);
26pub(crate) const CONSTRUCT_DESCENDENT: RestyleDamage =
27 RestyleDamage::from_bits_retain(0b_0000_0000_0100_0000);
28
29pub(crate) const ONLY_RELAYOUT: RestyleDamage =
30 RestyleDamage::from_bits_retain(0b_0000_0000_0000_1000);
31
32pub(crate) const ALL_DAMAGE: RestyleDamage =
33 RestyleDamage::from_bits_retain(0b_0000_0000_0111_1111);
34
35impl BaseDocument {
36 pub(crate) fn propagate_damage_flags(
37 &mut self,
38 node_id: NodeId,
39 damage_from_parent: RestyleDamage,
40 ) -> RestyleDamage {
41 let mut damage = if let Some(data) = self.nodes[node_id]
42 .stylo_element_data_opt_mut()
43 .and_then(|s| s.get_mut())
44 {
45 data.damage
46 } else {
47 return RestyleDamage::empty();
48 };
49 damage |= damage_from_parent;
50
51 {
56 let node = &self.nodes[node_id];
57 if damage.is_empty() && !node.has_damaged_descendants() && !node.is_anonymous() {
58 return RestyleDamage::empty();
59 }
60 }
61
62 let damage_for_children = RestyleDamage::empty();
63 let children = std::mem::take(&mut self.nodes[node_id].children);
64 let layout_children = std::mem::take(self.nodes[node_id].layout_children.get_mut());
65 let use_layout_children = self.nodes[node_id].should_traverse_layout_children();
66 if use_layout_children {
67 let layout_children = layout_children.as_ref().unwrap();
68 for child in layout_children.iter() {
69 damage |= self.propagate_damage_flags(*child, damage_for_children);
70 }
71 } else {
72 for child in children.iter() {
73 damage |= self.propagate_damage_flags(*child, damage_for_children);
74 }
75 if let Some(before_id) = self.nodes[node_id].before() {
76 damage |= self.propagate_damage_flags(before_id, damage_for_children);
77 }
78 if let Some(after_id) = self.nodes[node_id].after() {
79 damage |= self.propagate_damage_flags(after_id, damage_for_children);
80 }
81 }
82
83 let node = &mut self.nodes[node_id];
84
85 node.children = children;
87 *node.layout_children.get_mut() = layout_children;
88
89 if damage.contains(CONSTRUCT_BOX) {
90 damage.insert(RestyleDamage::RELAYOUT);
91 }
92
93 let damage_for_parent = damage; if damage.intersects(ONLY_RELAYOUT | CONSTRUCT_BOX) {
99 node.cache_mut().clear();
100 if let Some(inline_layout) = node
101 .data
102 .downcast_element_mut()
103 .and_then(|el| el.inline_layout_data.as_mut())
104 {
105 inline_layout.content_widths = None;
106 }
107 damage.remove(ONLY_RELAYOUT);
108 }
109
110 node.set_damage(damage);
112
113 damage_for_parent
132 }
133
134 pub(crate) fn clear_damage_and_dirty_flags(&mut self, node_id: NodeId) {
138 {
139 let node = &self.nodes[node_id];
140 let has_damage = node.damage().is_some_and(|d| !d.is_empty());
141 if !has_damage && !node.has_damaged_descendants() && !node.is_anonymous() {
142 return;
143 }
144 }
145
146 let children = std::mem::take(&mut self.nodes[node_id].children);
147 let layout_children = std::mem::take(self.nodes[node_id].layout_children.get_mut());
148 for child in children.iter() {
149 self.clear_damage_and_dirty_flags(*child);
150 }
151 if let Some(layout_children) = layout_children.as_ref() {
152 for child in layout_children.iter() {
153 self.clear_damage_and_dirty_flags(*child);
154 }
155 }
156 if let Some(before_id) = self.nodes[node_id].before() {
157 self.clear_damage_and_dirty_flags(before_id);
158 }
159 if let Some(after_id) = self.nodes[node_id].after() {
160 self.clear_damage_and_dirty_flags(after_id);
161 }
162
163 let node = &mut self.nodes[node_id];
164 node.children = children;
165 *node.layout_children.get_mut() = layout_children;
166 node.clear_damage_mut();
167 node.unset_damaged_descendants();
168 node.unset_dirty_descendants();
169 }
170}
171
172pub(crate) fn compute_layout_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
200 let box_tree_needs_rebuild = || {
201 let old_box = old.get_box();
202 let new_box = new.get_box();
203
204 if old_box.display != new_box.display
205 || old_box.float != new_box.float
206 || old_box.position != new_box.position
207 || old_box.contain != new_box.contain
208 || old.clone_visibility() != new.clone_visibility()
209 {
210 return true;
211 }
212
213 if old.get_font() != new.get_font() {
214 return true;
215 }
216
217 if new_box.display.outside() == DisplayOutside::Block
218 && new_box.display.inside() == DisplayInside::Flow
219 {
220 let alignment_establishes_new_block_formatting_context = |style: &ComputedValues| {
221 style.get_position().align_content.primary() != AlignFlags::NORMAL
222 };
223
224 let old_column = old.get_column();
225 let new_column = new.get_column();
226 if old_box.overflow_x.is_scrollable() != new_box.overflow_x.is_scrollable()
227 || old_column.is_multicol() != new_column.is_multicol()
228 || old_column.column_span != new_column.column_span
229 || alignment_establishes_new_block_formatting_context(old)
230 != alignment_establishes_new_block_formatting_context(new)
231 {
232 return true;
233 }
234 }
235
236 if old_box.display.is_list_item() {
237 let old_list = old.get_list();
238 let new_list = new.get_list();
239 if old_list.list_style_position != new_list.list_style_position
240 || old_list.list_style_image != new_list.list_style_image
241 || (new_list.list_style_image == StyloImage::None
242 && old_list.list_style_type != new_list.list_style_type)
243 {
244 return true;
245 }
246 }
247
248 if new.is_pseudo_style() && old.get_counters().content != new.get_counters().content {
249 return true;
250 }
251
252 false
253 };
254
255 let text_shaping_needs_recollect = || {
256 if old.clone_direction() != new.clone_direction()
257 || old.clone_unicode_bidi() != new.clone_unicode_bidi()
258 {
259 return true;
260 }
261
262 let old_text = old.get_inherited_text();
263 let new_text = new.get_inherited_text();
264 if !std::ptr::eq(old_text, new_text)
265 && (old_text.white_space_collapse != new_text.white_space_collapse
266 || old_text.text_transform != new_text.text_transform
267 || old_text.word_break != new_text.word_break
268 || old_text.overflow_wrap != new_text.overflow_wrap
269 || old_text.letter_spacing != new_text.letter_spacing
270 || old_text.word_spacing != new_text.word_spacing
271 || old_text.text_rendering != new_text.text_rendering)
272 {
273 return true;
274 }
275
276 false
277 };
278
279 #[allow(
280 clippy::if_same_then_else,
281 reason = "these branches will soon be different"
282 )]
283 if box_tree_needs_rebuild() {
284 ALL_DAMAGE
285 } else if text_shaping_needs_recollect() {
286 ALL_DAMAGE
287 } else {
288 RestyleDamage::RELAYOUT
292 }
293}
294
295#[derive(Debug, Clone)]
297pub struct HoistedPaintChild {
298 pub node_id: NodeId,
299 pub z_index: i32,
300 pub position: taffy::Point<f32>,
301}
302
303#[derive(Debug)]
304pub struct HoistedPaintChildren {
305 pub children: Vec<HoistedPaintChild>,
306 pub negative_z_count: u32,
308
309 pub content_area: taffy::Rect<f32>,
310}
311
312impl HoistedPaintChildren {
313 fn new() -> Self {
314 Self {
315 children: Vec::new(),
316 negative_z_count: 0,
317 content_area: taffy::Rect::ZERO,
318 }
319 }
320
321 pub fn reset(&mut self) {
322 self.children.clear();
323 self.negative_z_count = 0;
324 }
325
326 pub fn compute_content_size(&mut self, doc: &BaseDocument) {
327 fn child_pos(child: &HoistedPaintChild, doc: &BaseDocument) -> Rect<f32> {
328 let node = &doc.nodes[child.node_id];
329 let left = child.position.x + node.final_layout().location.x;
330 let top = child.position.y + node.final_layout().location.y;
331 let right = left + node.final_layout().size.width;
332 let bottom = top + node.final_layout().size.height;
333
334 taffy::Rect {
335 top,
336 left,
337 bottom,
338 right,
339 }
340 }
341
342 if self.children.is_empty() {
343 self.content_area = taffy::Rect::ZERO;
344 } else {
345 self.content_area = child_pos(&self.children[0], doc);
346 for child in self.children[1..].iter() {
347 let pos = child_pos(child, doc);
348 self.content_area.left = self.content_area.left.min(pos.left);
349 self.content_area.top = self.content_area.top.min(pos.top);
350 self.content_area.right = self.content_area.right.max(pos.right);
351 self.content_area.bottom = self.content_area.bottom.max(pos.bottom);
352 }
353 }
354 }
355
356 pub fn sort(&mut self) {
357 self.children.sort_by_key(|c| c.z_index);
358 self.negative_z_count = self.children.iter().take_while(|c| c.z_index < 0).count() as u32;
359 }
360
361 pub fn neg_z_range(&self) -> Range<usize> {
362 0..(self.negative_z_count as usize)
363 }
364
365 pub fn pos_z_range(&self) -> Range<usize> {
366 (self.negative_z_count as usize)..self.children.len()
367 }
368
369 pub fn neg_z_hoisted_children(
370 &self,
371 ) -> impl ExactSizeIterator<Item = &HoistedPaintChild> + DoubleEndedIterator {
372 self.children[self.neg_z_range()].iter()
373 }
374
375 pub fn pos_z_hoisted_children(
376 &self,
377 ) -> impl ExactSizeIterator<Item = &HoistedPaintChild> + DoubleEndedIterator {
378 self.children[self.pos_z_range()].iter()
379 }
380}
381
382impl BaseDocument {
383 pub(crate) fn invalidate_inline_contexts(&mut self) {
384 let scale = self.viewport.scale();
385
386 let font_ctx = &self.font_ctx;
387 let layout_ctx = &mut self.layout_ctx;
388
389 let mut anon_nodes = Vec::new();
390
391 for (_, node) in self.nodes.iter_mut() {
392 if !(node.flags.contains(NodeFlags::IS_IN_DOCUMENT)) {
393 continue;
394 }
395
396 let Some(element) = node.data.downcast_element_mut() else {
397 continue;
398 };
399
400 if element.inline_layout_data.is_some() {
401 if node.is_anonymous() {
402 anon_nodes.push(node.id);
403 } else {
404 node.insert_damage(ALL_DAMAGE);
405 }
406 } else if let Some(input) = element.text_input_data_mut() {
407 input.editor.set_scale(scale);
408 let mut font_ctx = font_ctx.lock().unwrap();
409 input.editor.refresh_layout(&mut font_ctx, layout_ctx);
410 node.insert_damage(ONLY_RELAYOUT);
411 }
412 }
413
414 for node_id in anon_nodes {
415 if let Some(parent_id) = *(self.nodes[node_id].layout_parent.get_mut()) {
416 self.nodes[parent_id].insert_damage(ALL_DAMAGE);
417 }
418 }
419 }
420
421 pub fn flush_styles_to_layout(&mut self, node_id: NodeId) {
422 self.flush_styles_to_layout_impl(node_id, None);
423 }
424
425 pub(crate) fn flush_pending_style_images(&mut self) {
428 let mut pending = std::mem::take(&mut self.pending_style_image_nodes);
429 pending.sort_unstable();
430 pending.dedup();
431 for node_id in pending {
432 if !self.nodes.contains_key(node_id) {
435 continue;
436 }
437 self.flush_image_layers_from_style(node_id, ImageLayerKind::Background);
438 self.flush_image_layers_from_style(node_id, ImageLayerKind::Mask);
439 }
440 }
441
442 fn flush_image_layers_from_style(&mut self, node_id: NodeId, kind: ImageLayerKind) {
445 let doc_id = self.id();
446 let node = self.nodes.get_mut(node_id).unwrap();
447 let style = {
451 let stylo_element_data = node.stylo_element_data_opt().and_then(|s| s.get());
452 let primary_styles = stylo_element_data
453 .as_ref()
454 .and_then(|data| data.styles.get_primary());
455 let Some(style) = primary_styles else {
456 return;
457 };
458 style.clone()
459 };
460 let Some(elem) = node.data.downcast_element_mut() else {
461 return;
462 };
463
464 let (style_images, elem_images) = match kind {
465 ImageLayerKind::Background => (
466 &style.get_background().background_image.0,
467 &mut elem.background_images,
468 ),
469 ImageLayerKind::Mask => (&style.get_svg().mask_image.0, &mut elem.mask_images),
470 };
471
472 let len = style_images.len();
473 elem_images.resize_with(len, || None);
474
475 for idx in 0..len {
476 let style_image = &style_images[idx];
477 let new_image = match style_image {
478 StyloImage::Url(ComputedUrl::Valid(new_url)) => {
479 let old_image = elem_images[idx].as_ref();
480 let old_image_url = old_image.map(|data| &data.url);
481 if old_image_url.is_some_and(|old_url| **new_url == **old_url) {
482 continue;
483 }
484
485 let url_str = new_url.as_str();
487 if let Some(cached_image) = self.image_cache.get(url_str) {
488 #[cfg(feature = "tracing")]
489 tracing::info!("Loading image {url_str} from cache");
490 Some(ImageResourceData {
491 url: new_url.clone(),
492 status: Status::Ok,
493 image: cached_image.clone(),
494 })
495 } else if let Some(waiting_list) = self.pending_images.get_mut(url_str) {
496 #[cfg(feature = "tracing")]
498 tracing::info!("Image {url_str} already pending, queueing node {node_id}");
499 waiting_list.push((node_id, kind.image_type(idx)));
500 Some(ImageResourceData::new(new_url.clone()))
501 } else {
502 #[cfg(feature = "tracing")]
504 tracing::info!("Fetching image {url_str}");
505 self.pending_images
506 .insert(url_str.to_string(), vec![(node_id, kind.image_type(idx))]);
507
508 self.net_provider.fetch(
509 doc_id,
510 crate::net::stamped_request(
511 (**new_url).clone(),
512 self.abort_signal.as_ref(),
513 ),
514 ResourceHandler::boxed(
515 self.tx.clone(),
516 doc_id,
517 None, self.shell_provider.clone(),
519 ImageHandler::new(kind.image_type(idx)),
520 ),
521 );
522
523 Some(ImageResourceData::new(new_url.clone()))
524 }
525 }
526 _ => None,
527 };
528
529 elem_images[idx] = new_image;
531 }
532 }
533
534 fn flush_styles_to_layout_impl(
536 &mut self,
537 node_id: NodeId,
538 parent_stacking_context: Option<&mut HoistedPaintChildren>,
539 ) {
540 let mut new_stacking_context: HoistedPaintChildren = HoistedPaintChildren::new();
541 let stacking_context = &mut new_stacking_context;
542
543 let incremental = self.incremental_layout;
544 let display = {
545 let node = self.nodes.get_mut(node_id).unwrap();
546 let _damage = node.damage().unwrap_or(ALL_DAMAGE);
547
548 let (mut taffy_style, display_constructed_as) = {
552 let stylo_element_data = node.stylo_element_data_opt().and_then(|s| s.get());
553 let primary_styles = stylo_element_data
554 .as_ref()
555 .and_then(|data| data.styles.get_primary());
556
557 let Some(style) = primary_styles else {
558 return;
559 };
560
561 (stylo_taffy::to_taffy_style(style), style.clone_display())
562 };
563 taffy_style.item_is_replaced = node
564 .data
565 .downcast_element()
566 .is_some_and(|el| crate::layout::replaced::is_replaced_element(&el.name.local));
567
568 *node.style_mut() = taffy_style;
570 *node.display_constructed_as_mut() = display_constructed_as;
571 if !incremental {
576 node.cache_mut().clear();
577 if let Some(inline_layout) = node
578 .data
579 .downcast_element_mut()
580 .and_then(|el| el.inline_layout_data.as_mut())
581 {
582 inline_layout.content_widths = None;
583 }
584 }
585
586 node.style().display
587 };
588
589 let children = self.nodes[node_id].layout_children.borrow_mut().take();
591 if let Some(mut children) = children {
592 let is_flex_or_grid = matches!(display, taffy::Display::Flex | taffy::Display::Grid);
593
594 for &child in children.iter() {
596 self.flush_styles_to_layout_impl(
597 child,
598 match self.nodes[child].is_stacking_context_root(is_flex_or_grid) {
599 true => None,
600 false => Some(stacking_context),
601 },
602 );
603 }
604
605 if is_flex_or_grid {
607 children.sort_by(|left, right| {
608 let left_node = self.nodes.get(*left).unwrap();
609 let right_node = self.nodes.get(*right).unwrap();
610 left_node.order().cmp(&right_node.order())
611 });
612 }
613
614 let mut paint_children = self.nodes[node_id].paint_children.borrow_mut();
616 if paint_children.is_none() {
617 *paint_children = Some(ThinVec::new());
618 }
619 let paint_children = paint_children.as_mut().unwrap();
620 paint_children.clear();
621 paint_children.reserve(children.len());
622
623 for &child_id in children.iter() {
625 let child = &self.nodes[child_id];
626
627 let Some(style) = child.primary_styles() else {
628 paint_children.push(child_id);
629 continue;
630 };
631
632 let position = style.clone_position();
633 let z_index = style.clone_z_index().integer_or(0);
634
635 if z_index != 0 && (position != Position::Static || is_flex_or_grid) {
639 stacking_context.children.push(HoistedPaintChild {
640 node_id: child_id,
641 z_index,
642 position: taffy::Point::ZERO,
643 })
644 } else {
645 paint_children.push(child_id);
646 }
647 }
648
649 paint_children.sort_by(|left, right| {
651 let left_node = self.nodes.get(*left).unwrap();
652 let right_node = self.nodes.get(*right).unwrap();
653 node_to_paint_order(left_node, is_flex_or_grid)
654 .cmp(&node_to_paint_order(right_node, is_flex_or_grid))
655 });
656
657 *self.nodes[node_id].layout_children.borrow_mut() = Some(children);
659 }
660
661 if let Some(parent_stacking_context) = parent_stacking_context {
662 let position = self.nodes[node_id].final_layout().location;
663 let scroll_offset = *self.nodes[node_id].scroll_offset();
664 for hoisted in stacking_context.children.iter_mut() {
665 hoisted.position.x += position.x - scroll_offset.x as f32;
666 hoisted.position.y += position.y - scroll_offset.y as f32;
667 }
668 parent_stacking_context
669 .children
670 .extend(stacking_context.children.iter().cloned());
671 } else {
672 stacking_context.sort();
673 stacking_context.compute_content_size(self);
674 self.nodes[node_id].stacking_context = Some(Box::new(new_stacking_context));
675 }
676 }
677}
678
679#[inline(always)]
680fn position_to_order(pos: Position) -> i32 {
681 match pos {
682 Position::Static => 0,
683 Position::Relative | Position::Sticky | Position::Absolute | Position::Fixed => 2,
687 }
688}
689#[inline(always)]
690fn float_to_order(pos: Float) -> i32 {
691 match pos {
692 Float::None => 0,
693 _ => 1,
694 }
695}
696
697#[inline(always)]
702fn node_to_paint_order(node: &Node, is_flex_or_grid: bool) -> (i32, i32) {
703 let Some(style) = node.primary_styles() else {
704 return (0, 0);
705 };
706 let position = style.clone_position();
707 if is_flex_or_grid {
708 match position {
709 Position::Static => (0, style.clone_order()),
710 Position::Relative | Position::Sticky => (2, style.clone_order()),
711 Position::Absolute | Position::Fixed => (2, 0),
714 }
715 } else {
716 (
717 position_to_order(position) + float_to_order(style.clone_float()),
718 0,
719 )
720 }
721}