#![deny(missing_docs)]
use crate::context::{PerThreadTraversalStatistics, StyleContext};
use crate::context::{ThreadLocalStyleContext, TraversalStatistics};
use crate::dom::{SendNode, TElement, TNode};
use crate::parallel;
use crate::scoped_tls::ScopedTLS;
use crate::traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken};
use std::collections::VecDeque;
use std::time::Instant;
#[cfg(feature = "servo")]
fn should_report_statistics() -> bool {
false
}
#[cfg(feature = "gecko")]
fn should_report_statistics() -> bool {
unsafe { crate::gecko_bindings::structs::ServoTraversalStatistics_sActive }
}
#[cfg(feature = "servo")]
fn report_statistics(_stats: &PerThreadTraversalStatistics) {
unreachable!("Servo never report stats");
}
#[cfg(feature = "gecko")]
fn report_statistics(stats: &PerThreadTraversalStatistics) {
unsafe {
debug_assert!(crate::gecko_bindings::bindings::Gecko_IsMainThread());
let gecko_stats = std::ptr::addr_of_mut!(
crate::gecko_bindings::structs::ServoTraversalStatistics_sSingleton
);
(*gecko_stats).mElementsTraversed += stats.elements_traversed;
(*gecko_stats).mElementsStyled += stats.elements_styled;
(*gecko_stats).mElementsMatched += stats.elements_matched;
(*gecko_stats).mStylesShared += stats.styles_shared;
(*gecko_stats).mStylesReused += stats.styles_reused;
}
}
fn with_pool_in_place_scope<'scope>(
work_unit_max: usize,
pool: Option<&rayon::ThreadPool>,
closure: impl FnOnce(Option<&rayon::ScopeFifo<'scope>>) + Send + 'scope,
) {
if work_unit_max == 0 || pool.is_none() {
closure(None);
} else {
let pool = pool.unwrap();
pool.in_place_scope_fifo(|scope| {
#[cfg(feature = "gecko")]
debug_assert_eq!(
pool.current_thread_index(),
Some(0),
"Main thread should be the first thread"
);
if cfg!(feature = "gecko") || pool.current_thread_index().is_some() {
closure(Some(scope));
} else {
scope.spawn_fifo(|scope| closure(Some(scope)));
}
});
}
}
fn work_unit_max() -> usize {
static_prefs::pref!("layout.css.stylo-work-unit-size") as usize
}
pub fn traverse_dom<E, D>(
traversal: &D,
token: PreTraverseToken<E>,
pool: Option<&rayon::ThreadPool>,
) -> E
where
E: TElement,
D: DomTraversal<E>,
{
let root = token
.traversal_root()
.expect("Should've ensured we needed to traverse");
let report_stats = should_report_statistics();
let dump_stats = traversal.shared_context().options.dump_style_statistics;
let start_time = if dump_stats {
Some(Instant::now())
} else {
None
};
let mut scoped_tls = ScopedTLS::<ThreadLocalStyleContext<E>>::new(pool);
let work_unit_max = work_unit_max();
let send_root = unsafe { SendNode::new(root.as_node()) };
with_pool_in_place_scope(work_unit_max, pool, |maybe_scope| {
let mut tlc = scoped_tls.ensure(parallel::create_thread_local_context);
let mut context = StyleContext {
shared: traversal.shared_context(),
thread_local: &mut tlc,
};
let mut discovered = VecDeque::with_capacity(work_unit_max * 2);
let current_dom_depth = send_root.depth();
let opaque_root = send_root.opaque();
discovered.push_back(send_root);
parallel::style_trees(
&mut context,
discovered,
opaque_root,
work_unit_max,
PerLevelTraversalData { current_dom_depth },
maybe_scope,
traversal,
&scoped_tls,
);
});
if dump_stats || report_stats {
let mut aggregate = PerThreadTraversalStatistics::default();
for slot in scoped_tls.slots() {
if let Some(cx) = slot.get_mut() {
aggregate += cx.statistics.clone();
}
}
if report_stats {
report_statistics(&aggregate);
}
if dump_stats {
let parallel = pool.is_some();
let stats =
TraversalStatistics::new(aggregate, traversal, parallel, start_time.unwrap());
if stats.is_large {
println!("{}", stats);
}
}
}
root
}