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_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    // 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 = 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
76/// See documentation of the pref for performance characteristics.
77fn work_unit_max() -> usize {
78    static_prefs::pref!("layout.css.stylo-work-unit-size") as usize
79}
80
81/// Do a DOM traversal for top-down and (optionally) bottom-up processing, generic over `D`.
82///
83/// We use an adaptive traversal strategy. We start out with simple sequential processing, until we
84/// arrive at a wide enough level in the DOM that the parallel traversal would parallelize it.
85/// If a thread pool is provided, we then transfer control over to the parallel traversal.
86///
87/// Returns true if the traversal was parallel, and also returns the statistics object containing
88/// information on nodes traversed (on nightly only). Not all of its fields will be initialized
89/// since we don't call finish().
90pub 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    // Declare the main-thread context, as well as the worker-thread contexts,
112    // which we may or may not instantiate. It's important to declare the worker-
113    // thread contexts first, so that they get dropped second. This matters because:
114    //   * ThreadLocalContexts borrow AtomicRefCells in TLS.
115    //   * Dropping a ThreadLocalContext can run SequentialTasks.
116    //   * Sequential tasks may call into functions like
117    //     Servo_StyleSet_GetBaseComputedValuesForElement, which instantiate a
118    //     ThreadLocalStyleContext on the main thread. If the main thread
119    //     ThreadLocalStyleContext has not released its TLS borrow by that point,
120    //     we'll panic on double-borrow.
121    let mut scoped_tls = ScopedTLS::<ThreadLocalStyleContext<E>>::new(pool);
122    // Process the nodes breadth-first. This helps keep similar traversal characteristics for the
123    // style sharing cache.
124    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    // Collect statistics from thread-locals if requested.
151    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        // dump statistics to stdout if requested
163        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}