tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

driver.rs (6516B)


      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 
     10 use crate::context::{PerThreadTraversalStatistics, StyleContext};
     11 use crate::context::{ThreadLocalStyleContext, TraversalStatistics};
     12 use crate::dom::{SendNode, TElement, TNode};
     13 use crate::parallel;
     14 use crate::scoped_tls::ScopedTLS;
     15 use crate::traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken};
     16 use std::collections::VecDeque;
     17 use std::time::Instant;
     18 
     19 #[cfg(feature = "servo")]
     20 fn should_report_statistics() -> bool {
     21    false
     22 }
     23 
     24 #[cfg(feature = "gecko")]
     25 fn should_report_statistics() -> bool {
     26    unsafe { crate::gecko_bindings::structs::ServoTraversalStatistics_sActive }
     27 }
     28 
     29 #[cfg(feature = "servo")]
     30 fn report_statistics(_stats: &PerThreadTraversalStatistics) {
     31    unreachable!("Servo never report stats");
     32 }
     33 
     34 #[cfg(feature = "gecko")]
     35 fn 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 
     51 fn 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.
     77 fn 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().
     90 pub fn traverse_dom<E, D>(
     91    traversal: &D,
     92    token: PreTraverseToken<E>,
     93    pool: Option<&rayon::ThreadPool>,
     94 ) -> E
     95 where
     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 }