1use std::{
4 cell::RefCell,
5 time::{SystemTime, UNIX_EPOCH},
6};
7
8use debug_timer::debug_timer;
9use kurbo::{Affine, Rect};
10use parley::LayoutContext;
11use selectors::Element as _;
12use style::dom::TDocument;
13
14#[cfg(feature = "parallel-construct")]
15use rayon::prelude::*;
16
17thread_local! {
20 pub(crate) static LAYOUT_CTX: RefCell<Option<Box<LayoutContext<TextBrush>>>> = const { RefCell::new(None) };
21}
22
23use style::properties::generated::longhands::position::computed_value::T as Position;
24use style::selector_parser::RestyleDamage;
25use taffy::AvailableSpace;
26
27use crate::{
28 BaseDocument,
29 events::ScrollAnimationState,
30 layout::{
31 construct::{
32 ConstructionTask, ConstructionTaskData, ConstructionTaskResult,
33 ConstructionTaskResultData, LayoutChildren, build_inline_layout_into,
34 collect_layout_children,
35 },
36 damage::{ALL_DAMAGE, CONSTRUCT_BOX, CONSTRUCT_DESCENDENT, CONSTRUCT_FC},
37 },
38 node::TextBrush,
39};
40
41impl BaseDocument {
42 pub fn resolve(&mut self, current_time_for_animations: f64) {
44 if TDocument::as_node(&&self.nodes[0])
45 .first_element_child()
46 .is_none()
47 {
48 #[cfg(feature = "tracing")]
49 tracing::warn!("No DOM - not resolving");
50 return;
51 }
52
53 self.handle_messages();
55
56 self.resolve_scroll_animation();
57
58 let nodes = &self.nodes;
62 self.scrollbar_activity
63 .retain(|node_id, _| nodes.contains(*node_id));
64
65 let root_node_id = self.root_element().id;
66 debug_timer!(timer, feature = "log-phase-times");
67
68 self.resolve_stylist(current_time_for_animations);
70 timer.record_time("style");
71
72 if self.incremental_layout {
74 self.propagate_damage_flags(root_node_id, RestyleDamage::empty());
75 timer.record_time("damage");
76 }
77
78 self.resolve_layout_children();
80 timer.record_time("construct");
81
82 self.resolve_deferred_tasks();
83 timer.record_time("pconstruct");
84
85 self.flush_styles_to_layout(root_node_id);
87 timer.record_time("flush");
88
89 self.resolve_layout();
91 timer.record_time("layout");
92
93 self.resolve_transforms(root_node_id);
94 timer.record_time("transform");
95
96 if self.incremental_layout {
98 for (_, node) in self.nodes.iter_mut() {
99 node.clear_damage_mut();
100 node.unset_dirty_descendants();
101 }
102 timer.record_time("c_damage");
103 }
104
105 let mut subdoc_is_animating = false;
106 for &node_id in &self.sub_document_nodes {
107 let node = &mut self.nodes[node_id];
108 let size = node.final_layout.size;
109 if let Some(mut sub_doc) = node.subdoc_mut().map(|doc| doc.inner_mut()) {
110 let mut sub_viewport = sub_doc.viewport_mut();
113 sub_viewport.hidpi_scale = self.viewport.hidpi_scale;
114 sub_viewport.zoom = self.viewport.zoom;
115 sub_viewport.color_scheme = self.viewport.color_scheme;
116
117 let viewport_scale = self.viewport.scale();
118 sub_viewport.window_size = (
119 (size.width * viewport_scale) as u32,
120 (size.height * viewport_scale) as u32,
121 );
122 drop(sub_viewport);
123
124 sub_doc.resolve(current_time_for_animations);
125
126 subdoc_is_animating |= sub_doc.is_animating();
127 }
128 }
129 self.subdoc_is_animating = subdoc_is_animating;
130 timer.record_time("subdocs");
131
132 #[cfg(feature = "log-phase-times")]
137 {
138 let offenders = crate::layout::layout_counters::worst_offenders(6);
141 if offenders.first().is_some_and(|(_, count)| *count > 8) {
142 let described: Vec<String> = offenders
143 .iter()
144 .map(|(id, count)| {
145 let tag = self
146 .nodes
147 .get(*id)
148 .and_then(|node| node.element_data())
149 .map(|element| element.name.local.to_string())
150 .unwrap_or_else(|| "?".to_string());
151 let display = self
152 .nodes
153 .get(*id)
154 .map(|node| format!("{:?}", node.style.display))
155 .unwrap_or_default();
156 format!("{id}:{tag}({display})x{count}")
157 })
158 .collect();
159 println!(" layout hotspots: {}", described.join(" "));
160 }
161 let counts = crate::layout::layout_counters::take();
162 let total_nodes = self.nodes.len();
163 let hit_rate = if counts.lookups > 0 {
164 (counts.hits as f64 / counts.lookups as f64) * 100.0
165 } else {
166 0.0
167 };
168 timer.print_times(&format!(
169 "Resolve({}) [computed {} over {} distinct of {total_nodes} nodes, \
170 cache {}/{} hits {hit_rate:.0}%, {} cleared]: ",
171 self.id(),
172 counts.computed,
173 counts.distinct,
174 counts.hits,
175 counts.lookups,
176 counts.caches_cleared,
177 ));
178 }
179 #[cfg(not(feature = "log-phase-times"))]
180 timer.print_times(&format!("Resolve({}): ", self.id()));
181 }
182
183 fn resolve_transforms(&mut self, node_id: usize) -> Rect {
184 if !self.nodes.contains(node_id) {
185 return Rect::ZERO;
186 }
187
188 if !self.nodes[node_id]
189 .damage()
190 .map(|d| d.contains(style::selector_parser::RestyleDamage::RECALCULATE_OVERFLOW))
191 .unwrap_or(false)
192 {
193 return self.nodes[node_id].scrollable_overflow;
194 }
195
196 let scale = self.viewport.scale_f64();
197
198 let transform = self.nodes[node_id].set_transform(scale as f32);
199
200 let w = self.nodes[node_id].final_layout.size.width as f64 * scale;
201 let h = self.nodes[node_id].final_layout.size.height as f64 * scale;
202 let mut overflow = Rect::new(0.0, 0.0, w, h);
203
204 let layout_children = std::mem::take(self.nodes[node_id].layout_children.get_mut());
205
206 if let Some(ref children) = layout_children {
207 for &child_id in children {
208 let child_rect_in_self = self.resolve_transforms(child_id);
209 overflow = overflow.union(child_rect_in_self);
210 }
211 }
212 if let Some(before) = self.nodes[node_id].before {
213 let child_rect_in_self = self.resolve_transforms(before);
214 overflow = overflow.union(child_rect_in_self);
215 }
216 if let Some(after) = self.nodes[node_id].after {
217 let child_rect_in_self = self.resolve_transforms(after);
218 overflow = overflow.union(child_rect_in_self);
219 }
220
221 self.nodes[node_id].scrollable_overflow = overflow;
222 *self.nodes[node_id].layout_children.get_mut() = layout_children;
223
224 let scaled_x = self.nodes[node_id].final_layout.location.x as f64 * scale;
225 let scaled_y = self.nodes[node_id].final_layout.location.y as f64 * scale;
226
227 let full = if let Some(t) = transform {
228 Affine::translate((scaled_x, scaled_y)) * t
229 } else {
230 Affine::translate((scaled_x, scaled_y))
231 };
232
233 full.transform_rect_bbox(overflow)
234 }
235
236 pub fn resolve_scroll_animation(&mut self) {
237 match &mut self.scroll_animation {
238 ScrollAnimationState::Fling(fling_state) => {
239 let time_ms = SystemTime::now()
240 .duration_since(UNIX_EPOCH)
241 .unwrap()
242 .as_millis() as u64 as f64;
243
244 let time_diff_ms = time_ms - fling_state.last_seen_time;
245
246 let deceleration = 1.0 - ((0.05 / 16.66666) * time_diff_ms);
248
249 fling_state.x_velocity *= deceleration;
250 fling_state.y_velocity *= deceleration;
251 fling_state.last_seen_time = time_ms;
252 let fling_state = fling_state.clone();
253
254 let dx = fling_state.x_velocity * time_diff_ms;
255 let dy = fling_state.y_velocity * time_diff_ms;
256
257 self.scroll_by(Some(fling_state.target), dx, dy, &mut |_| {});
258 if fling_state.x_velocity.abs() < 0.1 && fling_state.y_velocity.abs() < 0.1 {
259 self.scroll_animation = ScrollAnimationState::None;
260 }
261 }
262 ScrollAnimationState::None => {
263 }
265 }
266 }
267
268 pub fn resolve_layout_children(&mut self) {
270 resolve_layout_children_recursive(self, self.root_node().id);
271
272 fn resolve_layout_children_recursive(doc: &mut BaseDocument, node_id: usize) {
273 if doc.nodes.get(node_id).is_none() {
276 return;
277 }
278
279 let mut damage = doc.nodes[node_id].damage().unwrap_or(ALL_DAMAGE);
280 let _flags = doc.nodes[node_id].flags;
281
282 if !doc.incremental_layout || damage.intersects(CONSTRUCT_FC | CONSTRUCT_BOX) {
283 let mut collected = LayoutChildren::default();
285 collect_layout_children(doc, node_id, &mut collected);
286 let layout_children = collected.children;
287
288 for child_id in layout_children.iter().copied() {
290 resolve_layout_children_recursive(doc, child_id);
291 doc.nodes[child_id].layout_parent.set(Some(node_id));
292 if let Some(mut data) = doc.nodes[child_id].stylo_element_data.get_mut() {
293 data.damage
294 .remove(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
295 }
296 }
297
298 *doc.nodes[node_id].layout_children.borrow_mut() = Some(layout_children.clone());
299 damage.remove(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
302 } else {
304 let layout_children = doc.nodes[node_id].layout_children.borrow_mut().take();
306 if let Some(layout_children) = layout_children {
307 for child_id in layout_children.iter().copied() {
308 if !doc.nodes.contains(child_id) {
311 continue;
312 }
313 resolve_layout_children_recursive(doc, child_id);
314 doc.nodes[child_id].layout_parent.set(Some(node_id));
315 }
316
317 *doc.nodes[node_id].layout_children.borrow_mut() = Some(layout_children);
318 }
319
320 }
323
324 doc.nodes[node_id].set_damage(damage);
325 }
326 }
327
328 pub fn resolve_deferred_tasks(&mut self) {
329 let mut deferred_construction_nodes = std::mem::take(&mut self.deferred_construction_nodes);
330
331 deferred_construction_nodes.sort_unstable_by_key(|task| task.node_id);
333 deferred_construction_nodes.dedup_by_key(|task| task.node_id);
334
335 #[cfg(feature = "parallel-construct")]
336 let iter = deferred_construction_nodes.into_par_iter();
337 #[cfg(not(feature = "parallel-construct"))]
338 let iter = deferred_construction_nodes.into_iter();
339
340 let results: Vec<ConstructionTaskResult> = iter
341 .map(|task: ConstructionTask| match task.data {
342 ConstructionTaskData::InlineLayout(mut layout) => {
343 #[cfg(feature = "parallel-construct")]
344 let mut layout_ctx = LAYOUT_CTX
345 .take()
346 .unwrap_or_else(|| Box::new(LayoutContext::new()));
347 #[cfg(feature = "parallel-construct")]
348 let layout_ctx_mut = &mut layout_ctx;
349
350 #[cfg(feature = "parallel-construct")]
351 let mut font_ctx = self
352 .thread_font_contexts
353 .get_or(|| RefCell::new(Box::new(self.font_ctx.lock().unwrap().clone())))
354 .borrow_mut();
355 #[cfg(feature = "parallel-construct")]
356 let font_ctx_mut = &mut *font_ctx;
357
358 #[cfg(not(feature = "parallel-construct"))]
359 let layout_ctx_mut = &mut self.layout_ctx;
360 #[cfg(not(feature = "parallel-construct"))]
361 let font_ctx_mut = &mut *self.font_ctx.lock().unwrap();
362
363 layout.content_widths = None;
364 build_inline_layout_into(
365 &self.nodes,
366 layout_ctx_mut,
367 font_ctx_mut,
368 &mut layout,
369 self.viewport.scale(),
370 task.node_id,
371 );
372
373 #[cfg(feature = "parallel-construct")]
374 {
375 LAYOUT_CTX.set(Some(layout_ctx));
376 }
377
378 ConstructionTaskResult {
385 node_id: task.node_id,
386 data: ConstructionTaskResultData::InlineLayout(layout),
387 }
388 }
389 })
390 .collect();
391
392 for result in results {
393 match result.data {
394 ConstructionTaskResultData::InlineLayout(layout) => {
395 self.nodes[result.node_id].cache.clear();
396 self.nodes[result.node_id]
397 .element_data_mut()
398 .unwrap()
399 .inline_layout_data = Some(layout);
400 }
401 }
402 }
403
404 self.deferred_construction_nodes.clear();
405 }
406
407 pub fn resolve_layout(&mut self) {
412 let size = self.stylist.device().au_viewport_size();
413
414 let available_space = taffy::Size {
415 width: AvailableSpace::Definite(size.width.to_f32_px()),
416 height: AvailableSpace::Definite(size.height.to_f32_px()),
417 };
418
419 let root_element_id = taffy::NodeId::from(self.root_element().id);
420
421 taffy::compute_root_layout(self, root_element_id, available_space);
424 taffy::round_layout(self, root_element_id);
425
426 let fixed_nodes = self
433 .nodes
434 .iter()
435 .filter_map(|(node_id, node)| {
436 let is_fixed = node
437 .primary_styles()
438 .is_some_and(|style| style.clone_position() == Position::Fixed);
439 is_fixed.then_some((node_id, node.layout_parent.get()))
440 })
441 .collect::<Vec<_>>();
442
443 for (node_id, parent_id) in fixed_nodes {
444 let Some(parent_id) = parent_id else {
445 continue;
446 };
447 let parent_position = self.nodes[parent_id].absolute_position(0.0, 0.0);
448 self.nodes[node_id].final_layout.location.x -= parent_position.x;
449 self.nodes[node_id].final_layout.location.y -= parent_position.y;
450 }
451
452 }
455}