From f9a02f0aba352736c372201a11d4c19ecc99dcf4 Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Wed, 6 Jan 2016 18:36:14 -0800 Subject: [PATCH] Hoist the style parts of traversal.rs into style/. --- components/layout/parallel.rs | 5 +- components/layout/sequential.rs | 5 +- components/layout/traversal.rs | 254 +------------------------------ components/style/lib.rs | 1 + components/style/traversal.rs | 260 ++++++++++++++++++++++++++++++++ 5 files changed, 269 insertions(+), 256 deletions(-) create mode 100644 components/style/traversal.rs diff --git a/components/layout/parallel.rs b/components/layout/parallel.rs index 0aac7dc822e..3e63f4ae8bf 100644 --- a/components/layout/parallel.rs +++ b/components/layout/parallel.rs @@ -16,10 +16,9 @@ use profile_traits::time::{self, TimerMetadata, profile}; use std::mem; use std::sync::atomic::{AtomicIsize, Ordering}; use style::dom::{TNode, UnsafeNode}; -use traversal::PostorderNodeMutTraversal; +use style::traversal::DomTraversalContext; use traversal::{AssignBSizesAndStoreOverflow, AssignISizes, BubbleISizes}; -use traversal::{BuildDisplayList, ComputeAbsolutePositions}; -use traversal::DomTraversalContext; +use traversal::{BuildDisplayList, ComputeAbsolutePositions, PostorderNodeMutTraversal}; use util::opts; use util::workqueue::{WorkQueue, WorkUnit, WorkerProxy}; diff --git a/components/layout/sequential.rs b/components/layout/sequential.rs index bafd47872d4..98b8da8a9a9 100644 --- a/components/layout/sequential.rs +++ b/components/layout/sequential.rs @@ -12,10 +12,9 @@ use flow::{self, Flow, ImmutableFlowUtils, InorderFlowTraversal, MutableFlowUtil use flow_ref::{self, FlowRef}; use fragment::FragmentBorderBoxIterator; use generated_content::ResolveGeneratedContent; -use traversal::PostorderNodeMutTraversal; +use style::traversal::DomTraversalContext; use traversal::{AssignBSizesAndStoreOverflow, AssignISizes}; -use traversal::{BubbleISizes, BuildDisplayList, ComputeAbsolutePositions}; -use traversal::DomTraversalContext; +use traversal::{BubbleISizes, BuildDisplayList, ComputeAbsolutePositions, PostorderNodeMutTraversal}; use util::opts; use wrapper::LayoutNode; diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs index 05d4e6171b0..ac13a014c12 100644 --- a/components/layout/traversal.rs +++ b/components/layout/traversal.rs @@ -13,165 +13,15 @@ use flow::{self, Flow}; use gfx::display_list::OpaqueNode; use incremental::{BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW, REPAINT, RestyleDamage}; use script::layout_interface::ReflowGoal; -use selectors::bloom::BloomFilter; -use std::cell::RefCell; use std::mem; -use std::rc::Rc; -use style::context::{LocalStyleContext, SharedStyleContext, StyleContext}; -use style::dom::{TNode, TRestyleDamage, UnsafeNode}; -use style::matching::{ApplicableDeclarations, ElementMatchMethods, MatchMethods, StyleSharingResult}; +use style::context::StyleContext; +use style::matching::MatchMethods; +use style::traversal::{DomTraversalContext, STYLE_BLOOM}; +use style::traversal::{put_task_local_bloom_filter, recalc_style_at}; use util::opts; use util::tid::tid; use wrapper::{LayoutNode, ThreadSafeLayoutNode}; -/// Every time we do another layout, the old bloom filters are invalid. This is -/// detected by ticking a generation number every layout. -type Generation = u32; - -/// A pair of the bloom filter used for css selector matching, and the node to -/// which it applies. This is used to efficiently do `Descendant` selector -/// matches. Thanks to the bloom filter, we can avoid walking up the tree -/// looking for ancestors that aren't there in the majority of cases. -/// -/// As we walk down the DOM tree a task-local bloom filter is built of all the -/// CSS `SimpleSelector`s which are part of a `Descendant` compound selector -/// (i.e. paired with a `Descendant` combinator, in the `next` field of a -/// `CompoundSelector`. -/// -/// Before a `Descendant` selector match is tried, it's compared against the -/// bloom filter. If the bloom filter can exclude it, the selector is quickly -/// rejected. -/// -/// When done styling a node, all selectors previously inserted into the filter -/// are removed. -/// -/// Since a work-stealing queue is used for styling, sometimes, the bloom filter -/// will no longer be the for the parent of the node we're currently on. When -/// this happens, the task local bloom filter will be thrown away and rebuilt. -thread_local!( - static STYLE_BLOOM: RefCell, UnsafeNode, Generation)>> = RefCell::new(None)); - -/// Returns the task 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. -fn take_task_local_bloom_filter<'ln, N>(parent_node: Option, - root: OpaqueNode, - context: &SharedStyleContext) - -> Box - where N: TNode<'ln> { - STYLE_BLOOM.with(|style_bloom| { - match (parent_node, style_bloom.borrow_mut().take()) { - // Root node. Needs new bloom filter. - (None, _ ) => { - debug!("[{}] No parent, but new bloom filter!", tid()); - box BloomFilter::new() - } - // No bloom filter for this thread yet. - (Some(parent), None) => { - let mut bloom_filter = box BloomFilter::new(); - insert_ancestors_into_bloom_filter(&mut bloom_filter, parent, root); - bloom_filter - } - // Found cached bloom filter. - (Some(parent), Some((mut bloom_filter, old_node, old_generation))) => { - if old_node == parent.to_unsafe() && - old_generation == context.generation { - // Hey, the cached parent is our parent! We can reuse the bloom filter. - debug!("[{}] Parent matches (={}). Reusing bloom filter.", tid(), old_node.0); - } else { - // Oh no. the cached parent is stale. I guess we need a new one. Reuse the existing - // allocation to avoid malloc churn. - bloom_filter.clear(); - insert_ancestors_into_bloom_filter(&mut bloom_filter, parent, root); - } - bloom_filter - }, - } - }) -} - -fn put_task_local_bloom_filter(bf: Box, - unsafe_node: &UnsafeNode, - context: &SharedStyleContext) { - STYLE_BLOOM.with(move |style_bloom| { - assert!(style_bloom.borrow().is_none(), - "Putting into a never-taken task-local bloom filter"); - *style_bloom.borrow_mut() = Some((bf, *unsafe_node, context.generation)); - }) -} - -/// "Ancestors" in this context is inclusive of ourselves. -fn insert_ancestors_into_bloom_filter<'ln, N>(bf: &mut Box, - mut n: N, - root: OpaqueNode) - where N: TNode<'ln> { - debug!("[{}] Inserting ancestors.", tid()); - let mut ancestors = 0; - loop { - ancestors += 1; - - n.insert_into_bloom_filter(&mut **bf); - n = match n.layout_parent_node(root) { - None => break, - Some(p) => p, - }; - } - debug!("[{}] Inserted {} ancestors.", tid(), ancestors); -} - -pub trait DomTraversalContext<'ln, N: TNode<'ln>> { - type SharedContext: Sync + 'static; - fn new<'a>(&'a Self::SharedContext, OpaqueNode) -> Self; - fn process_preorder(&self, node: N); - fn process_postorder(&self, node: N); -} - -/// FIXME(bholley): I added this now to demonstrate the usefulness of the new design. -/// This is currently unused, but will be used shortly. - -#[allow(dead_code)] -pub struct StandaloneStyleContext<'a> { - pub shared: &'a SharedStyleContext, - cached_local_style_context: Rc, -} - -impl<'a> StandaloneStyleContext<'a> { - pub fn new(_: &'a SharedStyleContext) -> Self { panic!("Not implemented") } -} - -impl<'a> StyleContext<'a> for StandaloneStyleContext<'a> { - fn shared_context(&self) -> &'a SharedStyleContext { - &self.shared - } - - fn local_context(&self) -> &LocalStyleContext { - &self.cached_local_style_context - } -} - -#[allow(dead_code)] -pub struct RecalcStyleOnly<'lc> { - context: StandaloneStyleContext<'lc>, - root: OpaqueNode, -} - -impl<'lc, 'ln, N: TNode<'ln>> DomTraversalContext<'ln, N> for RecalcStyleOnly<'lc> { - type SharedContext = SharedStyleContext; - fn new<'a>(shared: &'a Self::SharedContext, root: OpaqueNode) -> Self { - // See the comment in RecalcStyleAndConstructFlows::new for an explanation of why this is - // necessary. - let shared_lc: &'lc SharedStyleContext = unsafe { mem::transmute(shared) }; - RecalcStyleOnly { - context: StandaloneStyleContext::new(shared_lc), - root: root, - } - } - - fn process_preorder(&self, node: N) { recalc_style_at(&self.context, self.root, node); } - fn process_postorder(&self, _: N) {} -} - pub struct RecalcStyleAndConstructFlows<'lc> { context: LayoutContext<'lc>, root: OpaqueNode, @@ -220,102 +70,6 @@ pub trait PostorderNodeMutTraversal<'ln, ConcreteThreadSafeLayoutNode: ThreadSaf fn process(&mut self, node: &ConcreteThreadSafeLayoutNode) -> bool; } -/// The recalc-style-for-node traversal, which styles each node and must run before -/// layout computation. This computes the styles applied to each node. -#[inline] -#[allow(unsafe_code)] -fn recalc_style_at<'a, 'ln, N: TNode<'ln>, C: StyleContext<'a>> (context: &'a C, root: OpaqueNode, node: N) { - // Initialize layout data. - // - // FIXME(pcwalton): Stop allocating here. Ideally this should just be done by the HTML - // parser. - node.initialize_data(); - - // Get the parent node. - let parent_opt = node.layout_parent_node(root); - - // Get the style bloom filter. - let mut bf = take_task_local_bloom_filter(parent_opt, root, context.shared_context()); - - let nonincremental_layout = opts::get().nonincremental_layout; - if nonincremental_layout || node.is_dirty() { - // Remove existing CSS styles from nodes whose content has changed (e.g. text changed), - // to force non-incremental reflow. - if node.has_changed() { - node.unstyle(); - } - - // 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 = match node.as_element() { - Some(element) => { - unsafe { - element.share_style_if_possible(style_sharing_candidate_cache, - parent_opt.clone()) - } - }, - None => StyleSharingResult::CannotShare, - }; - - // Otherwise, match and cascade selectors. - match sharing_result { - StyleSharingResult::CannotShare => { - let mut applicable_declarations = ApplicableDeclarations::new(); - - let shareable_element = match node.as_element() { - Some(element) => { - // Perform the CSS selector matching. - let stylist = unsafe { &*context.shared_context().stylist.0 }; - if element.match_element(stylist, - Some(&*bf), - &mut applicable_declarations) { - Some(element) - } else { - None - } - }, - None => { - if node.has_changed() { - node.set_restyle_damage(N::ConcreteRestyleDamage::rebuild_and_reflow()) - } - None - }, - }; - - // Perform the CSS cascade. - unsafe { - node.cascade_node(&context.shared_context(), - parent_opt, - &applicable_declarations, - &mut context.local_context().applicable_declarations_cache.borrow_mut(), - &context.shared_context().new_animations_sender); - } - - // Add ourselves to the LRU cache. - if let Some(element) = shareable_element { - style_sharing_candidate_cache.insert_if_possible(&element); - } - } - StyleSharingResult::StyleWasShared(index, damage) => { - style_sharing_candidate_cache.touch(index); - node.set_restyle_damage(damage); - } - } - } - - let unsafe_layout_node = node.to_unsafe(); - - // Before running the children, we need to insert our nodes into the bloom - // filter. - debug!("[{}] + {:X}", tid(), unsafe_layout_node.0); - node.insert_into_bloom_filter(&mut *bf); - - // NB: flow construction updates the bloom filter on the way up. - put_task_local_bloom_filter(bf, &unsafe_layout_node, context.shared_context()); -} - /// The flow construction traversal, which builds flows for styled nodes. #[inline] #[allow(unsafe_code)] diff --git a/components/style/lib.rs b/components/style/lib.rs index e010a92f0b2..18c43638cfb 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -57,6 +57,7 @@ pub mod parser; pub mod restyle_hints; pub mod selector_matching; pub mod stylesheets; +pub mod traversal; #[macro_use] #[allow(non_camel_case_types)] pub mod values; diff --git a/components/style/traversal.rs b/components/style/traversal.rs new file mode 100644 index 00000000000..48bb8ce047b --- /dev/null +++ b/components/style/traversal.rs @@ -0,0 +1,260 @@ +/* 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/. */ + +#![allow(unsafe_code)] + +use context::{LocalStyleContext, SharedStyleContext, StyleContext}; +use dom::{OpaqueNode, TNode, TRestyleDamage, UnsafeNode}; +use matching::{ApplicableDeclarations, ElementMatchMethods, MatchMethods, StyleSharingResult}; +use selectors::bloom::BloomFilter; +use std::cell::RefCell; +use std::mem; +use std::rc::Rc; +use util::opts; +use util::tid::tid; + +/// 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; + +/// A pair of the bloom filter used for css selector matching, and the node to +/// which it applies. This is used to efficiently do `Descendant` selector +/// matches. Thanks to the bloom filter, we can avoid walking up the tree +/// looking for ancestors that aren't there in the majority of cases. +/// +/// As we walk down the DOM tree a task-local bloom filter is built of all the +/// CSS `SimpleSelector`s which are part of a `Descendant` compound selector +/// (i.e. paired with a `Descendant` combinator, in the `next` field of a +/// `CompoundSelector`. +/// +/// Before a `Descendant` selector match is tried, it's compared against the +/// bloom filter. If the bloom filter can exclude it, the selector is quickly +/// rejected. +/// +/// When done styling a node, all selectors previously inserted into the filter +/// are removed. +/// +/// Since a work-stealing queue is used for styling, sometimes, the bloom filter +/// will no longer be the for the parent of the node we're currently on. When +/// this happens, the task local bloom filter will be thrown away and rebuilt. +thread_local!( + pub static STYLE_BLOOM: RefCell, UnsafeNode, Generation)>> = RefCell::new(None)); + +/// Returns the task 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. +fn take_task_local_bloom_filter<'ln, N>(parent_node: Option, + root: OpaqueNode, + context: &SharedStyleContext) + -> Box + where N: TNode<'ln> { + STYLE_BLOOM.with(|style_bloom| { + match (parent_node, style_bloom.borrow_mut().take()) { + // Root node. Needs new bloom filter. + (None, _ ) => { + debug!("[{}] No parent, but new bloom filter!", tid()); + box BloomFilter::new() + } + // No bloom filter for this thread yet. + (Some(parent), None) => { + let mut bloom_filter = box BloomFilter::new(); + insert_ancestors_into_bloom_filter(&mut bloom_filter, parent, root); + bloom_filter + } + // Found cached bloom filter. + (Some(parent), Some((mut bloom_filter, old_node, old_generation))) => { + if old_node == parent.to_unsafe() && + old_generation == context.generation { + // Hey, the cached parent is our parent! We can reuse the bloom filter. + debug!("[{}] Parent matches (={}). Reusing bloom filter.", tid(), old_node.0); + } else { + // Oh no. the cached parent is stale. I guess we need a new one. Reuse the existing + // allocation to avoid malloc churn. + bloom_filter.clear(); + insert_ancestors_into_bloom_filter(&mut bloom_filter, parent, root); + } + bloom_filter + }, + } + }) +} + +pub fn put_task_local_bloom_filter(bf: Box, + unsafe_node: &UnsafeNode, + context: &SharedStyleContext) { + STYLE_BLOOM.with(move |style_bloom| { + assert!(style_bloom.borrow().is_none(), + "Putting into a never-taken task-local bloom filter"); + *style_bloom.borrow_mut() = Some((bf, *unsafe_node, context.generation)); + }) +} + +/// "Ancestors" in this context is inclusive of ourselves. +fn insert_ancestors_into_bloom_filter<'ln, N>(bf: &mut Box, + mut n: N, + root: OpaqueNode) + where N: TNode<'ln> { + debug!("[{}] Inserting ancestors.", tid()); + let mut ancestors = 0; + loop { + ancestors += 1; + + n.insert_into_bloom_filter(&mut **bf); + n = match n.layout_parent_node(root) { + None => break, + Some(p) => p, + }; + } + debug!("[{}] Inserted {} ancestors.", tid(), ancestors); +} + +pub trait DomTraversalContext<'ln, N: TNode<'ln>> { + type SharedContext: Sync + 'static; + fn new<'a>(&'a Self::SharedContext, OpaqueNode) -> Self; + fn process_preorder(&self, node: N); + fn process_postorder(&self, node: N); +} + +/// FIXME(bholley): I added this now to demonstrate the usefulness of the new design. +/// This is currently unused, but will be used shortly. + +#[allow(dead_code)] +pub struct StandaloneStyleContext<'a> { + pub shared: &'a SharedStyleContext, + cached_local_style_context: Rc, +} + +impl<'a> StandaloneStyleContext<'a> { + pub fn new(_: &'a SharedStyleContext) -> Self { panic!("Not implemented") } +} + +impl<'a> StyleContext<'a> for StandaloneStyleContext<'a> { + fn shared_context(&self) -> &'a SharedStyleContext { + &self.shared + } + + fn local_context(&self) -> &LocalStyleContext { + &self.cached_local_style_context + } +} + +#[allow(dead_code)] +pub struct RecalcStyleOnly<'lc> { + context: StandaloneStyleContext<'lc>, + root: OpaqueNode, +} + +impl<'lc, 'ln, N: TNode<'ln>> DomTraversalContext<'ln, N> for RecalcStyleOnly<'lc> { + type SharedContext = SharedStyleContext; + fn new<'a>(shared: &'a Self::SharedContext, root: OpaqueNode) -> Self { + // See the comment in RecalcStyleAndConstructFlows::new for an explanation of why this is + // necessary. + let shared_lc: &'lc SharedStyleContext = unsafe { mem::transmute(shared) }; + RecalcStyleOnly { + context: StandaloneStyleContext::new(shared_lc), + root: root, + } + } + + fn process_preorder(&self, node: N) { recalc_style_at(&self.context, self.root, node); } + fn process_postorder(&self, _: N) {} +} + +/// The recalc-style-for-node traversal, which styles each node and must run before +/// layout computation. This computes the styles applied to each node. +#[inline] +#[allow(unsafe_code)] +pub fn recalc_style_at<'a, 'ln, N: TNode<'ln>, C: StyleContext<'a>> (context: &'a C, root: OpaqueNode, node: N) { + // Initialize layout data. + // + // FIXME(pcwalton): Stop allocating here. Ideally this should just be done by the HTML + // parser. + node.initialize_data(); + + // Get the parent node. + let parent_opt = node.layout_parent_node(root); + + // Get the style bloom filter. + let mut bf = take_task_local_bloom_filter(parent_opt, root, context.shared_context()); + + let nonincremental_layout = opts::get().nonincremental_layout; + if nonincremental_layout || node.is_dirty() { + // Remove existing CSS styles from nodes whose content has changed (e.g. text changed), + // to force non-incremental reflow. + if node.has_changed() { + node.unstyle(); + } + + // 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 = match node.as_element() { + Some(element) => { + unsafe { + element.share_style_if_possible(style_sharing_candidate_cache, + parent_opt.clone()) + } + }, + None => StyleSharingResult::CannotShare, + }; + + // Otherwise, match and cascade selectors. + match sharing_result { + StyleSharingResult::CannotShare => { + let mut applicable_declarations = ApplicableDeclarations::new(); + + let shareable_element = match node.as_element() { + Some(element) => { + // Perform the CSS selector matching. + let stylist = unsafe { &*context.shared_context().stylist.0 }; + if element.match_element(stylist, + Some(&*bf), + &mut applicable_declarations) { + Some(element) + } else { + None + } + }, + None => { + if node.has_changed() { + node.set_restyle_damage(N::ConcreteRestyleDamage::rebuild_and_reflow()) + } + None + }, + }; + + // Perform the CSS cascade. + unsafe { + node.cascade_node(&context.shared_context(), + parent_opt, + &applicable_declarations, + &mut context.local_context().applicable_declarations_cache.borrow_mut(), + &context.shared_context().new_animations_sender); + } + + // Add ourselves to the LRU cache. + if let Some(element) = shareable_element { + style_sharing_candidate_cache.insert_if_possible(&element); + } + } + StyleSharingResult::StyleWasShared(index, damage) => { + style_sharing_candidate_cache.touch(index); + node.set_restyle_damage(damage); + } + } + } + + let unsafe_layout_node = node.to_unsafe(); + + // Before running the children, we need to insert our nodes into the bloom + // filter. + debug!("[{}] + {:X}", tid(), unsafe_layout_node.0); + node.insert_into_bloom_filter(&mut *bf); + + // NB: flow construction updates the bloom filter on the way up. + put_task_local_bloom_filter(bf, &unsafe_layout_node, context.shared_context()); +} +