use std::{
cell::RefCell,
time::{SystemTime, UNIX_EPOCH},
};
use debug_timer::debug_timer;
use parley::LayoutContext;
use selectors::Element as _;
use style::dom::TDocument;
#[cfg(feature = "parallel-construct")]
use rayon::prelude::*;
thread_local! {
pub(crate) static LAYOUT_CTX: RefCell<Option<Box<LayoutContext<TextBrush>>>> = const { RefCell::new(None) };
}
#[cfg(feature = "incremental")]
use style::selector_parser::RestyleDamage;
use taffy::AvailableSpace;
use crate::{
BaseDocument, NON_INCREMENTAL,
events::ScrollAnimationState,
layout::{
construct::{
ConstructionTask, ConstructionTaskData, ConstructionTaskResult,
ConstructionTaskResultData, build_inline_layout_into, collect_layout_children,
},
damage::{ALL_DAMAGE, CONSTRUCT_BOX, CONSTRUCT_DESCENDENT, CONSTRUCT_FC},
},
node::TextBrush,
};
impl BaseDocument {
pub fn resolve(&mut self, current_time_for_animations: f64) {
if TDocument::as_node(&&self.nodes[0])
.first_element_child()
.is_none()
{
#[cfg(feature = "tracing")]
tracing::warn!("No DOM - not resolving");
return;
}
self.handle_messages();
self.resolve_scroll_animation();
let root_node_id = self.root_element().id;
debug_timer!(timer, feature = "log_phase_times");
self.resolve_stylist(current_time_for_animations);
timer.record_time("style");
#[cfg(feature = "incremental")]
self.propagate_damage_flags(root_node_id, RestyleDamage::empty());
#[cfg(feature = "incremental")]
timer.record_time("damage");
self.resolve_layout_children();
timer.record_time("construct");
self.resolve_deferred_tasks();
timer.record_time("pconstruct");
self.flush_styles_to_layout(root_node_id);
timer.record_time("flush");
self.resolve_layout();
timer.record_time("layout");
#[cfg(feature = "incremental")]
{
for (_, node) in self.nodes.iter_mut() {
node.clear_damage_mut();
node.unset_dirty_descendants();
}
timer.record_time("c_damage");
}
let mut subdoc_is_animating = false;
for &node_id in &self.sub_document_nodes {
let node = &mut self.nodes[node_id];
let size = node.final_layout.size;
if let Some(mut sub_doc) = node.subdoc_mut().map(|doc| doc.inner_mut()) {
let mut sub_viewport = sub_doc.viewport_mut();
sub_viewport.hidpi_scale = self.viewport.hidpi_scale;
sub_viewport.zoom = self.viewport.zoom;
sub_viewport.color_scheme = self.viewport.color_scheme;
let viewport_scale = self.viewport.scale();
sub_viewport.window_size = (
(size.width * viewport_scale) as u32,
(size.height * viewport_scale) as u32,
);
drop(sub_viewport);
sub_doc.resolve(current_time_for_animations);
subdoc_is_animating |= sub_doc.is_animating();
}
}
self.subdoc_is_animating = subdoc_is_animating;
timer.record_time("subdocs");
timer.print_times(&format!("Resolve({}): ", self.id()));
}
pub fn resolve_scroll_animation(&mut self) {
match &mut self.scroll_animation {
ScrollAnimationState::Fling(fling_state) => {
let time_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64 as f64;
let time_diff_ms = time_ms - fling_state.last_seen_time;
let deceleration = 1.0 - ((0.05 / 16.66666) * time_diff_ms);
fling_state.x_velocity *= deceleration;
fling_state.y_velocity *= deceleration;
fling_state.last_seen_time = time_ms;
let fling_state = fling_state.clone();
let dx = fling_state.x_velocity * time_diff_ms;
let dy = fling_state.y_velocity * time_diff_ms;
self.scroll_by(Some(fling_state.target), dx, dy, &mut |_| {});
if fling_state.x_velocity.abs() < 0.1 && fling_state.y_velocity.abs() < 0.1 {
self.scroll_animation = ScrollAnimationState::None;
}
}
ScrollAnimationState::None => {
}
}
}
pub fn resolve_layout_children(&mut self) {
resolve_layout_children_recursive(self, self.root_node().id);
fn resolve_layout_children_recursive(doc: &mut BaseDocument, node_id: usize) {
let mut damage = doc.nodes[node_id].damage().unwrap_or(ALL_DAMAGE);
let _flags = doc.nodes[node_id].flags;
if NON_INCREMENTAL || damage.intersects(CONSTRUCT_FC | CONSTRUCT_BOX) {
let mut layout_children = Vec::new();
let mut anonymous_block: Option<usize> = None;
collect_layout_children(doc, node_id, &mut layout_children, &mut anonymous_block);
for child_id in layout_children.iter().copied() {
resolve_layout_children_recursive(doc, child_id);
doc.nodes[child_id].layout_parent.set(Some(node_id));
if let Some(mut data) = doc.nodes[child_id].stylo_element_data.get_mut() {
data.damage
.remove(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
}
}
*doc.nodes[node_id].layout_children.borrow_mut() = Some(layout_children.clone());
damage.remove(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
} else {
let layout_children = doc.nodes[node_id].layout_children.borrow_mut().take();
if let Some(layout_children) = layout_children {
for child_id in layout_children.iter().copied() {
resolve_layout_children_recursive(doc, child_id);
doc.nodes[child_id].layout_parent.set(Some(node_id));
}
*doc.nodes[node_id].layout_children.borrow_mut() = Some(layout_children);
}
}
doc.nodes[node_id].set_damage(damage);
}
}
pub fn resolve_deferred_tasks(&mut self) {
let mut deferred_construction_nodes = std::mem::take(&mut self.deferred_construction_nodes);
deferred_construction_nodes.sort_unstable_by_key(|task| task.node_id);
deferred_construction_nodes.dedup_by_key(|task| task.node_id);
#[cfg(feature = "parallel-construct")]
let iter = deferred_construction_nodes.into_par_iter();
#[cfg(not(feature = "parallel-construct"))]
let iter = deferred_construction_nodes.into_iter();
let results: Vec<ConstructionTaskResult> = iter
.map(|task: ConstructionTask| match task.data {
ConstructionTaskData::InlineLayout(mut layout) => {
#[cfg(feature = "parallel-construct")]
let mut layout_ctx = LAYOUT_CTX
.take()
.unwrap_or_else(|| Box::new(LayoutContext::new()));
#[cfg(feature = "parallel-construct")]
let layout_ctx_mut = &mut layout_ctx;
#[cfg(feature = "parallel-construct")]
let mut font_ctx = self
.thread_font_contexts
.get_or(|| RefCell::new(Box::new(self.font_ctx.lock().unwrap().clone())))
.borrow_mut();
#[cfg(feature = "parallel-construct")]
let font_ctx_mut = &mut *font_ctx;
#[cfg(not(feature = "parallel-construct"))]
let layout_ctx_mut = &mut self.layout_ctx;
#[cfg(not(feature = "parallel-construct"))]
let font_ctx_mut = &mut *self.font_ctx.lock().unwrap();
layout.content_widths = None;
build_inline_layout_into(
&self.nodes,
layout_ctx_mut,
font_ctx_mut,
&mut layout,
self.viewport.scale(),
task.node_id,
);
#[cfg(feature = "parallel-construct")]
{
LAYOUT_CTX.set(Some(layout_ctx));
}
ConstructionTaskResult {
node_id: task.node_id,
data: ConstructionTaskResultData::InlineLayout(layout),
}
}
})
.collect();
for result in results {
match result.data {
ConstructionTaskResultData::InlineLayout(layout) => {
self.nodes[result.node_id].cache.clear();
self.nodes[result.node_id]
.element_data_mut()
.unwrap()
.inline_layout_data = Some(layout);
}
}
}
self.deferred_construction_nodes.clear();
}
pub fn resolve_layout(&mut self) {
let size = self.stylist.device().au_viewport_size();
let available_space = taffy::Size {
width: AvailableSpace::Definite(size.width.to_f32_px()),
height: AvailableSpace::Definite(size.height.to_f32_px()),
};
let root_element_id = taffy::NodeId::from(self.root_element().id);
taffy::compute_root_layout(self, root_element_id, available_space);
taffy::round_layout(self, root_element_id);
}
}