blitz_dom/layout/
damage.rs1use std::ops::Range;
2
3use crate::net::ResourceHandler;
4use crate::node::NodeFlags;
5use crate::{
6 BaseDocument, net::ImageHandler, node::BackgroundImageData, node::Status, util::ImageType,
7};
8use crate::{NON_INCREMENTAL, Node};
9use style::properties::ComputedValues;
10use style::properties::generated::longhands::position::computed_value::T as Position;
11use style::selector_parser::RestyleDamage;
12use style::servo::url::ComputedUrl;
13use style::values::computed::Float;
14use style::values::generics::image::Image as StyloImage;
15use style::values::specified::align::AlignFlags;
16use style::values::specified::box_::DisplayInside;
17use style::values::specified::box_::DisplayOutside;
18use taffy::Rect;
19
20pub(crate) const CONSTRUCT_BOX: RestyleDamage =
21 RestyleDamage::from_bits_retain(0b_0000_0000_0001_0000);
22pub(crate) const CONSTRUCT_FC: RestyleDamage =
23 RestyleDamage::from_bits_retain(0b_0000_0000_0010_0000);
24pub(crate) const CONSTRUCT_DESCENDENT: RestyleDamage =
25 RestyleDamage::from_bits_retain(0b_0000_0000_0100_0000);
26
27pub(crate) const ONLY_RELAYOUT: RestyleDamage =
28 RestyleDamage::from_bits_retain(0b_0000_0000_0000_1000);
29
30pub(crate) const ALL_DAMAGE: RestyleDamage =
31 RestyleDamage::from_bits_retain(0b_0000_0000_0111_1111);
32
33impl BaseDocument {
34 #[cfg(feature = "incremental")]
35 pub(crate) fn propagate_damage_flags(
36 &mut self,
37 node_id: usize,
38 damage_from_parent: RestyleDamage,
39 ) -> RestyleDamage {
40 let mut damage = if let Some(data) = self.nodes[node_id].stylo_element_data.get_mut() {
41 data.damage
42 } else {
43 return RestyleDamage::empty();
44 };
45 damage |= damage_from_parent;
46
47 let damage_for_children = RestyleDamage::empty();
48 let children = std::mem::take(&mut self.nodes[node_id].children);
49 let layout_children = std::mem::take(self.nodes[node_id].layout_children.get_mut());
50 let use_layout_children = self.nodes[node_id].should_traverse_layout_children();
51 if use_layout_children {
52 let layout_children = layout_children.as_ref().unwrap();
53 for child in layout_children.iter() {
54 damage |= self.propagate_damage_flags(*child, damage_for_children);
55 }
56 } else {
57 for child in children.iter() {
58 damage |= self.propagate_damage_flags(*child, damage_for_children);
59 }
60 if let Some(before_id) = self.nodes[node_id].before {
61 damage |= self.propagate_damage_flags(before_id, damage_for_children);
62 }
63 if let Some(after_id) = self.nodes[node_id].after {
64 damage |= self.propagate_damage_flags(after_id, damage_for_children);
65 }
66 }
67
68 let node = &mut self.nodes[node_id];
69
70 node.children = children;
72 *node.layout_children.get_mut() = layout_children;
73
74 if damage.contains(CONSTRUCT_BOX) {
75 damage.insert(RestyleDamage::RELAYOUT);
76 }
77
78 let damage_for_parent = damage; if damage.intersects(ONLY_RELAYOUT | CONSTRUCT_BOX) {
84 node.cache.clear();
85 if let Some(inline_layout) = node
86 .data
87 .downcast_element_mut()
88 .and_then(|el| el.inline_layout_data.as_mut())
89 {
90 inline_layout.content_widths = None;
91 }
92 damage.remove(ONLY_RELAYOUT);
93 }
94
95 node.set_damage(damage);
97
98 damage_for_parent
117 }
118}
119
120pub(crate) fn compute_layout_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
149 let box_tree_needs_rebuild = || {
150 let old_box = old.get_box();
151 let new_box = new.get_box();
152
153 if old_box.display != new_box.display
154 || old_box.float != new_box.float
155 || old_box.position != new_box.position
156 || old.clone_visibility() != new.clone_visibility()
157 {
158 return true;
159 }
160
161 if old.get_font() != new.get_font() {
162 return true;
163 }
164
165 if new_box.display.outside() == DisplayOutside::Block
166 && new_box.display.inside() == DisplayInside::Flow
167 {
168 let alignment_establishes_new_block_formatting_context = |style: &ComputedValues| {
169 style.get_position().align_content.primary() != AlignFlags::NORMAL
170 };
171
172 let old_column = old.get_column();
173 let new_column = new.get_column();
174 if old_box.overflow_x.is_scrollable() != new_box.overflow_x.is_scrollable()
175 || old_column.is_multicol() != new_column.is_multicol()
176 || old_column.column_span != new_column.column_span
177 || alignment_establishes_new_block_formatting_context(old)
178 != alignment_establishes_new_block_formatting_context(new)
179 {
180 return true;
181 }
182 }
183
184 if old_box.display.is_list_item() {
185 let old_list = old.get_list();
186 let new_list = new.get_list();
187 if old_list.list_style_position != new_list.list_style_position
188 || old_list.list_style_image != new_list.list_style_image
189 || (new_list.list_style_image == StyloImage::None
190 && old_list.list_style_type != new_list.list_style_type)
191 {
192 return true;
193 }
194 }
195
196 if new.is_pseudo_style() && old.get_counters().content != new.get_counters().content {
197 return true;
198 }
199
200 false
201 };
202
203 let text_shaping_needs_recollect = || {
204 if old.clone_direction() != new.clone_direction()
205 || old.clone_unicode_bidi() != new.clone_unicode_bidi()
206 {
207 return true;
208 }
209
210 let old_text = old.get_inherited_text();
211 let new_text = new.get_inherited_text();
212 if !std::ptr::eq(old_text, new_text)
213 && (old_text.white_space_collapse != new_text.white_space_collapse
214 || old_text.text_transform != new_text.text_transform
215 || old_text.word_break != new_text.word_break
216 || old_text.overflow_wrap != new_text.overflow_wrap
217 || old_text.letter_spacing != new_text.letter_spacing
218 || old_text.word_spacing != new_text.word_spacing
219 || old_text.text_rendering != new_text.text_rendering)
220 {
221 return true;
222 }
223
224 false
225 };
226
227 #[allow(
228 clippy::if_same_then_else,
229 reason = "these branches will soon be different"
230 )]
231 if box_tree_needs_rebuild() {
232 ALL_DAMAGE
233 } else if text_shaping_needs_recollect() {
234 ALL_DAMAGE
235 } else {
236 RestyleDamage::RELAYOUT
240 }
241}
242
243#[derive(Debug, Clone)]
245pub struct HoistedPaintChild {
246 pub node_id: usize,
247 pub z_index: i32,
248 pub position: taffy::Point<f32>,
249}
250
251#[derive(Debug)]
252pub struct HoistedPaintChildren {
253 pub children: Vec<HoistedPaintChild>,
254 pub negative_z_count: u32,
256
257 pub content_area: taffy::Rect<f32>,
258}
259
260impl HoistedPaintChildren {
261 fn new() -> Self {
262 Self {
263 children: Vec::new(),
264 negative_z_count: 0,
265 content_area: taffy::Rect::ZERO,
266 }
267 }
268
269 pub fn reset(&mut self) {
270 self.children.clear();
271 self.negative_z_count = 0;
272 }
273
274 pub fn compute_content_size(&mut self, doc: &BaseDocument) {
275 fn child_pos(child: &HoistedPaintChild, doc: &BaseDocument) -> Rect<f32> {
276 let node = &doc.nodes[child.node_id];
277 let left = child.position.x + node.final_layout.location.x;
278 let top = child.position.y + node.final_layout.location.y;
279 let right = left + node.final_layout.size.width;
280 let bottom = top + node.final_layout.size.height;
281
282 taffy::Rect {
283 top,
284 left,
285 bottom,
286 right,
287 }
288 }
289
290 if self.children.is_empty() {
291 self.content_area = taffy::Rect::ZERO;
292 } else {
293 self.content_area = child_pos(&self.children[0], doc);
294 for child in self.children[1..].iter() {
295 let pos = child_pos(child, doc);
296 self.content_area.left = self.content_area.left.min(pos.left);
297 self.content_area.top = self.content_area.top.min(pos.top);
298 self.content_area.right = self.content_area.right.max(pos.right);
299 self.content_area.bottom = self.content_area.bottom.max(pos.bottom);
300 }
301 }
302 }
303
304 pub fn sort(&mut self) {
305 self.children.sort_by_key(|c| c.z_index);
306 self.negative_z_count = self.children.iter().take_while(|c| c.z_index < 0).count() as u32;
307 }
308
309 pub fn neg_z_range(&self) -> Range<usize> {
310 0..(self.negative_z_count as usize)
311 }
312
313 pub fn pos_z_range(&self) -> Range<usize> {
314 (self.negative_z_count as usize)..self.children.len()
315 }
316
317 pub fn neg_z_hoisted_children(
318 &self,
319 ) -> impl ExactSizeIterator<Item = &HoistedPaintChild> + DoubleEndedIterator {
320 self.children[self.neg_z_range()].iter()
321 }
322
323 pub fn pos_z_hoisted_children(
324 &self,
325 ) -> impl ExactSizeIterator<Item = &HoistedPaintChild> + DoubleEndedIterator {
326 self.children[self.pos_z_range()].iter()
327 }
328}
329
330impl BaseDocument {
331 pub(crate) fn invalidate_inline_contexts(&mut self) {
332 let scale = self.viewport.scale();
333
334 let font_ctx = &self.font_ctx;
335 let layout_ctx = &mut self.layout_ctx;
336
337 let mut anon_nodes = Vec::new();
338
339 for (_, node) in self.nodes.iter_mut() {
340 if !(node.flags.contains(NodeFlags::IS_IN_DOCUMENT)) {
341 continue;
342 }
343
344 let Some(element) = node.data.downcast_element_mut() else {
345 continue;
346 };
347
348 if element.inline_layout_data.is_some() {
349 if node.is_anonymous() {
350 anon_nodes.push(node.id);
351 } else {
352 node.insert_damage(ALL_DAMAGE);
353 }
354 } else if let Some(input) = element.text_input_data_mut() {
355 input.editor.set_scale(scale);
356 let mut font_ctx = font_ctx.lock().unwrap();
357 input.editor.refresh_layout(&mut font_ctx, layout_ctx);
358 node.insert_damage(ONLY_RELAYOUT);
359 }
360 }
361
362 for node_id in anon_nodes {
363 if let Some(parent_id) = *(self.nodes[node_id].layout_parent.get_mut()) {
364 self.nodes[parent_id].insert_damage(ALL_DAMAGE);
365 }
366 }
367 }
368
369 pub fn flush_styles_to_layout(&mut self, node_id: usize) {
370 self.flush_styles_to_layout_impl(node_id, None);
371 }
372
373 fn flush_styles_to_layout_impl(
375 &mut self,
376 node_id: usize,
377 parent_stacking_context: Option<&mut HoistedPaintChildren>,
378 ) {
379 let doc_id = self.id();
380
381 let mut new_stacking_context: HoistedPaintChildren = HoistedPaintChildren::new();
382 let stacking_context = &mut new_stacking_context;
383
384 let display = {
385 let node = self.nodes.get_mut(node_id).unwrap();
386 let _damage = node.damage().unwrap_or(ALL_DAMAGE);
387 let stylo_element_data = node.stylo_element_data.get();
388 let primary_styles = stylo_element_data
389 .as_ref()
390 .and_then(|data| data.styles.get_primary());
391
392 let Some(style) = primary_styles else {
393 return;
394 };
395
396 node.style = stylo_taffy::to_taffy_style(style);
398 node.display_constructed_as = style.clone_display();
399 if let Some(elem) = node.data.downcast_element_mut() {
404 let style_bgs = &style.get_background().background_image.0;
405 let elem_bgs = &mut elem.background_images;
406
407 let len = style_bgs.len();
408 elem_bgs.resize_with(len, || None);
409
410 for idx in 0..len {
411 let background_image = &style_bgs[idx];
412 let new_bg_image = match background_image {
413 StyloImage::Url(ComputedUrl::Valid(new_url)) => {
414 let old_bg_image = elem_bgs[idx].as_ref();
415 let old_bg_image_url = old_bg_image.map(|data| &data.url);
416 if old_bg_image_url.is_some_and(|old_url| **new_url == **old_url) {
417 break;
418 }
419
420 let url_str = new_url.as_str();
422 if let Some(cached_image) = self.image_cache.get(url_str) {
423 #[cfg(feature = "tracing")]
424 tracing::info!("Loading image {url_str} from cache");
425 Some(BackgroundImageData {
426 url: new_url.clone(),
427 status: Status::Ok,
428 image: cached_image.clone(),
429 })
430 } else if let Some(waiting_list) = self.pending_images.get_mut(url_str)
431 {
432 #[cfg(feature = "tracing")]
434 tracing::info!(
435 "Image {url_str} already pending, queueing node {node_id}"
436 );
437 waiting_list.push((node_id, ImageType::Background(idx)));
438 Some(BackgroundImageData::new(new_url.clone()))
439 } else {
440 #[cfg(feature = "tracing")]
442 tracing::info!("Fetching image {url_str}");
443 self.pending_images.insert(
444 url_str.to_string(),
445 vec![(node_id, ImageType::Background(idx))],
446 );
447
448 self.net_provider.fetch(
449 doc_id,
450 crate::net::stamped_request(
451 (**new_url).clone(),
452 self.abort_signal.as_ref(),
453 ),
454 ResourceHandler::boxed(
455 self.tx.clone(),
456 doc_id,
457 None, self.shell_provider.clone(),
459 ImageHandler::new(ImageType::Background(idx)),
460 ),
461 );
462
463 Some(BackgroundImageData::new(new_url.clone()))
464 }
465 }
466 _ => None,
467 };
468
469 elem_bgs[idx] = new_bg_image;
471 }
472 }
473
474 if NON_INCREMENTAL {
477 node.cache.clear();
478 if let Some(inline_layout) = node
479 .data
480 .downcast_element_mut()
481 .and_then(|el| el.inline_layout_data.as_mut())
482 {
483 inline_layout.content_widths = None;
484 }
485 }
486
487 node.style.display
488 };
489
490 let children = self.nodes[node_id].layout_children.borrow_mut().take();
492 if let Some(mut children) = children {
493 let is_flex_or_grid = matches!(display, taffy::Display::Flex | taffy::Display::Grid);
494
495 for &child in children.iter() {
497 self.flush_styles_to_layout_impl(
498 child,
499 match self.nodes[child].is_stacking_context_root(is_flex_or_grid) {
500 true => None,
501 false => Some(stacking_context),
502 },
503 );
504 }
505
506 if is_flex_or_grid {
508 children.sort_by(|left, right| {
509 let left_node = self.nodes.get(*left).unwrap();
510 let right_node = self.nodes.get(*right).unwrap();
511 left_node.order().cmp(&right_node.order())
512 });
513 }
514
515 let mut paint_children = self.nodes[node_id].paint_children.borrow_mut();
517 if paint_children.is_none() {
518 *paint_children = Some(Vec::new());
519 }
520 let paint_children = paint_children.as_mut().unwrap();
521 paint_children.clear();
522 paint_children.reserve(children.len());
523
524 for &child_id in children.iter() {
526 let child = &self.nodes[child_id];
527
528 let Some(style) = child.primary_styles() else {
529 paint_children.push(child_id);
530 continue;
531 };
532
533 let position = style.clone_position();
534 let z_index = style.clone_z_index().integer_or(0);
535
536 if position != Position::Static && z_index != 0 {
538 stacking_context.children.push(HoistedPaintChild {
539 node_id: child_id,
540 z_index,
541 position: taffy::Point::ZERO,
542 })
543 } else {
544 paint_children.push(child_id);
545 }
546 }
547
548 paint_children.sort_by(|left, right| {
550 let left_node = self.nodes.get(*left).unwrap();
551 let right_node = self.nodes.get(*right).unwrap();
552 node_to_paint_order(left_node, is_flex_or_grid)
553 .cmp(&node_to_paint_order(right_node, is_flex_or_grid))
554 });
555
556 *self.nodes[node_id].layout_children.borrow_mut() = Some(children);
558 }
559
560 if let Some(parent_stacking_context) = parent_stacking_context {
561 let position = self.nodes[node_id].final_layout.location;
562 let scroll_offset = self.nodes[node_id].scroll_offset;
563 for hoisted in stacking_context.children.iter_mut() {
564 hoisted.position.x += position.x - scroll_offset.x as f32;
565 hoisted.position.y += position.y - scroll_offset.y as f32;
566 }
567 parent_stacking_context
568 .children
569 .extend(stacking_context.children.iter().cloned());
570 } else {
571 stacking_context.sort();
572 stacking_context.compute_content_size(self);
573 self.nodes[node_id].stacking_context = Some(Box::new(new_stacking_context));
574 }
575 }
576}
577
578#[inline(always)]
579fn position_to_order(pos: Position) -> i32 {
580 match pos {
581 Position::Static | Position::Relative | Position::Sticky => 0,
582 Position::Absolute | Position::Fixed => 2,
583 }
584}
585#[inline(always)]
586fn float_to_order(pos: Float) -> i32 {
587 match pos {
588 Float::None => 0,
589 _ => 1,
590 }
591}
592
593#[inline(always)]
594fn node_to_paint_order(node: &Node, is_flex_or_grid: bool) -> i32 {
595 let Some(style) = node.primary_styles() else {
596 return 0;
597 };
598 if is_flex_or_grid {
599 match style.clone_position() {
600 Position::Static | Position::Relative | Position::Sticky => style.clone_order(),
601 Position::Absolute | Position::Fixed => 0,
602 }
603 } else {
604 position_to_order(style.clone_position()) + float_to_order(style.clone_float())
605 }
606}