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