1use apace_traits::node_id::NodeId;
4use std::cell::RefCell;
5
6use apace_debug_timer::apace_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 takia::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 profile::Phase,
36};
37
38impl BaseDocument {
39 pub fn resolve(&mut self, current_time_for_animations: f64) {
41 if TDocument::as_node(&self.root_node())
42 .first_element_child()
43 .is_none()
44 {
45 #[cfg(feature = "tracing")]
46 tracing::warn!("No DOM - not resolving");
47 return;
48 }
49
50 self.handle_messages();
52
53 if self.has_pending_critical_resources() {
63 return;
64 }
65
66 self.resolve_scroll_animation();
67
68 {
71 use crate::node::scrollbar::{FADE_DELAY, FADE_DURATION};
72 self.scrollbar_activity
73 .retain(|_, last| last.elapsed() < FADE_DELAY + FADE_DURATION);
74 }
75
76 let root_node_id = self.root_element().id;
77 apace_debug_timer!(timer, feature = "log-phase-times");
78
79 let style_span = self.profile_span(Phase::Style);
82 self.flush_pending_device_changes();
83
84 self.resolve_stylist(current_time_for_animations);
86 drop(style_span);
87 timer.record_time("style");
88
89 let damage_span = self.profile_span(Phase::Damage);
93 if !self.incremental_layout {
94 self.mark_all_damaged();
95 timer.record_time("mark_all");
96 }
97
98 self.propagate_damage_flags(root_node_id);
100 drop(damage_span);
101 timer.record_time("damage");
102
103 let construct_span = self.profile_span(Phase::Construct);
105 self.resolve_layout_children();
106 #[cfg(debug_assertions)]
107 self.assert_layout_parents_consistent();
108 drop(construct_span);
109 timer.record_time("construct");
110
111 let post_construct_span = self.profile_span(Phase::PostConstruct);
112 self.resolve_deferred_tasks();
113 self.flush_pending_style_images();
117 drop(post_construct_span);
118 timer.record_time("pconstruct");
119
120 let layout_span = self.profile_span(Phase::BoxLayout);
122 self.resolve_layout();
123 drop(layout_span);
124 timer.record_time("layout");
125 self.nodes.bump_geometry_generation();
126
127 let transform_span = self.profile_span(Phase::Transforms);
129 self.resolve_transforms(root_node_id);
130 for index in 0..self.top_layers.len() {
131 if !self.top_layer_is_rendered(self.top_layers[index].node_id) {
132 continue;
133 }
134 self.resolve_transforms(self.top_layers[index].node_id);
135 }
136 timer.record_time("transform");
137 if let Some(focus) = self.focus_node_id {
140 self.clamp_text_input_scroll(focus);
141 }
142 drop(transform_span);
143
144 let paint_tree_span = self.profile_span(Phase::PaintTree);
148 self.build_paint_tree(root_node_id);
149 for index in 0..self.top_layers.len() {
150 if !self.top_layer_is_rendered(self.top_layers[index].node_id) {
151 continue;
152 }
153 self.build_paint_tree(self.top_layers[index].node_id);
154 }
155 drop(paint_tree_span);
156 timer.record_time("paint_tree");
157
158 let doc_node_id = self.root_node().id;
161 let cleanup_span = self.profile_span(Phase::Cleanup);
162 self.clear_damage_and_dirty_flags(doc_node_id);
163 drop(cleanup_span);
164 timer.record_time("c_damage");
165
166 let hover_span = self.profile_span(Phase::Hover);
174 self.refresh_hover();
175 drop(hover_span);
176
177 let subdocuments_span = self.profile_span(Phase::Subdocuments);
178 let mut subdoc_is_animating = false;
179 for &node_id in &self.sub_document_nodes {
180 let node = &mut self.nodes[node_id];
181 let size = node.final_layout().size;
182 if let Some(mut sub_doc) = node.subdoc_mut().map(|doc| doc.inner_mut()) {
183 let mut sub_viewport = sub_doc.viewport_mut();
186 sub_viewport.hidpi_scale = self.viewport.hidpi_scale;
187 sub_viewport.zoom = self.viewport.zoom;
188 sub_viewport.color_scheme = self.viewport.color_scheme;
189
190 let viewport_scale = self.viewport.scale();
191 sub_viewport.window_size = (
192 (size.width * viewport_scale) as u32,
193 (size.height * viewport_scale) as u32,
194 );
195 drop(sub_viewport);
196
197 sub_doc.resolve(current_time_for_animations);
198
199 subdoc_is_animating |= sub_doc.is_animating();
200 }
201 }
202 self.subdoc_is_animating = subdoc_is_animating;
203 drop(subdocuments_span);
204 timer.record_time("subdocs");
205
206 timer.print_times(&format!("Resolve({}): ", self.id()));
207 }
208
209 fn resolve_transforms(&mut self, node_id: NodeId) -> Rect {
210 if !self.nodes.contains_key(node_id) {
211 return Rect::ZERO;
212 }
213
214 let scale = self.viewport.scale_f64();
215
216 if !self.nodes[node_id]
217 .damage()
218 .map(|d| d.contains(style::selector_parser::RestyleDamage::RECALCULATE_OVERFLOW))
219 .unwrap_or(false)
220 {
221 let node = &self.nodes[node_id];
222 let location = node.final_layout().location.map(|v| v as f64 * scale);
223
224 let mut transform = Affine::translate((location.x, location.y));
225 if let Some(t) = node.transform().as_deref() {
226 transform *= *t
227 }
228
229 let overflow = *node.scrollable_overflow();
230 return transform.transform_rect_bbox(overflow);
231 }
232
233 let transform = self.nodes[node_id].set_transform(scale as f32);
234
235 let w = self.nodes[node_id].final_layout().size.width as f64 * scale;
236 let h = self.nodes[node_id].final_layout().size.height as f64 * scale;
237 let mut overflow = Rect::new(0.0, 0.0, w, h);
238
239 let layout_children = std::mem::take(self.nodes[node_id].layout_children.get_mut());
240
241 if let Some(ref children) = layout_children {
242 for &child_id in children {
243 if self.nodes[child_id].is_out_of_flow() {
247 continue;
248 }
249 let child_rect_in_self = self.resolve_transforms(child_id);
250 overflow = overflow.union(child_rect_in_self);
251 }
252 }
253 let hoisted_children =
254 std::mem::take(&mut *self.nodes[node_id].hoisted_children.borrow_mut());
255 for &child_id in &hoisted_children {
256 if !self.nodes.contains_key(child_id) {
257 continue;
258 }
259 let child_rect_in_self = self.resolve_transforms(child_id);
260 overflow = overflow.union(child_rect_in_self);
261 }
262 *self.nodes[node_id].hoisted_children.borrow_mut() = hoisted_children;
263 for pseudo in [self.nodes[node_id].before(), self.nodes[node_id].after()] {
264 let Some(pseudo) = pseudo else { continue };
265 if self.nodes[pseudo].is_out_of_flow() {
266 continue;
267 }
268 let child_rect_in_self = self.resolve_transforms(pseudo);
269 overflow = overflow.union(child_rect_in_self);
270 }
271
272 *self.nodes[node_id].scrollable_overflow_mut() = overflow;
273 *self.nodes[node_id].layout_children.get_mut() = layout_children;
274
275 let scaled_x = self.nodes[node_id].final_layout().location.x as f64 * scale;
276 let scaled_y = self.nodes[node_id].final_layout().location.y as f64 * scale;
277
278 let full = if let Some(t) = transform {
279 Affine::translate((scaled_x, scaled_y)) * t
280 } else {
281 Affine::translate((scaled_x, scaled_y))
282 };
283
284 full.transform_rect_bbox(overflow)
285 }
286
287 pub fn resolve_layout_children(&mut self) {
289 resolve_layout_children_recursive(self, self.root_node().id);
290 for index in 0..self.top_layers.len() {
291 let id = self.top_layers[index].node_id;
292 if !self.top_layer_is_rendered(id) {
293 continue;
294 }
295 resolve_layout_children_recursive(self, id);
296 self.nodes[id].layout_parent.set(None);
297 self.nodes[id].oof_containing_block.set(None);
298 }
299
300 fn resolve_layout_children_recursive(doc: &mut BaseDocument, node_id: NodeId) {
301 if doc.nodes.get(node_id).is_none() {
304 return;
305 }
306
307 let mut damage = doc.nodes[node_id].damage().unwrap_or(ALL_DAMAGE);
308 let _flags = doc.nodes[node_id].flags;
309
310 if damage.intersects(CONSTRUCT_FC | CONSTRUCT_BOX) {
311 let old_anonymous_blocks = std::mem::take(&mut doc.nodes[node_id].anonymous_blocks);
318 for anon_id in old_anonymous_blocks {
319 doc.deallocate_anonymous_block(anon_id);
320 }
321
322 let mut collected = LayoutChildren::default();
323 collect_layout_children(doc, node_id, &mut collected);
324 let mut layout_children = collected.children;
325 layout_children.retain(|id| !doc.nodes[*id].flags.is_top_layer());
326 doc.nodes[node_id].anonymous_blocks = collected.anonymous_blocks;
327
328 for child_id in layout_children.iter().copied() {
330 resolve_layout_children_recursive(doc, child_id);
331 doc.nodes[child_id].layout_parent.set(Some(node_id));
332 if let Some(mut data) = doc.nodes[child_id]
333 .try_stylo_element_data_mut()
334 .and_then(|s| s.get_mut())
335 {
336 data.damage
337 .remove(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
338 }
339 }
340
341 *doc.nodes[node_id].layout_children.borrow_mut() = Some(layout_children.clone());
342 damage.remove(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
345 } else {
347 let layout_children = doc.nodes[node_id].layout_children.borrow_mut().take();
349 if let Some(layout_children) = layout_children {
350 for child_id in layout_children.iter().copied() {
351 if !doc.nodes.contains_key(child_id) {
354 continue;
355 }
356 resolve_layout_children_recursive(doc, child_id);
357 doc.nodes[child_id].layout_parent.set(Some(node_id));
358 }
359
360 *doc.nodes[node_id].layout_children.borrow_mut() = Some(layout_children);
361 }
362
363 }
366
367 doc.nodes[node_id].set_damage(damage);
368 }
369 }
370
371 #[cfg(debug_assertions)]
375 fn assert_layout_parents_consistent(&self) {
376 use crate::node::NodeFlags;
377
378 for (parent_id, node) in self.nodes.iter() {
379 if !node.flags.contains(NodeFlags::IS_IN_DOCUMENT) {
380 continue;
381 }
382 let Some(children) = node.layout_children.borrow().clone() else {
383 continue;
384 };
385 for child_id in children {
386 let Some(child) = self.nodes.get(child_id) else {
387 panic!("layout child {child_id:?} of {parent_id:?} is not in the slab");
388 };
389 debug_assert_eq!(
390 child.layout_parent.get(),
391 Some(parent_id),
392 "layout_parent of {child_id:?} does not point at its layout container {parent_id:?}"
393 );
394 }
395 }
396 }
397
398 pub fn resolve_deferred_tasks(&mut self) {
399 let mut deferred_construction_nodes = std::mem::take(&mut self.deferred_construction_nodes);
400
401 deferred_construction_nodes.sort_unstable_by_key(|task| task.node_id);
403 deferred_construction_nodes.dedup_by_key(|task| task.node_id);
404
405 #[cfg(feature = "parallel-construct")]
406 let iter = deferred_construction_nodes.into_par_iter();
407 #[cfg(not(feature = "parallel-construct"))]
408 let iter = deferred_construction_nodes.into_iter();
409
410 let results: Vec<ConstructionTaskResult> = iter
411 .map(|task: ConstructionTask| match task.data {
412 ConstructionTaskData::InlineLayout(mut layout) => {
413 #[cfg(feature = "parallel-construct")]
414 let mut layout_ctx = LAYOUT_CTX
415 .take()
416 .unwrap_or_else(|| Box::new(LayoutContext::new()));
417 #[cfg(feature = "parallel-construct")]
418 let layout_ctx_mut = &mut layout_ctx;
419
420 #[cfg(feature = "parallel-construct")]
421 let mut font_ctx = self
422 .thread_font_contexts
423 .get_or(|| RefCell::new(Box::new(self.font_ctx.lock().unwrap().clone())))
424 .borrow_mut();
425 #[cfg(feature = "parallel-construct")]
426 let font_ctx_mut = &mut *font_ctx;
427
428 #[cfg(not(feature = "parallel-construct"))]
429 let layout_ctx_mut = &mut self.layout_ctx;
430 #[cfg(not(feature = "parallel-construct"))]
431 let font_ctx_mut = &mut *self.font_ctx.lock().unwrap();
432
433 layout.content_widths = None;
434 build_inline_layout_into(
435 &self.nodes,
436 layout_ctx_mut,
437 font_ctx_mut,
438 &mut layout,
439 self.viewport.scale(),
440 task.node_id,
441 self.profile.as_ref(),
442 );
443
444 #[cfg(feature = "parallel-construct")]
445 {
446 LAYOUT_CTX.set(Some(layout_ctx));
447 }
448
449 ConstructionTaskResult {
456 node_id: task.node_id,
457 data: ConstructionTaskResultData::InlineLayout(layout),
458 }
459 }
460 })
461 .collect();
462
463 for result in results {
464 match result.data {
465 ConstructionTaskResultData::InlineLayout(layout) => {
466 self.nodes[result.node_id].clear_layout_cache();
467 self.nodes[result.node_id]
468 .element_data_mut()
469 .unwrap()
470 .inline_layout_data = Some(layout);
471 }
472 }
473 }
474
475 self.deferred_construction_nodes.clear();
476 }
477
478 pub fn resolve_layout(&mut self) {
483 let size = self.stylist.device().au_viewport_size();
484
485 let available_space = takia::Size {
486 width: AvailableSpace::Definite(size.width.to_f32_px()),
487 height: AvailableSpace::Definite(size.height.to_f32_px()),
488 };
489
490 let root_element_id = crate::takia_node_id(self.root_element().id);
491 let layers: Vec<_> = self
492 .top_layers
493 .iter()
494 .map(|layer| layer.node_id)
495 .filter(|id| self.top_layer_is_rendered(*id))
496 .collect();
497
498 let mut state = LayoutPassState::new(self);
501 #[cfg(feature = "writing-mode")]
502 let available_space = {
503 state.layout_wm = state.layout_wm_of(crate::dom_node_id(root_element_id));
504 if state.layout_wm.is_vertical() {
505 available_space.transpose()
506 } else {
507 available_space
508 }
509 };
510 takia::compute_root_layout(&mut state, root_element_id, available_space);
511 #[cfg(feature = "writing-mode")]
512 state.physicalise_and_round_layout(root_element_id);
513 #[cfg(not(feature = "writing-mode"))]
514 takia::round_layout(&mut state, root_element_id);
515 for id in layers {
516 let id = crate::takia_node_id(id);
517 #[cfg(feature = "writing-mode")]
518 {
519 state.layout_wm = state.layout_wm_of(crate::dom_node_id(id));
520 }
521 #[cfg(feature = "writing-mode")]
522 let layer_space = if state.layout_wm.is_vertical() {
523 takia::Size {
524 width: AvailableSpace::Definite(size.height.to_f32_px()),
525 height: AvailableSpace::Definite(size.width.to_f32_px()),
526 }
527 } else {
528 takia::Size {
529 width: AvailableSpace::Definite(size.width.to_f32_px()),
530 height: AvailableSpace::Definite(size.height.to_f32_px()),
531 }
532 };
533 #[cfg(not(feature = "writing-mode"))]
534 let layer_space = available_space;
535 takia::compute_root_layout(&mut state, id, layer_space);
536 #[cfg(feature = "writing-mode")]
537 state.physicalise_and_round_layout(id);
538 #[cfg(not(feature = "writing-mode"))]
539 takia::round_layout(&mut state, id);
540 }
541
542 }
545}