/* 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 http://mozilla.org/MPL/2.0/. */ //! Traversing the DOM tree; the bloom filter. use atomic_refcell::{AtomicRefCell, AtomicRefMut}; use bloom::StyleBloom; use context::{LocalStyleContext, SharedStyleContext, StyleContext}; use data::{ElementData, StoredRestyleHint}; use dom::{OpaqueNode, TElement, TNode}; use matching::{MatchMethods, StyleSharingResult}; use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_SELF}; use selector_parser::RestyleDamage; use selectors::Element; use selectors::matching::StyleRelations; use std::borrow::Borrow; use std::cell::RefCell; use std::mem; use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering}; use stylist::Stylist; use util::opts; /// Every time we do another layout, the old bloom filters are invalid. This is /// detected by ticking a generation number every layout. pub type Generation = u32; /// Style sharing candidate cache stats. These are only used when /// `-Z style-sharing-stats` is given. pub static STYLE_SHARING_CACHE_HITS: AtomicUsize = ATOMIC_USIZE_INIT; pub static STYLE_SHARING_CACHE_MISSES: AtomicUsize = ATOMIC_USIZE_INIT; thread_local!( static STYLE_BLOOM: RefCell> = RefCell::new(None)); /// Returns the thread local bloom filter. /// /// If one does not exist, a new one will be made for you. If it is out of date, /// it will be cleared and reused. pub fn take_thread_local_bloom_filter(context: &SharedStyleContext) -> StyleBloom { trace!("{} taking bf", ::tid::tid()); STYLE_BLOOM.with(|style_bloom| { style_bloom.borrow_mut().take() .unwrap_or_else(|| StyleBloom::new(context.generation)) }) } pub fn put_thread_local_bloom_filter(bf: StyleBloom) { trace!("[{}] putting bloom filter back", ::tid::tid()); STYLE_BLOOM.with(move |style_bloom| { debug_assert!(style_bloom.borrow().is_none(), "Putting into a never-taken thread-local bloom filter"); *style_bloom.borrow_mut() = Some(bf); }) } /// Remove `element` from the bloom filter if it's the last element we inserted. /// /// Restores the bloom filter if this is not the root of the reflow. /// /// This is mostly useful for sequential traversal, where the element will /// always be the last one. pub fn remove_from_bloom_filter<'a, E, C>(context: &C, root: OpaqueNode, element: E) where E: TElement, C: StyleContext<'a> { trace!("[{}] remove_from_bloom_filter", ::tid::tid()); // We may have arrived to `reconstruct_flows` without entering in style // recalc at all due to our optimizations, nor that it's up to date, so we // can't ensure there's a bloom filter at all. let bf = STYLE_BLOOM.with(|style_bloom| { style_bloom.borrow_mut().take() }); if let Some(mut bf) = bf { if context.shared_context().generation == bf.generation() { bf.maybe_pop(element); // If we're the root of the reflow, just get rid of the bloom // filter. // // FIXME: We might want to just leave it in TLS? You don't do 4k // allocations every day. Also, this just clears one thread's bloom // filter, which is... not great? if element.as_node().opaque() != root { put_thread_local_bloom_filter(bf); } } } } // NB: Keep this as small as possible, please! #[derive(Clone, Debug)] pub struct PerLevelTraversalData { pub current_dom_depth: Option, } /// This structure exists to enforce that callers invoke pre_traverse, and also /// to pass information from the pre-traversal into the primary traversal. pub struct PreTraverseToken { traverse: bool, unstyled_children_only: bool, } impl PreTraverseToken { pub fn should_traverse(&self) -> bool { self.traverse } pub fn traverse_unstyled_children_only(&self) -> bool { self.unstyled_children_only } } pub trait DomTraversalContext { type SharedContext: Sync + 'static + Borrow; fn new<'a>(&'a Self::SharedContext, OpaqueNode) -> Self; /// Process `node` on the way down, before its children have been processed. fn process_preorder(&self, node: N, data: &mut PerLevelTraversalData); /// Process `node` on the way up, after its children have been processed. /// /// This is only executed if `needs_postorder_traversal` returns true. fn process_postorder(&self, node: N); /// Boolean that specifies whether a bottom up traversal should be /// performed. /// /// If it's false, then process_postorder has no effect at all. fn needs_postorder_traversal() -> bool { true } /// Must be invoked before traversing the root element to determine whether /// a traversal is needed. Returns a token that allows the caller to prove /// that the call happened. /// /// The unstyled_children_only parameter is used in Gecko to style newly- /// appended children without restyling the parent. fn pre_traverse(root: N::ConcreteElement, stylist: &Stylist, unstyled_children_only: bool) -> PreTraverseToken { if unstyled_children_only { return PreTraverseToken { traverse: true, unstyled_children_only: true, }; } // Expand the snapshot, if any. This is normally handled by the parent, so // we need a special case for the root. // // Expanding snapshots here may create a LATER_SIBLINGS restyle hint, which // we will drop on the floor. To prevent missed restyles, we assert against // restyling a root with later siblings. if let Some(mut data) = root.mutate_data() { if let Some(r) = data.as_restyle_mut() { debug_assert!(root.next_sibling_element().is_none()); let _later_siblings = r.expand_snapshot(root, stylist); } } PreTraverseToken { traverse: Self::node_needs_traversal(root.as_node()), unstyled_children_only: false, } } /// Returns true if traversal should visit a text node. The style system never /// processes text nodes, but Servo overrides this to visit them for flow /// construction when necessary. fn text_node_needs_traversal(node: N) -> bool { debug_assert!(node.is_text_node()); false } /// Returns true if traversal is needed for the given node and subtree. fn node_needs_traversal(node: N) -> bool { // Non-incremental layout visits every node. if cfg!(feature = "servo") && opts::get().nonincremental_layout { return true; } match node.as_element() { None => Self::text_node_needs_traversal(node), Some(el) => { // If the dirty descendants bit is set, we need to traverse no // matter what. Skip examining the ElementData. if el.has_dirty_descendants() { return true; } // Check the element data. If it doesn't exist, we need to visit // the element. let data = match el.borrow_data() { Some(d) => d, None => return true, }; // Check what kind of element data we have. If it's Initial or Persistent, // we're done. let restyle = match *data { ElementData::Initial(ref i) => return i.is_none(), ElementData::Persistent(_) => return false, ElementData::Restyle(ref r) => r, }; // Check whether we have any selector matching or re-cascading to // do in this subtree. debug_assert!(restyle.snapshot.is_none(), "Snapshots should already be expanded"); if !restyle.hint.is_empty() || restyle.recascade { return true; } // Servo uses the post-order traversal for flow construction, so // we need to traverse any element with damage so that we can perform // fixup / reconstruction on our way back up the tree. if cfg!(feature = "servo") && restyle.damage != RestyleDamage::empty() { return true; } false }, } } /// Helper for the traversal implementations to select the children that /// should be enqueued for processing. fn traverse_children(parent: N::ConcreteElement, mut f: F) { if parent.is_display_none() { return; } for kid in parent.as_node().children() { if Self::node_needs_traversal(kid) { let el = kid.as_element(); if el.as_ref().and_then(|el| el.borrow_data()) .map_or(false, |d| d.is_restyle()) { unsafe { parent.set_dirty_descendants(); } } f(kid); } } } /// Ensures the existence of the ElementData, and returns it. This can't live /// on TNode because of the trait-based separation between Servo's script /// and layout crates. /// /// This is only safe to call in top-down traversal before processing the /// children of |element|. unsafe fn ensure_element_data(element: &N::ConcreteElement) -> &AtomicRefCell; /// Clears the ElementData attached to this element, if any. /// /// This is only safe to call in top-down traversal before processing the /// children of |element|. unsafe fn clear_element_data(element: &N::ConcreteElement); fn local_context(&self) -> &LocalStyleContext; } /// Determines the amount of relations where we're going to share style. #[inline] pub fn relations_are_shareable(relations: &StyleRelations) -> bool { use selectors::matching::*; !relations.intersects(AFFECTED_BY_ID_SELECTOR | AFFECTED_BY_PSEUDO_ELEMENTS | AFFECTED_BY_STATE | AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR | AFFECTED_BY_STYLE_ATTRIBUTE | AFFECTED_BY_PRESENTATIONAL_HINTS) } /// Handles lazy resolution of style in display:none subtrees. See the comment /// at the callsite in query.rs. pub fn style_element_in_display_none_subtree<'a, E, C, F>(element: E, init_data: &F, context: &'a C) -> E where E: TElement, C: StyleContext<'a>, F: Fn(E), { // Check the base case. if element.get_data().is_some() { debug_assert!(element.is_display_none()); return element; } // Ensure the parent is styled. let parent = element.parent_element().unwrap(); let display_none_root = style_element_in_display_none_subtree(parent, init_data, context); // Initialize our data. init_data(element); // Resolve our style. let mut data = element.mutate_data().unwrap(); let match_results = element.match_element(context, None); unsafe { let shareable = match_results.primary_is_shareable(); element.cascade_node(context, &mut data, Some(parent), match_results.primary, match_results.per_pseudo, shareable); } display_none_root } /// Calculates the style for a single node. #[inline] #[allow(unsafe_code)] pub fn recalc_style_at<'a, E, C, D>(context: &'a C, traversal_data: &mut PerLevelTraversalData, element: E, mut data: &mut AtomicRefMut) where E: TElement, C: StyleContext<'a>, D: DomTraversalContext { debug_assert!(data.as_restyle().map_or(true, |r| r.snapshot.is_none()), "Snapshots should be expanded by the caller"); let compute_self = !data.has_current_styles(); let mut inherited_style_changed = false; debug!("recalc_style_at: {:?} (compute_self={:?}, dirty_descendants={:?}, data={:?})", element, compute_self, element.has_dirty_descendants(), data); // Compute style for this element if necessary. if compute_self { inherited_style_changed = compute_style::<_, _, D>(context, &mut data, traversal_data, element); } // Now that matching and cascading is done, clear the bits corresponding to // those operations and compute the propagated restyle hint. let empty_hint = StoredRestyleHint::empty(); let propagated_hint = match data.as_restyle_mut() { None => empty_hint, Some(r) => { r.recascade = false; mem::replace(&mut r.hint, empty_hint).propagate() }, }; debug_assert!(data.has_current_styles()); trace!("propagated_hint={:?}, inherited_style_changed={:?}", propagated_hint, inherited_style_changed); // Preprocess children, propagating restyle hints and handling sibling relationships. if !data.styles().is_display_none() && (element.has_dirty_descendants() || !propagated_hint.is_empty() || inherited_style_changed) { preprocess_children::<_, _, D>(context, element, propagated_hint, inherited_style_changed); } } // Computes style, returning true if the inherited styles changed for this // element. // // FIXME(bholley): This should differentiate between matching and cascading, // since we have separate bits for each now. fn compute_style<'a, E, C, D>(context: &'a C, mut data: &mut AtomicRefMut, traversal_data: &mut PerLevelTraversalData, element: E) -> bool where E: TElement, C: StyleContext<'a>, D: DomTraversalContext, { let shared_context = context.shared_context(); let mut bf = take_thread_local_bloom_filter(shared_context); // Ensure the bloom filter is up to date. let dom_depth = bf.insert_parents_recovering(element, traversal_data.current_dom_depth, shared_context.generation); // Update the dom depth with the up-to-date dom depth. // // Note that this is always the same than the pre-existing depth, but it can // change from unknown to known at this step. traversal_data.current_dom_depth = Some(dom_depth); bf.assert_complete(element); // Check to see whether we can share a style with someone. let style_sharing_candidate_cache = &mut context.local_context().style_sharing_candidate_cache.borrow_mut(); let sharing_result = if element.parent_element().is_none() { StyleSharingResult::CannotShare } else { unsafe { element.share_style_if_possible(style_sharing_candidate_cache, shared_context, &mut data) } }; // Otherwise, match and cascade selectors. match sharing_result { StyleSharingResult::CannotShare => { let match_results; let shareable_element = { if opts::get().style_sharing_stats { STYLE_SHARING_CACHE_MISSES.fetch_add(1, Ordering::Relaxed); } // Perform the CSS selector matching. match_results = element.match_element(context, Some(bf.filter())); if match_results.primary_is_shareable() { Some(element) } else { None } }; let relations = match_results.relations; // Perform the CSS cascade. unsafe { let shareable = match_results.primary_is_shareable(); element.cascade_node(context, &mut data, element.parent_element(), match_results.primary, match_results.per_pseudo, shareable); } // Add ourselves to the LRU cache. if let Some(element) = shareable_element { style_sharing_candidate_cache.insert_if_possible(&element, &data.styles().primary.values, relations); } } StyleSharingResult::StyleWasShared(index) => { if opts::get().style_sharing_stats { STYLE_SHARING_CACHE_HITS.fetch_add(1, Ordering::Relaxed); } style_sharing_candidate_cache.touch(index); } } // If we're restyling this element to display:none, throw away all style data // in the subtree, notify the caller to early-return. let display_none = data.styles().is_display_none(); if display_none { debug!("New element style is display:none - clearing data from descendants."); clear_descendant_data(element, &|e| unsafe { D::clear_element_data(&e) }); } // TODO(emilio): It's pointless to insert the element in the parallel // traversal, but it may be worth todo it for sequential restyling. What we // do now is trying to recover it which in that case is really cheap, so // we'd save a few instructions, but probably not worth given the added // complexity. put_thread_local_bloom_filter(bf); // FIXME(bholley): Compute this accurately from the call to CalcStyleDifference. let inherited_styles_changed = true; inherited_styles_changed } fn preprocess_children<'a, E, C, D>(context: &'a C, element: E, mut propagated_hint: StoredRestyleHint, parent_inherited_style_changed: bool) where E: TElement, C: StyleContext<'a>, D: DomTraversalContext { // Loop over all the children. for child in element.as_node().children() { // FIXME(bholley): Add TElement::element_children instead of this. let child = match child.as_element() { Some(el) => el, None => continue, }; let mut child_data = unsafe { D::ensure_element_data(&child).borrow_mut() }; if child_data.is_unstyled_initial() { continue; } let mut restyle_data = match child_data.restyle() { Some(d) => d, None => continue, }; // Propagate the parent and sibling restyle hint. if !propagated_hint.is_empty() { restyle_data.hint.insert(&propagated_hint); } // Handle element snapshots. let stylist = &context.shared_context().stylist; let later_siblings = restyle_data.expand_snapshot(child, stylist); if later_siblings { propagated_hint.insert(&(RESTYLE_SELF | RESTYLE_DESCENDANTS).into()); } // If properties that we inherited from the parent changed, we need to recascade. // // FIXME(bholley): Need to handle explicitly-inherited reset properties somewhere. if parent_inherited_style_changed { restyle_data.recascade = true; } } } pub fn clear_descendant_data(el: E, clear_data: &F) { for kid in el.as_node().children() { if let Some(kid) = kid.as_element() { // We maintain an invariant that, if an element has data, all its ancestors // have data as well. By consequence, any element without data has no // descendants with data. if kid.get_data().is_some() { clear_data(kid); clear_descendant_data(kid, clear_data); } } } unsafe { el.unset_dirty_descendants(); } }