1#![deny(missing_docs)]
24
25use crate::context::{StyleContext, ThreadLocalStyleContext};
26use crate::dom::{OpaqueNode, SendNode, TElement};
27use crate::scoped_tls::ScopedTLS;
28use crate::traversal::{DomTraversal, PerLevelTraversalData};
29use std::collections::VecDeque;
30
31#[cfg(feature = "gecko")]
33pub const STYLE_THREAD_STACK_SIZE_KB: usize = 256;
34
35#[cfg(feature = "servo")]
40pub const STYLE_THREAD_STACK_SIZE_KB: usize = const {
41 let default_stack_size = 512;
42 if let Some(user_def_size) = option_env!("SERVO_STYLE_THREAD_STACK_SIZE_KB") {
43 if let Ok(user_def_size) = usize::from_str_radix(user_def_size, 10) {
44 user_def_size
45 } else {
46 panic!("SERVO_STYLE_THREAD_STACK_SIZE_KB must be a valid integer")
47 }
48 } else {
49 default_stack_size
50 }
51};
52
53pub const STACK_SAFETY_MARGIN_KB: usize = 168;
70
71#[inline(never)]
75pub(crate) fn create_thread_local_context<'scope, E>(slot: &mut Option<ThreadLocalStyleContext<E>>)
76where
77 E: TElement + 'scope,
78{
79 *slot = Some(ThreadLocalStyleContext::new());
80}
81
82fn distribute_one_chunk<'a, 'scope, E, D>(
84 items: VecDeque<SendNode<E::ConcreteNode>>,
85 traversal_root: OpaqueNode,
86 work_unit_max: usize,
87 traversal_data: PerLevelTraversalData,
88 scope: &'a rayon::ScopeFifo<'scope>,
89 traversal: &'scope D,
90 tls: &'scope ScopedTLS<'scope, ThreadLocalStyleContext<E>>,
91) where
92 E: TElement + 'scope,
93 D: DomTraversal<E>,
94{
95 scope.spawn_fifo(move |scope| {
96 #[cfg(feature = "gecko")]
97 gecko_profiler_label!(Layout, StyleComputation);
98 let mut tlc = tls.ensure(create_thread_local_context);
99 let mut context = StyleContext {
100 shared: traversal.shared_context(),
101 thread_local: &mut *tlc,
102 };
103 style_trees(
104 &mut context,
105 items,
106 traversal_root,
107 work_unit_max,
108 traversal_data,
109 Some(scope),
110 traversal,
111 tls,
112 );
113 })
114}
115
116fn distribute_work<'a, 'scope, E, D>(
118 mut items: impl Iterator<Item = SendNode<E::ConcreteNode>>,
119 traversal_root: OpaqueNode,
120 work_unit_max: usize,
121 traversal_data: PerLevelTraversalData,
122 scope: &'a rayon::ScopeFifo<'scope>,
123 traversal: &'scope D,
124 tls: &'scope ScopedTLS<'scope, ThreadLocalStyleContext<E>>,
125) where
126 E: TElement + 'scope,
127 D: DomTraversal<E>,
128{
129 use std::iter::FromIterator;
130 loop {
131 let chunk = VecDeque::from_iter(items.by_ref().take(work_unit_max));
132 if chunk.is_empty() {
133 return;
134 }
135 distribute_one_chunk(
136 chunk,
137 traversal_root,
138 work_unit_max,
139 traversal_data,
140 scope,
141 traversal,
142 tls,
143 );
144 }
145}
146
147#[inline]
149pub fn style_trees<'a, 'scope, E, D>(
150 context: &mut StyleContext<E>,
151 mut discovered: VecDeque<SendNode<E::ConcreteNode>>,
152 traversal_root: OpaqueNode,
153 work_unit_max: usize,
154 mut traversal_data: PerLevelTraversalData,
155 scope: Option<&'a rayon::ScopeFifo<'scope>>,
156 traversal: &'scope D,
157 tls: &'scope ScopedTLS<'scope, ThreadLocalStyleContext<E>>,
158) where
159 E: TElement + 'scope,
160 D: DomTraversal<E>,
161{
162 let local_queue_size = if tls.current_thread_index() == 0 {
163 static_prefs::pref!("layout.css.stylo-local-work-queue.in-main-thread")
164 } else {
165 static_prefs::pref!("layout.css.stylo-local-work-queue.in-worker")
166 } as usize;
167
168 let mut nodes_remaining_at_current_depth = discovered.len();
169 while let Some(node) = discovered.pop_front() {
170 let mut children_to_process = 0isize;
171 traversal.process_preorder(&traversal_data, context, *node, |n| {
172 children_to_process += 1;
173 discovered.push_back(unsafe { SendNode::new(n) });
174 });
175
176 traversal.handle_postorder_traversal(context, traversal_root, *node, children_to_process);
177
178 nodes_remaining_at_current_depth -= 1;
179
180 let discovered_children = discovered.len() - nodes_remaining_at_current_depth;
184 if discovered_children >= work_unit_max
185 && discovered.len() >= local_queue_size + work_unit_max
186 && scope.is_some()
187 {
188 let kept_work = std::cmp::max(nodes_remaining_at_current_depth, local_queue_size);
189 let mut traversal_data_copy = traversal_data.clone();
190 traversal_data_copy.current_dom_depth += 1;
191 distribute_work(
192 discovered.range(kept_work..).cloned(),
193 traversal_root,
194 work_unit_max,
195 traversal_data_copy,
196 scope.unwrap(),
197 traversal,
198 tls,
199 );
200 discovered.truncate(kept_work);
201 }
202
203 if nodes_remaining_at_current_depth == 0 {
204 traversal_data.current_dom_depth += 1;
205 nodes_remaining_at_current_depth = discovered.len();
206 }
207 }
208}