1use blitz_traits::node_id::NodeId;
4use std::cell::RefCell;
5
6use debug_timer::debug_timer;
7use kurbo::{Affine, Rect};
8use parley::LayoutContext;
9use selectors::Element as _;
10use style::dom::TDocument;
11
12#[cfg(feature = "parallel-construct")]
13use rayon::prelude::*;
14
15thread_local! {
18 pub(crate) static LAYOUT_CTX: RefCell<Option<Box<LayoutContext<TextBrush>>>> = const { RefCell::new(None) };
19}
20
21use taffy::AvailableSpace;
22
23use crate::{
24 BaseDocument,
25 layout::{
26 LayoutPassState,
27 construct::{
28 ConstructionTask, ConstructionTaskData, ConstructionTaskResult,
29 ConstructionTaskResultData, LayoutChildren, build_inline_layout_into,
30 collect_layout_children,
31 },
32 damage::{ALL_DAMAGE, CONSTRUCT_BOX, CONSTRUCT_DESCENDENT, CONSTRUCT_FC},
33 },
34 node::TextBrush,
35};
36
37impl BaseDocument {
38 pub fn resolve(&mut self, current_time_for_animations: f64) {
40 if TDocument::as_node(&self.root_node())
41 .first_element_child()
42 .is_none()
43 {
44 #[cfg(feature = "tracing")]
45 tracing::warn!("No DOM - not resolving");
46 return;
47 }
48
49 self.handle_messages();
51
52 if self.has_pending_critical_resources() {
62 return;
63 }
64
65 self.resolve_scroll_animation();
66
67 {
70 use crate::node::scrollbar::{FADE_DELAY, FADE_DURATION};
71 self.scrollbar_activity
72 .retain(|_, last| last.elapsed() < FADE_DELAY + FADE_DURATION);
73 }
74
75 let root_node_id = self.root_element().id;
76 debug_timer!(timer, feature = "log-phase-times");
77
78 self.flush_pending_device_changes();
81
82 self.resolve_stylist(current_time_for_animations);
84 timer.record_time("style");
85
86 if !self.incremental_layout {
90 self.mark_all_damaged();
91 timer.record_time("mark_all");
92 }
93
94 self.propagate_damage_flags(root_node_id);
96 timer.record_time("damage");
97
98 self.resolve_layout_children();
100 #[cfg(debug_assertions)]
101 self.assert_layout_parents_consistent();
102 timer.record_time("construct");
103
104 self.resolve_deferred_tasks();
105 self.flush_pending_style_images();
109 timer.record_time("pconstruct");
110
111 self.resolve_layout();
113 timer.record_time("layout");
114 self.nodes.bump_geometry_generation();
115
116 self.resolve_transforms(root_node_id);
118 timer.record_time("transform");
119
120 self.build_paint_tree(root_node_id);
124 timer.record_time("paint_tree");
125
126 let doc_node_id = self.root_node().id;
129 self.clear_damage_and_dirty_flags(doc_node_id);
130 timer.record_time("c_damage");
131
132 self.refresh_hover();
140
141 let mut subdoc_is_animating = false;
142 for &node_id in &self.sub_document_nodes {
143 let node = &mut self.nodes[node_id];
144 let size = node.final_layout().size;
145 if let Some(mut sub_doc) = node.subdoc_mut().map(|doc| doc.inner_mut()) {
146 let mut sub_viewport = sub_doc.viewport_mut();
149 sub_viewport.hidpi_scale = self.viewport.hidpi_scale;
150 sub_viewport.zoom = self.viewport.zoom;
151 sub_viewport.color_scheme = self.viewport.color_scheme;
152
153 let viewport_scale = self.viewport.scale();
154 sub_viewport.window_size = (
155 (size.width * viewport_scale) as u32,
156 (size.height * viewport_scale) as u32,
157 );
158 drop(sub_viewport);
159
160 sub_doc.resolve(current_time_for_animations);
161
162 subdoc_is_animating |= sub_doc.is_animating();
163 }
164 }
165 self.subdoc_is_animating = subdoc_is_animating;
166 timer.record_time("subdocs");
167
168 timer.print_times(&format!("Resolve({}): ", self.id()));
169 }
170
171 fn resolve_transforms(&mut self, node_id: NodeId) -> Rect {
172 if !self.nodes.contains_key(node_id) {
173 return Rect::ZERO;
174 }
175
176 let scale = self.viewport.scale_f64();
177
178 if !self.nodes[node_id]
179 .damage()
180 .map(|d| d.contains(style::selector_parser::RestyleDamage::RECALCULATE_OVERFLOW))
181 .unwrap_or(false)
182 {
183 let node = &self.nodes[node_id];
184 let location = node.final_layout().location.map(|v| v as f64 * scale);
185
186 let mut transform = Affine::translate((location.x, location.y));
187 if let Some(t) = node.transform().as_deref() {
188 transform *= *t
189 }
190
191 let overflow = *node.scrollable_overflow();
192 return transform.transform_rect_bbox(overflow);
193 }
194
195 let transform = self.nodes[node_id].set_transform(scale as f32);
196
197 let w = self.nodes[node_id].final_layout().size.width as f64 * scale;
198 let h = self.nodes[node_id].final_layout().size.height as f64 * scale;
199 let mut overflow = Rect::new(0.0, 0.0, w, h);
200
201 let layout_children = std::mem::take(self.nodes[node_id].layout_children.get_mut());
202
203 if let Some(ref children) = layout_children {
204 for &child_id in children {
205 if self.nodes[child_id].is_out_of_flow() {
209 continue;
210 }
211 let child_rect_in_self = self.resolve_transforms(child_id);
212 overflow = overflow.union(child_rect_in_self);
213 }
214 }
215 let hoisted_children =
216 std::mem::take(&mut *self.nodes[node_id].hoisted_children.borrow_mut());
217 for &child_id in &hoisted_children {
218 if !self.nodes.contains_key(child_id) {
219 continue;
220 }
221 let child_rect_in_self = self.resolve_transforms(child_id);
222 overflow = overflow.union(child_rect_in_self);
223 }
224 *self.nodes[node_id].hoisted_children.borrow_mut() = hoisted_children;
225 for pseudo in [self.nodes[node_id].before(), self.nodes[node_id].after()] {
226 let Some(pseudo) = pseudo else { continue };
227 if self.nodes[pseudo].is_out_of_flow() {
228 continue;
229 }
230 let child_rect_in_self = self.resolve_transforms(pseudo);
231 overflow = overflow.union(child_rect_in_self);
232 }
233
234 *self.nodes[node_id].scrollable_overflow_mut() = overflow;
235 *self.nodes[node_id].layout_children.get_mut() = layout_children;
236
237 let scaled_x = self.nodes[node_id].final_layout().location.x as f64 * scale;
238 let scaled_y = self.nodes[node_id].final_layout().location.y as f64 * scale;
239
240 let full = if let Some(t) = transform {
241 Affine::translate((scaled_x, scaled_y)) * t
242 } else {
243 Affine::translate((scaled_x, scaled_y))
244 };
245
246 full.transform_rect_bbox(overflow)
247 }
248
249 pub fn resolve_layout_children(&mut self) {
251 resolve_layout_children_recursive(self, self.root_node().id);
252
253 fn resolve_layout_children_recursive(doc: &mut BaseDocument, node_id: NodeId) {
254 if doc.nodes.get(node_id).is_none() {
257 return;
258 }
259
260 let mut damage = doc.nodes[node_id].damage().unwrap_or(ALL_DAMAGE);
261 let _flags = doc.nodes[node_id].flags;
262
263 if damage.intersects(CONSTRUCT_FC | CONSTRUCT_BOX) {
264 let old_anonymous_blocks = std::mem::take(&mut doc.nodes[node_id].anonymous_blocks);
271 for anon_id in old_anonymous_blocks {
272 doc.deallocate_anonymous_block(anon_id);
273 }
274
275 let mut collected = LayoutChildren::default();
276 collect_layout_children(doc, node_id, &mut collected);
277 let layout_children = collected.children;
278 doc.nodes[node_id].anonymous_blocks = collected.anonymous_blocks;
279
280 for child_id in layout_children.iter().copied() {
282 resolve_layout_children_recursive(doc, child_id);
283 doc.nodes[child_id].layout_parent.set(Some(node_id));
284 if let Some(mut data) = doc.nodes[child_id]
285 .try_stylo_element_data_mut()
286 .and_then(|s| s.get_mut())
287 {
288 data.damage
289 .remove(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
290 }
291 }
292
293 *doc.nodes[node_id].layout_children.borrow_mut() = Some(layout_children.clone());
294 damage.remove(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
297 } else {
299 let layout_children = doc.nodes[node_id].layout_children.borrow_mut().take();
301 if let Some(layout_children) = layout_children {
302 for child_id in layout_children.iter().copied() {
303 if !doc.nodes.contains_key(child_id) {
306 continue;
307 }
308 resolve_layout_children_recursive(doc, child_id);
309 doc.nodes[child_id].layout_parent.set(Some(node_id));
310 }
311
312 *doc.nodes[node_id].layout_children.borrow_mut() = Some(layout_children);
313 }
314
315 }
318
319 doc.nodes[node_id].set_damage(damage);
320 }
321 }
322
323 #[cfg(debug_assertions)]
327 fn assert_layout_parents_consistent(&self) {
328 use crate::node::NodeFlags;
329
330 for (parent_id, node) in self.nodes.iter() {
331 if !node.flags.contains(NodeFlags::IS_IN_DOCUMENT) {
332 continue;
333 }
334 let Some(children) = node.layout_children.borrow().clone() else {
335 continue;
336 };
337 for child_id in children {
338 let Some(child) = self.nodes.get(child_id) else {
339 panic!("layout child {child_id:?} of {parent_id:?} is not in the slab");
340 };
341 debug_assert_eq!(
342 child.layout_parent.get(),
343 Some(parent_id),
344 "layout_parent of {child_id:?} does not point at its layout container {parent_id:?}"
345 );
346 }
347 }
348 }
349
350 pub fn resolve_deferred_tasks(&mut self) {
351 let mut deferred_construction_nodes = std::mem::take(&mut self.deferred_construction_nodes);
352
353 deferred_construction_nodes.sort_unstable_by_key(|task| task.node_id);
355 deferred_construction_nodes.dedup_by_key(|task| task.node_id);
356
357 #[cfg(feature = "parallel-construct")]
358 let iter = deferred_construction_nodes.into_par_iter();
359 #[cfg(not(feature = "parallel-construct"))]
360 let iter = deferred_construction_nodes.into_iter();
361
362 let results: Vec<ConstructionTaskResult> = iter
363 .map(|task: ConstructionTask| match task.data {
364 ConstructionTaskData::InlineLayout(mut layout) => {
365 #[cfg(feature = "parallel-construct")]
366 let mut layout_ctx = LAYOUT_CTX
367 .take()
368 .unwrap_or_else(|| Box::new(LayoutContext::new()));
369 #[cfg(feature = "parallel-construct")]
370 let layout_ctx_mut = &mut layout_ctx;
371
372 #[cfg(feature = "parallel-construct")]
373 let mut font_ctx = self
374 .thread_font_contexts
375 .get_or(|| RefCell::new(Box::new(self.font_ctx.lock().unwrap().clone())))
376 .borrow_mut();
377 #[cfg(feature = "parallel-construct")]
378 let font_ctx_mut = &mut *font_ctx;
379
380 #[cfg(not(feature = "parallel-construct"))]
381 let layout_ctx_mut = &mut self.layout_ctx;
382 #[cfg(not(feature = "parallel-construct"))]
383 let font_ctx_mut = &mut *self.font_ctx.lock().unwrap();
384
385 layout.content_widths = None;
386 build_inline_layout_into(
387 &self.nodes,
388 layout_ctx_mut,
389 font_ctx_mut,
390 &mut layout,
391 self.viewport.scale(),
392 task.node_id,
393 );
394
395 #[cfg(feature = "parallel-construct")]
396 {
397 LAYOUT_CTX.set(Some(layout_ctx));
398 }
399
400 ConstructionTaskResult {
407 node_id: task.node_id,
408 data: ConstructionTaskResultData::InlineLayout(layout),
409 }
410 }
411 })
412 .collect();
413
414 for result in results {
415 match result.data {
416 ConstructionTaskResultData::InlineLayout(layout) => {
417 self.nodes[result.node_id].clear_layout_cache();
418 self.nodes[result.node_id]
419 .element_data_mut()
420 .unwrap()
421 .inline_layout_data = Some(layout);
422 }
423 }
424 }
425
426 self.deferred_construction_nodes.clear();
427 }
428
429 pub fn resolve_layout(&mut self) {
434 let size = self.stylist.device().au_viewport_size();
435
436 let available_space = taffy::Size {
437 width: AvailableSpace::Definite(size.width.to_f32_px()),
438 height: AvailableSpace::Definite(size.height.to_f32_px()),
439 };
440
441 let root_element_id = crate::taffy_node_id(self.root_element().id);
442
443 let mut state = LayoutPassState::new(self);
446 #[cfg(feature = "writing-mode")]
447 let available_space = {
448 state.layout_wm = state.layout_wm_of(crate::dom_node_id(root_element_id));
449 if state.layout_wm.is_vertical() {
450 available_space.transpose()
451 } else {
452 available_space
453 }
454 };
455 taffy::compute_root_layout(&mut state, root_element_id, available_space);
456 #[cfg(feature = "writing-mode")]
457 state.physicalise_and_round_layout(root_element_id);
458 #[cfg(not(feature = "writing-mode"))]
459 taffy::round_layout(&mut state, root_element_id);
460
461 }
464}