#![deny(missing_docs)]
use crate::context::{StyleContext, ThreadLocalStyleContext};
use crate::dom::{OpaqueNode, SendNode, TElement};
use crate::scoped_tls::ScopedTLS;
use crate::traversal::{DomTraversal, PerLevelTraversalData};
use std::collections::VecDeque;
#[cfg(feature = "gecko")]
pub const STYLE_THREAD_STACK_SIZE_KB: usize = 256;
#[cfg(feature = "servo")]
pub const STYLE_THREAD_STACK_SIZE_KB: usize = 512;
pub const STACK_SAFETY_MARGIN_KB: usize = 168;
#[inline(never)]
pub(crate) fn create_thread_local_context<'scope, E>(slot: &mut Option<ThreadLocalStyleContext<E>>)
where
E: TElement + 'scope,
{
*slot = Some(ThreadLocalStyleContext::new());
}
fn distribute_one_chunk<'a, 'scope, E, D>(
items: VecDeque<SendNode<E::ConcreteNode>>,
traversal_root: OpaqueNode,
work_unit_max: usize,
traversal_data: PerLevelTraversalData,
scope: &'a rayon::ScopeFifo<'scope>,
traversal: &'scope D,
tls: &'scope ScopedTLS<'scope, ThreadLocalStyleContext<E>>,
) where
E: TElement + 'scope,
D: DomTraversal<E>,
{
scope.spawn_fifo(move |scope| {
#[cfg(feature = "gecko")]
gecko_profiler_label!(Layout, StyleComputation);
let mut tlc = tls.ensure(create_thread_local_context);
let mut context = StyleContext {
shared: traversal.shared_context(),
thread_local: &mut *tlc,
};
style_trees(
&mut context,
items,
traversal_root,
work_unit_max,
traversal_data,
Some(scope),
traversal,
tls,
);
})
}
fn distribute_work<'a, 'scope, E, D>(
mut items: impl Iterator<Item = SendNode<E::ConcreteNode>>,
traversal_root: OpaqueNode,
work_unit_max: usize,
traversal_data: PerLevelTraversalData,
scope: &'a rayon::ScopeFifo<'scope>,
traversal: &'scope D,
tls: &'scope ScopedTLS<'scope, ThreadLocalStyleContext<E>>,
) where
E: TElement + 'scope,
D: DomTraversal<E>,
{
use std::iter::FromIterator;
loop {
let chunk = VecDeque::from_iter(items.by_ref().take(work_unit_max));
if chunk.is_empty() {
return;
}
distribute_one_chunk(
chunk,
traversal_root,
work_unit_max,
traversal_data,
scope,
traversal,
tls,
);
}
}
#[inline]
pub fn style_trees<'a, 'scope, E, D>(
context: &mut StyleContext<E>,
mut discovered: VecDeque<SendNode<E::ConcreteNode>>,
traversal_root: OpaqueNode,
work_unit_max: usize,
mut traversal_data: PerLevelTraversalData,
scope: Option<&'a rayon::ScopeFifo<'scope>>,
traversal: &'scope D,
tls: &'scope ScopedTLS<'scope, ThreadLocalStyleContext<E>>,
) where
E: TElement + 'scope,
D: DomTraversal<E>,
{
let local_queue_size = if tls.current_thread_index() == 0 {
static_prefs::pref!("layout.css.stylo-local-work-queue.in-main-thread")
} else {
static_prefs::pref!("layout.css.stylo-local-work-queue.in-worker")
} as usize;
let mut nodes_remaining_at_current_depth = discovered.len();
while let Some(node) = discovered.pop_front() {
let mut children_to_process = 0isize;
traversal.process_preorder(&traversal_data, context, *node, |n| {
children_to_process += 1;
discovered.push_back(unsafe { SendNode::new(n) });
});
traversal.handle_postorder_traversal(context, traversal_root, *node, children_to_process);
nodes_remaining_at_current_depth -= 1;
let discovered_children = discovered.len() - nodes_remaining_at_current_depth;
if discovered_children >= work_unit_max
&& discovered.len() >= local_queue_size + work_unit_max
&& scope.is_some()
{
let kept_work = std::cmp::max(nodes_remaining_at_current_depth, local_queue_size);
let mut traversal_data_copy = traversal_data.clone();
traversal_data_copy.current_dom_depth += 1;
distribute_work(
discovered.range(kept_work..).cloned(),
traversal_root,
work_unit_max,
traversal_data_copy,
scope.unwrap(),
traversal,
tls,
);
discovered.truncate(kept_work);
}
if nodes_remaining_at_current_depth == 0 {
traversal_data.current_dom_depth += 1;
nodes_remaining_at_current_depth = discovered.len();
}
}
}