1#![deny(missing_docs)]
9
10use crate::context::{PerThreadTraversalStatistics, StyleContext};
11use crate::context::{ThreadLocalStyleContext, TraversalStatistics};
12use crate::dom::{SendNode, TElement, TNode};
13use crate::parallel;
14use crate::scoped_tls::ScopedTLS;
15use crate::traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken};
16use std::collections::VecDeque;
17use std::time::Instant;
18
19#[cfg(feature = "servo")]
20fn should_report_statistics() -> bool {
21 false
22}
23
24#[cfg(feature = "gecko")]
25fn should_report_statistics() -> bool {
26 unsafe { !crate::gecko_bindings::structs::ServoTraversalStatistics_sSingleton.is_null() }
27}
28
29#[cfg(feature = "servo")]
30fn report_statistics(_stats: &PerThreadTraversalStatistics) {
31 unreachable!("Servo never report stats");
32}
33
34#[cfg(feature = "gecko")]
35fn report_statistics(stats: &PerThreadTraversalStatistics) {
36 unsafe {
39 debug_assert!(crate::gecko_bindings::bindings::Gecko_IsMainThread());
40 let gecko_stats = crate::gecko_bindings::structs::ServoTraversalStatistics_sSingleton;
41 (*gecko_stats).mElementsTraversed += stats.elements_traversed;
42 (*gecko_stats).mElementsStyled += stats.elements_styled;
43 (*gecko_stats).mElementsMatched += stats.elements_matched;
44 (*gecko_stats).mStylesShared += stats.styles_shared;
45 (*gecko_stats).mStylesReused += stats.styles_reused;
46 }
47}
48
49fn with_pool_in_place_scope<'scope>(
50 work_unit_max: usize,
51 pool: Option<&rayon::ThreadPool>,
52 closure: impl FnOnce(Option<&rayon::ScopeFifo<'scope>>) + Send + 'scope,
53) {
54 if work_unit_max == 0 || pool.is_none() {
55 closure(None);
56 } else {
57 let pool = pool.unwrap();
58 pool.in_place_scope_fifo(|scope| {
59 #[cfg(feature = "gecko")]
60 debug_assert_eq!(
61 pool.current_thread_index(),
62 Some(0),
63 "Main thread should be the first thread"
64 );
65 if cfg!(feature = "gecko") || pool.current_thread_index().is_some() {
66 closure(Some(scope));
67 } else {
68 scope.spawn_fifo(|scope| closure(Some(scope)));
69 }
70 });
71 }
72}
73
74fn work_unit_max() -> usize {
76 static_prefs::pref!("layout.css.stylo-work-unit-size") as usize
77}
78
79pub fn traverse_dom<E, D>(
89 traversal: &D,
90 token: PreTraverseToken<E>,
91 pool: Option<&rayon::ThreadPool>,
92) -> E
93where
94 E: TElement,
95 D: DomTraversal<E>,
96{
97 let root = token
98 .traversal_root()
99 .expect("Should've ensured we needed to traverse");
100
101 let report_stats = should_report_statistics();
102 let dump_stats = traversal.shared_context().options.dump_style_statistics;
103 let start_time = if dump_stats {
104 Some(Instant::now())
105 } else {
106 None
107 };
108
109 let mut scoped_tls = ScopedTLS::<ThreadLocalStyleContext<E>>::new(pool);
120 let work_unit_max = work_unit_max();
123
124 let send_root = unsafe { SendNode::new(root.as_node()) };
125 with_pool_in_place_scope(work_unit_max, pool, |maybe_scope| {
126 let mut tlc = scoped_tls.ensure(parallel::create_thread_local_context);
127 let mut context = StyleContext {
128 shared: traversal.shared_context(),
129 thread_local: &mut tlc,
130 };
131
132 let mut discovered = VecDeque::with_capacity(work_unit_max * 2);
133 let current_dom_depth = send_root.depth();
134 let opaque_root = send_root.opaque();
135 discovered.push_back(send_root);
136 parallel::style_trees(
137 &mut context,
138 discovered,
139 opaque_root,
140 work_unit_max,
141 PerLevelTraversalData { current_dom_depth },
142 maybe_scope,
143 traversal,
144 &scoped_tls,
145 );
146 });
147
148 if dump_stats || report_stats {
150 let mut aggregate = PerThreadTraversalStatistics::default();
151 for slot in scoped_tls.slots() {
152 if let Some(cx) = slot.get_mut() {
153 aggregate += cx.statistics.clone();
154 }
155 }
156
157 if report_stats {
158 report_statistics(&aggregate);
159 }
160 if dump_stats {
162 let parallel = pool.is_some();
163 let stats =
164 TraversalStatistics::new(aggregate, traversal, parallel, start_time.unwrap());
165 if stats.is_large {
166 println!("{}", stats);
167 }
168 }
169 }
170
171 root
172}