Skip to main content

style/
driver.rs

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