174 lines
6.4 KiB
Rust
174 lines
6.4 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
//! Implements traversal over the DOM tree. The traversal starts in sequential
|
|
//! mode, and optionally parallelizes as it discovers work.
|
|
|
|
#![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) {
|
|
// This should only be called in the main thread, or it may be racy
|
|
// to update the statistics in a global variable.
|
|
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)));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/// See documentation of the pref for performance characteristics.
|
|
fn work_unit_max() -> usize {
|
|
static_prefs::pref!("layout.css.stylo-work-unit-size") as usize
|
|
}
|
|
|
|
/// Do a DOM traversal for top-down and (optionally) bottom-up processing, generic over `D`.
|
|
///
|
|
/// We use an adaptive traversal strategy. We start out with simple sequential processing, until we
|
|
/// arrive at a wide enough level in the DOM that the parallel traversal would parallelize it.
|
|
/// If a thread pool is provided, we then transfer control over to the parallel traversal.
|
|
///
|
|
/// Returns true if the traversal was parallel, and also returns the statistics object containing
|
|
/// information on nodes traversed (on nightly only). Not all of its fields will be initialized
|
|
/// since we don't call finish().
|
|
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
|
|
};
|
|
|
|
// Declare the main-thread context, as well as the worker-thread contexts,
|
|
// which we may or may not instantiate. It's important to declare the worker-
|
|
// thread contexts first, so that they get dropped second. This matters because:
|
|
// * ThreadLocalContexts borrow AtomicRefCells in TLS.
|
|
// * Dropping a ThreadLocalContext can run SequentialTasks.
|
|
// * Sequential tasks may call into functions like
|
|
// Servo_StyleSet_GetBaseComputedValuesForElement, which instantiate a
|
|
// ThreadLocalStyleContext on the main thread. If the main thread
|
|
// ThreadLocalStyleContext has not released its TLS borrow by that point,
|
|
// we'll panic on double-borrow.
|
|
let mut scoped_tls = ScopedTLS::<ThreadLocalStyleContext<E>>::new(pool);
|
|
// Process the nodes breadth-first. This helps keep similar traversal characteristics for the
|
|
// style sharing cache.
|
|
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,
|
|
);
|
|
});
|
|
|
|
// Collect statistics from thread-locals if requested.
|
|
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);
|
|
}
|
|
// dump statistics to stdout if requested
|
|
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
|
|
}
|