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_sActive }
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 = std::ptr::addr_of_mut!(
41 crate::gecko_bindings::structs::ServoTraversalStatistics_sSingleton
42 );
43 (*gecko_stats).mElementsTraversed += stats.elements_traversed;
44 (*gecko_stats).mElementsStyled += stats.elements_styled;
45 (*gecko_stats).mElementsMatched += stats.elements_matched;
46 (*gecko_stats).mStylesShared += stats.styles_shared;
47 (*gecko_stats).mStylesReused += stats.styles_reused;
48 }
49}
50
51fn with_pool_in_place_scope<'scope>(
52 work_unit_max: usize,
53 pool: Option<&rayon::ThreadPool>,
54 closure: impl FnOnce(Option<&rayon::ScopeFifo<'scope>>) + Send + 'scope,
55) {
56 if work_unit_max == 0 || pool.is_none() {
57 closure(None);
58 } else {
59 let pool = pool.unwrap();
60 pool.in_place_scope_fifo(|scope| {
61 #[cfg(feature = "gecko")]
62 debug_assert_eq!(
63 pool.current_thread_index(),
64 Some(0),
65 "Main thread should be the first thread"
66 );
67 if cfg!(feature = "gecko") || pool.current_thread_index().is_some() {
68 closure(Some(scope));
69 } else {
70 scope.spawn_fifo(|scope| closure(Some(scope)));
71 }
72 });
73 }
74}
75
76fn work_unit_max() -> usize {
78 static_prefs::pref!("layout.css.stylo-work-unit-size") as usize
79}
80
81pub fn traverse_dom<E, D>(
91 traversal: &D,
92 token: PreTraverseToken<E>,
93 pool: Option<&rayon::ThreadPool>,
94) -> E
95where
96 E: TElement,
97 D: DomTraversal<E>,
98{
99 let root = token
100 .traversal_root()
101 .expect("Should've ensured we needed to traverse");
102
103 let report_stats = should_report_statistics();
104 let dump_stats = traversal.shared_context().options.dump_style_statistics;
105 let start_time = if dump_stats {
106 Some(Instant::now())
107 } else {
108 None
109 };
110
111 let mut scoped_tls = ScopedTLS::<ThreadLocalStyleContext<E>>::new(pool);
122 let work_unit_max = work_unit_max();
125
126 let send_root = unsafe { SendNode::new(root.as_node()) };
127 with_pool_in_place_scope(work_unit_max, pool, |maybe_scope| {
128 let mut tlc = scoped_tls.ensure(parallel::create_thread_local_context);
129 let mut context = StyleContext {
130 shared: traversal.shared_context(),
131 thread_local: &mut tlc,
132 };
133
134 let mut discovered = VecDeque::with_capacity(work_unit_max * 2);
135 let current_dom_depth = send_root.depth();
136 let opaque_root = send_root.opaque();
137 discovered.push_back(send_root);
138 parallel::style_trees(
139 &mut context,
140 discovered,
141 opaque_root,
142 work_unit_max,
143 PerLevelTraversalData { current_dom_depth },
144 maybe_scope,
145 traversal,
146 &scoped_tls,
147 );
148 });
149
150 if dump_stats || report_stats {
152 let mut aggregate = PerThreadTraversalStatistics::default();
153 for slot in scoped_tls.slots() {
154 if let Some(cx) = slot.get_mut() {
155 aggregate += cx.statistics.clone();
156 }
157 }
158
159 if report_stats {
160 report_statistics(&aggregate);
161 }
162 if dump_stats {
164 let parallel = pool.is_some();
165 let stats =
166 TraversalStatistics::new(aggregate, traversal, parallel, start_time.unwrap());
167 if stats.is_large {
168 println!("{}", stats);
169 }
170 }
171 }
172
173 root
174}