blitz_dom/layout/
damage.rs1use crate::NON_INCREMENTAL;
2use crate::node::NodeFlags;
3use crate::{BaseDocument, net::ImageHandler, node::BackgroundImageData, util::ImageType};
4use blitz_traits::net::Request;
5use style::properties::ComputedValues;
6use style::properties::generated::longhands::position::computed_value::T as Position;
7use style::selector_parser::RestyleDamage;
8use style::servo::url::ComputedUrl;
9use style::values::generics::image::Image as StyloImage;
10use style::values::specified::align::AlignFlags;
11use style::values::specified::box_::DisplayInside;
12use style::values::specified::box_::DisplayOutside;
13
14pub(crate) const CONSTRUCT_BOX: RestyleDamage =
15 RestyleDamage::from_bits_retain(0b_0000_0000_0001_0000);
16pub(crate) const CONSTRUCT_FC: RestyleDamage =
17 RestyleDamage::from_bits_retain(0b_0000_0000_0010_0000);
18pub(crate) const CONSTRUCT_DESCENDENT: RestyleDamage =
19 RestyleDamage::from_bits_retain(0b_0000_0000_0100_0000);
20
21pub(crate) const ONLY_RELAYOUT: RestyleDamage =
22 RestyleDamage::from_bits_retain(0b_0000_0000_0000_1000);
23
24pub(crate) const ALL_DAMAGE: RestyleDamage =
25 RestyleDamage::from_bits_retain(0b_0000_0000_0111_1111);
26
27impl BaseDocument {
28 #[cfg(feature = "incremental")]
29 pub(crate) fn propagate_damage_flags(
30 &mut self,
31 node_id: usize,
32 damage_from_parent: RestyleDamage,
33 ) -> RestyleDamage {
34 let Some(mut damage) = self.nodes[node_id].damage() else {
35 return RestyleDamage::empty();
36 };
37 damage |= damage_from_parent;
38
39 let damage_for_children = RestyleDamage::empty();
40 let children = std::mem::take(&mut self.nodes[node_id].children);
41 let layout_children = std::mem::take(self.nodes[node_id].layout_children.get_mut());
42 let use_layout_children = self.nodes[node_id].should_traverse_layout_children();
43 if use_layout_children {
44 let layout_children = layout_children.as_ref().unwrap();
45 for child in layout_children.iter() {
46 damage |= self.propagate_damage_flags(*child, damage_for_children);
47 }
48 } else {
49 for child in children.iter() {
50 damage |= self.propagate_damage_flags(*child, damage_for_children);
51 }
52 if let Some(before_id) = self.nodes[node_id].before {
53 damage |= self.propagate_damage_flags(before_id, damage_for_children);
54 }
55 if let Some(after_id) = self.nodes[node_id].after {
56 damage |= self.propagate_damage_flags(after_id, damage_for_children);
57 }
58 }
59
60 let node = &mut self.nodes[node_id];
61
62 node.children = children;
64 *node.layout_children.get_mut() = layout_children;
65
66 if damage.contains(CONSTRUCT_BOX) {
67 damage.insert(RestyleDamage::RELAYOUT);
68 }
69
70 let damage_for_parent = damage; if damage.intersects(ONLY_RELAYOUT | CONSTRUCT_BOX) {
76 node.cache.clear();
77 if let Some(inline_layout) = node
78 .data
79 .downcast_element_mut()
80 .and_then(|el| el.inline_layout_data.as_mut())
81 {
82 inline_layout.content_widths = None;
83 }
84 damage.remove(ONLY_RELAYOUT);
85 }
86
87 node.set_damage(damage);
89
90 damage_for_parent
109 }
110}
111
112pub(crate) fn compute_layout_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
141 let box_tree_needs_rebuild = || {
142 let old_box = old.get_box();
143 let new_box = new.get_box();
144
145 if old_box.display != new_box.display
146 || old_box.float != new_box.float
147 || old_box.position != new_box.position
148 {
149 return true;
150 }
151
152 if old.get_font() != new.get_font() {
153 return true;
154 }
155
156 if new_box.display.outside() == DisplayOutside::Block
157 && new_box.display.inside() == DisplayInside::Flow
158 {
159 let alignment_establishes_new_block_formatting_context = |style: &ComputedValues| {
160 style.get_position().align_content.0.primary() != AlignFlags::NORMAL
161 };
162
163 let old_column = old.get_column();
164 let new_column = new.get_column();
165 if old_box.overflow_x.is_scrollable() != new_box.overflow_x.is_scrollable()
166 || old_column.is_multicol() != new_column.is_multicol()
167 || old_column.column_span != new_column.column_span
168 || alignment_establishes_new_block_formatting_context(old)
169 != alignment_establishes_new_block_formatting_context(new)
170 {
171 return true;
172 }
173 }
174
175 if old_box.display.is_list_item() {
176 let old_list = old.get_list();
177 let new_list = new.get_list();
178 if old_list.list_style_position != new_list.list_style_position
179 || old_list.list_style_image != new_list.list_style_image
180 || (new_list.list_style_image == StyloImage::None
181 && old_list.list_style_type != new_list.list_style_type)
182 {
183 return true;
184 }
185 }
186
187 if new.is_pseudo_style() && old.get_counters().content != new.get_counters().content {
188 return true;
189 }
190
191 false
192 };
193
194 let text_shaping_needs_recollect = || {
195 if old.clone_direction() != new.clone_direction()
196 || old.clone_unicode_bidi() != new.clone_unicode_bidi()
197 {
198 return true;
199 }
200
201 let old_text = old.get_inherited_text();
202 let new_text = new.get_inherited_text();
203 if !std::ptr::eq(old_text, new_text)
204 && (old_text.white_space_collapse != new_text.white_space_collapse
205 || old_text.text_transform != new_text.text_transform
206 || old_text.word_break != new_text.word_break
207 || old_text.overflow_wrap != new_text.overflow_wrap
208 || old_text.letter_spacing != new_text.letter_spacing
209 || old_text.word_spacing != new_text.word_spacing
210 || old_text.text_rendering != new_text.text_rendering)
211 {
212 return true;
213 }
214
215 false
216 };
217
218 #[allow(
219 clippy::if_same_then_else,
220 reason = "these branches will soon be different"
221 )]
222 if box_tree_needs_rebuild() {
223 ALL_DAMAGE
224 } else if text_shaping_needs_recollect() {
225 ALL_DAMAGE
226 } else {
227 RestyleDamage::RELAYOUT
231 }
232}
233
234impl BaseDocument {
235 pub(crate) fn invalidate_inline_contexts(&mut self) {
236 let scale = self.viewport.scale();
237
238 let font_ctx = &self.font_ctx;
239 let layout_ctx = &mut self.layout_ctx;
240
241 for (_, node) in self.nodes.iter_mut() {
242 if !(node.flags.contains(NodeFlags::IS_IN_DOCUMENT)) {
243 continue;
244 }
245 let Some(element) = node.data.downcast_element_mut() else {
246 continue;
247 };
248
249 if element.inline_layout_data.is_some() {
250 node.insert_damage(ALL_DAMAGE);
251 } else if let Some(input) = element.text_input_data_mut() {
252 input.editor.set_scale(scale);
253 let mut font_ctx = font_ctx.lock().unwrap();
254 input.editor.refresh_layout(&mut font_ctx, layout_ctx);
255 node.insert_damage(ONLY_RELAYOUT);
256 }
257 }
258 }
259
260 pub fn flush_styles_to_layout(&mut self, node_id: usize) {
262 let doc_id = self.id();
263
264 let display = {
265 let node = self.nodes.get_mut(node_id).unwrap();
266 let _damage = node.damage().unwrap_or(ALL_DAMAGE);
267 let stylo_element_data = node.stylo_element_data.borrow();
268 let primary_styles = stylo_element_data
269 .as_ref()
270 .and_then(|data| data.styles.get_primary());
271
272 let Some(style) = primary_styles else {
273 return;
274 };
275
276 node.style = stylo_taffy::to_taffy_style(style);
278 node.display_constructed_as = style.clone_display();
279 if let Some(elem) = node.data.downcast_element_mut() {
284 let style_bgs = &style.get_background().background_image.0;
285 let elem_bgs = &mut elem.background_images;
286
287 let len = style_bgs.len();
288 elem_bgs.resize_with(len, || None);
289
290 for idx in 0..len {
291 let background_image = &style_bgs[idx];
292 let new_bg_image = match background_image {
293 StyloImage::Url(ComputedUrl::Valid(new_url)) => {
294 let old_bg_image = elem_bgs[idx].as_ref();
295 let old_bg_image_url = old_bg_image.map(|data| &data.url);
296 if old_bg_image_url.is_some_and(|old_url| **new_url == **old_url) {
297 break;
298 }
299
300 self.net_provider.fetch(
301 doc_id,
302 Request::get((**new_url).clone()),
303 Box::new(ImageHandler::new(node_id, ImageType::Background(idx))),
304 );
305
306 let bg_image_data = BackgroundImageData::new(new_url.clone());
307 Some(bg_image_data)
308 }
309 _ => None,
310 };
311
312 elem_bgs[idx] = new_bg_image;
314 }
315 }
316
317 if NON_INCREMENTAL {
320 node.cache.clear();
321 if let Some(inline_layout) = node
322 .data
323 .downcast_element_mut()
324 .and_then(|el| el.inline_layout_data.as_mut())
325 {
326 inline_layout.content_widths = None;
327 }
328 }
329
330 node.style.display
331 };
332
333 let children = self.nodes[node_id].layout_children.borrow_mut().take();
335 if let Some(mut children) = children {
336 for child in children.iter() {
338 self.flush_styles_to_layout(*child);
339 }
340
341 if matches!(display, taffy::Display::Flex | taffy::Display::Grid) {
343 children.sort_by(|left, right| {
344 let left_node = self.nodes.get(*left).unwrap();
345 let right_node = self.nodes.get(*right).unwrap();
346 left_node.order().cmp(&right_node.order())
347 });
348 }
349
350 *self.nodes[node_id].layout_children.borrow_mut() = Some(children);
352
353 self.nodes[node_id]
355 .paint_children
356 .borrow_mut()
357 .as_mut()
358 .unwrap()
359 .sort_by(|left, right| {
360 let left_node = self.nodes.get(*left).unwrap();
361 let right_node = self.nodes.get(*right).unwrap();
362 left_node
363 .z_index()
364 .cmp(&right_node.z_index())
365 .then_with(|| {
366 fn position_to_order(pos: Position) -> u8 {
367 match pos {
368 Position::Static | Position::Relative | Position::Sticky => 0,
369 Position::Absolute | Position::Fixed => 1,
370 }
371 }
372 let left_position = left_node
373 .primary_styles()
374 .map(|s| position_to_order(s.clone_position()))
375 .unwrap_or(0);
376 let right_position = right_node
377 .primary_styles()
378 .map(|s| position_to_order(s.clone_position()))
379 .unwrap_or(0);
380
381 left_position.cmp(&right_position)
382 })
383 })
384 }
385 }
386}