From 947134949adc908d6a0414f423914025355ad931 Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Wed, 30 Dec 2015 17:02:38 -0800 Subject: [PATCH 1/5] Refactor parallel dom traversal to be agnostic to the processing steps themselves. --- components/layout/layout_task.rs | 7 +- components/layout/parallel.rs | 231 +++++++++------------- components/layout/sequential.rs | 32 ++-- components/layout/traversal.rs | 319 +++++++++++++++---------------- 4 files changed, 262 insertions(+), 327 deletions(-) diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index 88425fde26b..0015cd3fc33 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -69,6 +69,7 @@ use style::dom::{TDocument, TElement, TNode}; use style::media_queries::{Device, MediaType}; use style::selector_matching::{Stylist, USER_OR_USER_AGENT_STYLESHEETS}; use style::stylesheets::{CSSRuleIteratorExt, Stylesheet}; +use traversal::RecalcStyleAndConstructFlows; use url::Url; use util::geometry::MAX_RECT; use util::ipc::OptionalIpcSender; @@ -1024,10 +1025,12 @@ impl LayoutTask { // Perform CSS selector matching and flow construction. match self.parallel_traversal { None => { - sequential::traverse_dom_preorder(node, &shared_layout_context); + sequential::traverse_dom_preorder::( + node, &shared_layout_context); } Some(ref mut traversal) => { - parallel::traverse_dom_preorder(node, &shared_layout_context, traversal); + parallel::traverse_dom_preorder::( + node, &shared_layout_context, traversal); } } }); diff --git a/components/layout/parallel.rs b/components/layout/parallel.rs index 35c9b7f7955..fa52a7efdb2 100644 --- a/components/layout/parallel.rs +++ b/components/layout/parallel.rs @@ -19,8 +19,7 @@ use style::dom::UnsafeNode; use traversal::PostorderNodeMutTraversal; use traversal::{AssignBSizesAndStoreOverflow, AssignISizes, BubbleISizes}; use traversal::{BuildDisplayList, ComputeAbsolutePositions}; -use traversal::{ConstructFlows, RecalcStyleForNode}; -use traversal::{PostorderDomTraversal, PreorderDomTraversal}; +use traversal::{DomTraversal, DomTraversalContext}; use util::opts; use util::workqueue::{WorkQueue, WorkUnit, WorkerProxy}; use wrapper::LayoutNode; @@ -73,105 +72,6 @@ pub type ChunkedFlowTraversalFunction = pub type FlowTraversalFunction = extern "Rust" fn(UnsafeFlow, &SharedLayoutContext); -/// A parallel top-down DOM traversal. -pub trait ParallelPreorderDomTraversal<'ln, ConcreteLayoutNode> - : PreorderDomTraversal<'ln, ConcreteLayoutNode> - where ConcreteLayoutNode: LayoutNode<'ln> { - fn run_parallel(&self, - nodes: UnsafeNodeList, - proxy: &mut WorkerProxy); - - #[inline(always)] - fn run_parallel_helper( - &self, - unsafe_nodes: UnsafeNodeList, - proxy: &mut WorkerProxy, - top_down_func: ChunkedDomTraversalFunction, - bottom_up_func: DomTraversalFunction) { - let mut discovered_child_nodes = Vec::new(); - for unsafe_node in *unsafe_nodes.0 { - // Get a real layout node. - let node = unsafe { ConcreteLayoutNode::from_unsafe(&unsafe_node) }; - - // Perform the appropriate traversal. - self.process(node); - - let child_count = node.children_count(); - - // Reset the count of children. - { - let data = node.mutate_data().unwrap(); - data.parallel.children_count.store(child_count as isize, - Ordering::Relaxed); - } - - // Possibly enqueue the children. - if child_count != 0 { - for kid in node.children() { - discovered_child_nodes.push(kid.to_unsafe()) - } - } else { - // If there were no more children, start walking back up. - bottom_up_func(unsafe_nodes.1, unsafe_node, proxy) - } - } - - for chunk in discovered_child_nodes.chunks(CHUNK_SIZE) { - proxy.push(WorkUnit { - fun: top_down_func, - data: (box chunk.iter().cloned().collect(), unsafe_nodes.1), - }); - } - } -} - -/// A parallel bottom-up DOM traversal. -trait ParallelPostorderDomTraversal<'ln, ConcreteLayoutNode> - : PostorderDomTraversal<'ln, ConcreteLayoutNode> - where ConcreteLayoutNode: LayoutNode<'ln> { - fn root(&self) -> OpaqueNode; - - /// Process current node and potentially traverse its ancestors. - /// - /// If we are the last child that finished processing, recursively process - /// our parent. Else, stop. Also, stop at the root. - /// - /// Thus, if we start with all the leaves of a tree, we end up traversing - /// the whole tree bottom-up because each parent will be processed exactly - /// once (by the last child that finishes processing). - /// - /// The only communication between siblings is that they both - /// fetch-and-subtract the parent's children count. - fn run_parallel(&self, unsafe_node: UnsafeNode) { - // Get a real layout node. - let mut node = unsafe { ConcreteLayoutNode::from_unsafe(&unsafe_node) }; - loop { - // Perform the appropriate operation. - self.process(node); - - let parent = match node.layout_parent_node(self.root()) { - None => break, - Some(parent) => parent, - }; - - let parent_data = unsafe { - &*parent.borrow_data_unchecked().unwrap() - }; - - if parent_data - .parallel - .children_count - .fetch_sub(1, Ordering::Relaxed) != 1 { - // Get out of here and find another node to work on. - break - } - - // We were the last child of our parent. Construct flows for our parent. - node = parent; - } - } -} - /// Information that we need stored in each flow. pub struct FlowParallelInfo { /// The number of children that still need work done. @@ -332,59 +232,102 @@ impl<'a> ParallelPreorderFlowTraversal for ComputeAbsolutePositions<'a> { impl<'a> ParallelPostorderFlowTraversal for BuildDisplayList<'a> {} -impl<'a, 'ln, ConcreteLayoutNode> ParallelPostorderDomTraversal<'ln, ConcreteLayoutNode> - for ConstructFlows<'a> - where ConcreteLayoutNode: LayoutNode<'ln> { - fn root(&self) -> OpaqueNode { - self.root - } -} - -impl<'a, 'ln, ConcreteLayoutNode> ParallelPreorderDomTraversal<'ln, ConcreteLayoutNode> - for RecalcStyleForNode<'a> - where ConcreteLayoutNode: LayoutNode<'ln> { - fn run_parallel(&self, - unsafe_nodes: UnsafeNodeList, - proxy: &mut WorkerProxy) { - // Not exactly sure why we need UFCS here, but we seem to. - as ParallelPreorderDomTraversal<'ln, ConcreteLayoutNode>> - ::run_parallel_helper(self, unsafe_nodes, proxy, - recalc_style::<'ln, ConcreteLayoutNode>, - construct_flows::<'ln, ConcreteLayoutNode>) - } -} - -fn recalc_style<'ln, ConcreteLayoutNode>(unsafe_nodes: UnsafeNodeList, - proxy: &mut WorkerProxy) - where ConcreteLayoutNode: LayoutNode<'ln> { +/// A parallel top-down DOM traversal. +#[inline(always)] +fn top_down_dom<'ln, N, T>(unsafe_nodes: UnsafeNodeList, + proxy: &mut WorkerProxy) + where N: LayoutNode<'ln>, T: DomTraversal<'ln, N> { let shared_layout_context = proxy.user_data(); let layout_context = LayoutContext::new(shared_layout_context); - let recalc_style_for_node_traversal = RecalcStyleForNode { + let traversal_context = DomTraversalContext { layout_context: &layout_context, root: unsafe_nodes.1, }; - // The UFCS is necessary here to select the proper set of generic routines which - // will eventually cast the UnsafeNode to a concrete LayoutNode implementation. - > - ::run_parallel(&recalc_style_for_node_traversal, unsafe_nodes, proxy) + let mut discovered_child_nodes = Vec::new(); + for unsafe_node in *unsafe_nodes.0 { + // Get a real layout node. + let node = unsafe { N::from_unsafe(&unsafe_node) }; + + // Perform the appropriate traversal. + T::process_preorder(&traversal_context, node); + + let child_count = node.children_count(); + + // Reset the count of children. + { + let data = node.mutate_data().unwrap(); + data.parallel.children_count.store(child_count as isize, + Ordering::Relaxed); + } + + // Possibly enqueue the children. + if child_count != 0 { + for kid in node.children() { + discovered_child_nodes.push(kid.to_unsafe()) + } + } else { + // If there were no more children, start walking back up. + bottom_up_dom::(unsafe_nodes.1, unsafe_node, proxy) + } + } + + for chunk in discovered_child_nodes.chunks(CHUNK_SIZE) { + proxy.push(WorkUnit { + fun: top_down_dom::, + data: (box chunk.iter().cloned().collect(), unsafe_nodes.1), + }); + } } -fn construct_flows<'ln, ConcreteLayoutNode: LayoutNode<'ln>>( - root: OpaqueNode, - unsafe_node: UnsafeNode, - proxy: &mut WorkerProxy) { +/// Process current node and potentially traverse its ancestors. +/// +/// If we are the last child that finished processing, recursively process +/// our parent. Else, stop. Also, stop at the root. +/// +/// Thus, if we start with all the leaves of a tree, we end up traversing +/// the whole tree bottom-up because each parent will be processed exactly +/// once (by the last child that finishes processing). +/// +/// The only communication between siblings is that they both +/// fetch-and-subtract the parent's children count. +fn bottom_up_dom<'ln, N, T>(root: OpaqueNode, + unsafe_node: UnsafeNode, + proxy: &mut WorkerProxy) + where N: LayoutNode<'ln>, T: DomTraversal<'ln, N> { let shared_layout_context = proxy.user_data(); let layout_context = LayoutContext::new(shared_layout_context); - let construct_flows_traversal = ConstructFlows { + let traversal_context = DomTraversalContext { layout_context: &layout_context, root: root, }; - // The UFCS is necessary here to select the proper set of generic routines which - // will eventually cast the UnsafeNode to a concrete LayoutNode implementation. - > - ::run_parallel(&construct_flows_traversal, unsafe_node) + // Get a real layout node. + let mut node = unsafe { N::from_unsafe(&unsafe_node) }; + loop { + // Perform the appropriate operation. + T::process_postorder(&traversal_context, node); + + let parent = match node.layout_parent_node(traversal_context.root) { + None => break, + Some(parent) => parent, + }; + + let parent_data = unsafe { + &*parent.borrow_data_unchecked().unwrap() + }; + + if parent_data + .parallel + .children_count + .fetch_sub(1, Ordering::Relaxed) != 1 { + // Get out of here and find another node to work on. + break + } + + // We were the last child of our parent. Construct flows for our parent. + node = parent; + } } fn assign_inline_sizes(unsafe_flows: UnsafeFlowList, @@ -441,12 +384,14 @@ fn run_queue_with_custom_work_data_type( queue.run(shared_layout_context); } -pub fn traverse_dom_preorder<'ln, ConcreteLayoutNode: LayoutNode<'ln>>(root: ConcreteLayoutNode, +pub fn traverse_dom_preorder<'ln, N, T>( + root: N, shared_layout_context: &SharedLayoutContext, - queue: &mut WorkQueue) { + queue: &mut WorkQueue) + where N: LayoutNode<'ln>, T: DomTraversal<'ln, N> { run_queue_with_custom_work_data_type(queue, |queue| { queue.push(WorkUnit { - fun: recalc_style::<'ln, ConcreteLayoutNode>, + fun: top_down_dom::, data: (box vec![root.to_unsafe()], root.opaque()), }); }, shared_layout_context); diff --git a/components/layout/sequential.rs b/components/layout/sequential.rs index 5f33e5f352c..310560e2fa7 100644 --- a/components/layout/sequential.rs +++ b/components/layout/sequential.rs @@ -14,39 +14,33 @@ use fragment::FragmentBorderBoxIterator; use generated_content::ResolveGeneratedContent; use traversal::PostorderNodeMutTraversal; use traversal::{AssignBSizesAndStoreOverflow, AssignISizes}; -use traversal::{BubbleISizes, ConstructFlows, RecalcStyleForNode}; -use traversal::{BuildDisplayList, ComputeAbsolutePositions}; -use traversal::{PostorderDomTraversal, PreorderDomTraversal}; +use traversal::{BubbleISizes, BuildDisplayList, ComputeAbsolutePositions}; +use traversal::{DomTraversal, DomTraversalContext}; use util::opts; use wrapper::LayoutNode; -pub fn traverse_dom_preorder<'le, N>(root: N, - shared_layout_context: &SharedLayoutContext) - where N: LayoutNode<'le> { - fn doit<'le, N>(node: N, - recalc_style: RecalcStyleForNode, - construct_flows: ConstructFlows) - where N: LayoutNode<'le> { - recalc_style.process(node); +pub fn traverse_dom_preorder<'ln, N, T>(root: N, + shared_layout_context: &SharedLayoutContext) + where N: LayoutNode<'ln>, + T: DomTraversal<'ln, N> { + fn doit<'a, 'ln, N, T>(context: &'a DomTraversalContext<'a>, node: N) + where N: LayoutNode<'ln>, T: DomTraversal<'ln, N> { + T::process_preorder(context, node); for kid in node.children() { - doit(kid, recalc_style, construct_flows); + doit::(context, kid); } - construct_flows.process(node); + T::process_postorder(context, node); } let layout_context = LayoutContext::new(shared_layout_context); - let recalc_style = RecalcStyleForNode { - layout_context: &layout_context, - root: root.opaque(), - }; - let construct_flows = ConstructFlows { + let traversal_context = DomTraversalContext { layout_context: &layout_context, root: root.opaque(), }; - doit::<'le, N>(root, recalc_style, construct_flows); + doit::(&traversal_context, root); } pub fn resolve_generated_content(root: &mut FlowRef, shared_layout_context: &SharedLayoutContext) { diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs index c5bf7d482f3..6a0038053cb 100644 --- a/components/layout/traversal.rs +++ b/components/layout/traversal.rs @@ -118,17 +118,30 @@ fn insert_ancestors_into_bloom_filter<'ln, N>(bf: &mut Box, debug!("[{}] Inserted {} ancestors.", tid(), ancestors); } - -/// A top-down traversal. -pub trait PreorderDomTraversal<'ln, ConcreteLayoutNode: LayoutNode<'ln>> { - /// The operation to perform. Return true to continue or false to stop. - fn process(&self, node: ConcreteLayoutNode); +#[derive(Copy, Clone)] +pub struct DomTraversalContext<'a> { + pub layout_context: &'a LayoutContext<'a>, + pub root: OpaqueNode, } -/// A bottom-up traversal, with a optional in-order pass. -pub trait PostorderDomTraversal<'ln, ConcreteLayoutNode: LayoutNode<'ln>> { - /// The operation to perform. Return true to continue or false to stop. - fn process(&self, node: ConcreteLayoutNode); +pub trait DomTraversal<'ln, N: LayoutNode<'ln>> { + fn process_preorder<'a>(context: &'a DomTraversalContext<'a>, node: N); + fn process_postorder<'a>(context: &'a DomTraversalContext<'a>, 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 RecalcStyleOnly; +impl<'ln, N: LayoutNode<'ln>> DomTraversal<'ln, N> for RecalcStyleOnly { + fn process_preorder<'a>(context: &'a DomTraversalContext<'a>, node: N) { recalc_style_at(context, node); } + fn process_postorder<'a>(_: &'a DomTraversalContext<'a>, _: N) {} +} + +pub struct RecalcStyleAndConstructFlows; +impl<'ln, N: LayoutNode<'ln>> DomTraversal<'ln, N> for RecalcStyleAndConstructFlows { + fn process_preorder<'a>(context: &'a DomTraversalContext<'a>, node: N) { recalc_style_at(context, node); } + fn process_postorder<'a>(context: &'a DomTraversalContext<'a>, node: N) { construct_flows_at(context, node); } } /// A bottom-up, parallelizable traversal. @@ -139,176 +152,156 @@ pub trait PostorderNodeMutTraversal<'ln, ConcreteThreadSafeLayoutNode: ThreadSaf /// The recalc-style-for-node traversal, which styles each node and must run before /// layout computation. This computes the styles applied to each node. -#[derive(Copy, Clone)] -pub struct RecalcStyleForNode<'a> { - pub layout_context: &'a LayoutContext<'a>, - pub root: OpaqueNode, -} +#[inline] +#[allow(unsafe_code)] +fn recalc_style_at<'a, 'ln, N: LayoutNode<'ln>> (context: &'a DomTraversalContext<'a>, node: N) { + // Initialize layout data. + // + // FIXME(pcwalton): Stop allocating here. Ideally this should just be done by the HTML + // parser. + node.initialize_data(); -impl<'a, 'ln, ConcreteLayoutNode> PreorderDomTraversal<'ln, ConcreteLayoutNode> - for RecalcStyleForNode<'a> - where ConcreteLayoutNode: LayoutNode<'ln> { - #[inline] - #[allow(unsafe_code)] - fn process(&self, node: ConcreteLayoutNode) { - // 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(context.root); - // Get the parent node. - let parent_opt = node.layout_parent_node(self.root); + // Get the style bloom filter. + let mut bf = take_task_local_bloom_filter(parent_opt, context.root, context.layout_context); - // Get the style bloom filter. - let mut bf = take_task_local_bloom_filter(parent_opt, self.root, self.layout_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() { - let node = node.to_threadsafe(); - node.unstyle(); - } - - // Check to see whether we can share a style with someone. - let style_sharing_candidate_cache = - &mut self.layout_context.style_sharing_candidate_cache(); - - 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 { &*self.layout_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.to_threadsafe().set_restyle_damage( - incremental::rebuild_and_reflow()) - } - None - }, - }; - - // Perform the CSS cascade. - unsafe { - node.cascade_node(self.layout_context.shared, - parent_opt, - &applicable_declarations, - &mut self.layout_context.applicable_declarations_cache(), - &self.layout_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.to_threadsafe().set_restyle_damage(damage); - } - } + 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() { + let node = node.to_threadsafe(); + node.unstyle(); } - let unsafe_layout_node = node.to_unsafe(); + // Check to see whether we can share a style with someone. + let style_sharing_candidate_cache = + &mut context.layout_context.style_sharing_candidate_cache(); - // 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); + 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, + }; - // NB: flow construction updates the bloom filter on the way up. - put_task_local_bloom_filter(bf, &unsafe_layout_node, self.layout_context); + // 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.layout_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.to_threadsafe().set_restyle_damage( + incremental::rebuild_and_reflow()) + } + None + }, + }; + + // Perform the CSS cascade. + unsafe { + node.cascade_node(context.layout_context.shared, + parent_opt, + &applicable_declarations, + &mut context.layout_context.applicable_declarations_cache(), + &context.layout_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.to_threadsafe().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.layout_context); } /// The flow construction traversal, which builds flows for styled nodes. -#[derive(Copy, Clone)] -pub struct ConstructFlows<'a> { - pub layout_context: &'a LayoutContext<'a>, - pub root: OpaqueNode, -} +#[inline] +#[allow(unsafe_code)] +fn construct_flows_at<'a, 'ln, N: LayoutNode<'ln>>(context: &'a DomTraversalContext<'a>, node: N) { + // Construct flows for this node. + { + let tnode = node.to_threadsafe(); -impl<'a, 'ln, ConcreteLayoutNode> PostorderDomTraversal<'ln, ConcreteLayoutNode> - for ConstructFlows<'a> - where ConcreteLayoutNode: LayoutNode<'ln> { - #[inline] - #[allow(unsafe_code)] - fn process(&self, node: ConcreteLayoutNode) { - // Construct flows for this node. - { - let tnode = node.to_threadsafe(); - - // Always reconstruct if incremental layout is turned off. - let nonincremental_layout = opts::get().nonincremental_layout; - if nonincremental_layout || node.has_dirty_descendants() { - let mut flow_constructor = FlowConstructor::new(self.layout_context); - if nonincremental_layout || !flow_constructor.repair_if_possible(&tnode) { - flow_constructor.process(&tnode); - debug!("Constructed flow for {:x}: {:x}", - tnode.debug_id(), - tnode.flow_debug_id()); - } + // Always reconstruct if incremental layout is turned off. + let nonincremental_layout = opts::get().nonincremental_layout; + if nonincremental_layout || node.has_dirty_descendants() { + let mut flow_constructor = FlowConstructor::new(context.layout_context); + if nonincremental_layout || !flow_constructor.repair_if_possible(&tnode) { + flow_constructor.process(&tnode); + debug!("Constructed flow for {:x}: {:x}", + tnode.debug_id(), + tnode.flow_debug_id()); } - - // Reset the layout damage in this node. It's been propagated to the - // flow by the flow constructor. - tnode.set_restyle_damage(RestyleDamage::empty()); } - unsafe { - node.set_changed(false); - node.set_dirty(false); - node.set_dirty_descendants(false); - } - - let unsafe_layout_node = node.to_unsafe(); - - let (mut bf, old_node, old_generation) = - STYLE_BLOOM.with(|style_bloom| { - mem::replace(&mut *style_bloom.borrow_mut(), None) - .expect("The bloom filter should have been set by style recalc.") - }); - - assert_eq!(old_node, unsafe_layout_node); - assert_eq!(old_generation, self.layout_context.shared_context().generation); - - match node.layout_parent_node(self.root) { - None => { - debug!("[{}] - {:X}, and deleting BF.", tid(), unsafe_layout_node.0); - // If this is the reflow root, eat the task-local bloom filter. - } - Some(parent) => { - // Otherwise, put it back, but remove this node. - node.remove_from_bloom_filter(&mut *bf); - let unsafe_parent = parent.to_unsafe(); - put_task_local_bloom_filter(bf, &unsafe_parent, self.layout_context); - }, - }; + // Reset the layout damage in this node. It's been propagated to the + // flow by the flow constructor. + tnode.set_restyle_damage(RestyleDamage::empty()); } + + unsafe { + node.set_changed(false); + node.set_dirty(false); + node.set_dirty_descendants(false); + } + + let unsafe_layout_node = node.to_unsafe(); + + let (mut bf, old_node, old_generation) = + STYLE_BLOOM.with(|style_bloom| { + mem::replace(&mut *style_bloom.borrow_mut(), None) + .expect("The bloom filter should have been set by style recalc.") + }); + + assert_eq!(old_node, unsafe_layout_node); + assert_eq!(old_generation, context.layout_context.shared_context().generation); + + match node.layout_parent_node(context.root) { + None => { + debug!("[{}] - {:X}, and deleting BF.", tid(), unsafe_layout_node.0); + // If this is the reflow root, eat the task-local bloom filter. + } + Some(parent) => { + // Otherwise, put it back, but remove this node. + node.remove_from_bloom_filter(&mut *bf); + let unsafe_parent = parent.to_unsafe(); + put_task_local_bloom_filter(bf, &unsafe_parent, context.layout_context); + }, + }; } /// The bubble-inline-sizes traversal, the first part of layout computation. This computes From 27987c3bb43566252048734f536e36c6eef9e745 Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Thu, 31 Dec 2015 11:45:32 -0800 Subject: [PATCH 2/5] Introduce a concept of restyle damage to the style system. We can't hoist RestyleDamage itself, because it's very layout-dependent. But this should be enough to let us hoist the things we need. --- components/layout/flow.rs | 5 +++-- components/layout/fragment.rs | 5 +++-- components/layout/generated_content.rs | 5 +++-- components/layout/incremental.rs | 27 +++++++++++++++---------- components/layout/traversal.rs | 7 +++---- components/layout/wrapper.rs | 28 +++++++++++++++++--------- components/style/dom.rs | 15 +++++++++++++- 7 files changed, 61 insertions(+), 31 deletions(-) diff --git a/components/layout/flow.rs b/components/layout/flow.rs index 0440dcb52bb..4534061f3de 100644 --- a/components/layout/flow.rs +++ b/components/layout/flow.rs @@ -35,7 +35,7 @@ use flow_ref::{self, FlowRef, WeakFlowRef}; use fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo}; use gfx::display_list::{ClippingRegion, DisplayList}; use gfx_traits::LayerId; -use incremental::{self, RECONSTRUCT_FLOW, REFLOW, REFLOW_OUT_OF_FLOW, RestyleDamage}; +use incremental::{RECONSTRUCT_FLOW, REFLOW, REFLOW_OUT_OF_FLOW, RestyleDamage}; use inline::InlineFlow; use model::{CollapsibleMargins, IntrinsicISizes, MarginCollapseInfo}; use msg::compositor_msg::LayerType; @@ -48,6 +48,7 @@ use std::sync::Arc; use std::sync::atomic::Ordering; use std::{fmt, mem, raw}; use style::computed_values::{clear, display, empty_cells, float, position, text_align}; +use style::dom::TRestyleDamage; use style::properties::{self, ComputedValues}; use style::values::computed::LengthOrPercentageOrAuto; use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, TableFlow}; @@ -1046,7 +1047,7 @@ impl BaseFlow { } // New flows start out as fully damaged. - let mut damage = incremental::rebuild_and_reflow(); + let mut damage = RestyleDamage::rebuild_and_reflow(); damage.remove(RECONSTRUCT_FLOW); BaseFlow { diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 828cdeabf8d..16082ad075a 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -18,7 +18,7 @@ use gfx::display_list::{BLUR_INFLATION_FACTOR, OpaqueNode}; use gfx::text::glyph::CharIndex; use gfx::text::text_run::{TextRun, TextRunSlice}; use gfx_traits::LayerId; -use incremental::{self, RECONSTRUCT_FLOW, RestyleDamage}; +use incremental::{RECONSTRUCT_FLOW, RestyleDamage}; use inline::{FIRST_FRAGMENT_OF_ELEMENT, InlineFragmentContext, InlineFragmentNodeInfo}; use inline::{InlineMetrics, LAST_FRAGMENT_OF_ELEMENT}; use ipc_channel::ipc::IpcSender; @@ -40,6 +40,7 @@ use style::computed_values::content::ContentItem; use style::computed_values::{border_collapse, clear, display, mix_blend_mode, overflow_wrap}; use style::computed_values::{overflow_x, position, text_decoration, transform_style}; use style::computed_values::{white_space, word_break, z_index}; +use style::dom::TRestyleDamage; use style::properties::ComputedValues; use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto}; use style::values::computed::{LengthOrPercentageOrNone}; @@ -833,7 +834,7 @@ impl Fragment { self.border_box.start, size); - let mut restyle_damage = incremental::rebuild_and_reflow(); + let mut restyle_damage = RestyleDamage::rebuild_and_reflow(); restyle_damage.remove(RECONSTRUCT_FLOW); Fragment { diff --git a/components/layout/generated_content.rs b/components/layout/generated_content.rs index fd1f1d2f219..d6a2149237c 100644 --- a/components/layout/generated_content.rs +++ b/components/layout/generated_content.rs @@ -13,12 +13,13 @@ use flow::{InorderFlowTraversal}; use flow::{self, AFFECTS_COUNTERS, Flow, HAS_COUNTER_AFFECTING_CHILDREN, ImmutableFlowUtils}; use fragment::{Fragment, GeneratedContentInfo, SpecificFragmentInfo, UnscannedTextFragmentInfo}; use gfx::display_list::OpaqueNode; -use incremental::{self, RESOLVE_GENERATED_CONTENT}; +use incremental::{RESOLVE_GENERATED_CONTENT, RestyleDamage}; use smallvec::SmallVec; use std::collections::{HashMap, LinkedList}; use std::sync::Arc; use style::computed_values::content::ContentItem; use style::computed_values::{display, list_style_type}; +use style::dom::TRestyleDamage; use style::properties::ComputedValues; use text::TextRunScanner; use wrapper::PseudoElementType; @@ -431,7 +432,7 @@ fn render_text(layout_context: &LayoutContext, fragments.push_back(Fragment::from_opaque_node_and_style(node, pseudo, style, - incremental::rebuild_and_reflow(), + RestyleDamage::rebuild_and_reflow(), info)); // FIXME(pcwalton): This should properly handle multiple marker fragments. This could happen // due to text run splitting. diff --git a/components/layout/incremental.rs b/components/layout/incremental.rs index d69610daa12..6865d037651 100644 --- a/components/layout/incremental.rs +++ b/components/layout/incremental.rs @@ -6,6 +6,7 @@ use flow::{self, AFFECTS_COUNTERS, Flow, HAS_COUNTER_AFFECTING_CHILDREN, IS_ABSO use std::fmt; use std::sync::Arc; use style::computed_values::float; +use style::dom::TRestyleDamage; use style::properties::ComputedValues; bitflags! { @@ -47,6 +48,18 @@ bitflags! { } } +impl TRestyleDamage for RestyleDamage { + fn compute(old: &Option>, new: &ComputedValues) -> RestyleDamage { compute_damage(old, new) } + + /// Returns a bitmask that represents a flow that needs to be rebuilt and reflowed. + /// + /// Use this instead of `RestyleDamage::all()` because `RestyleDamage::all()` will result in + /// unnecessary sequential resolution of generated content. + fn rebuild_and_reflow() -> RestyleDamage { + REPAINT | BUBBLE_ISIZES | REFLOW_OUT_OF_FLOW | REFLOW | RECONSTRUCT_FLOW + } +} + impl RestyleDamage { /// Supposing a flow has the given `position` property and this damage, returns the damage that @@ -130,18 +143,10 @@ macro_rules! add_if_not_equal( }) ); -/// Returns a bitmask that represents a flow that needs to be rebuilt and reflowed. -/// -/// Use this instead of `RestyleDamage::all()` because `RestyleDamage::all()` will result in -/// unnecessary sequential resolution of generated content. -pub fn rebuild_and_reflow() -> RestyleDamage { - REPAINT | BUBBLE_ISIZES | REFLOW_OUT_OF_FLOW | REFLOW | RECONSTRUCT_FLOW -} - pub fn compute_damage(old: &Option>, new: &ComputedValues) -> RestyleDamage { let old: &ComputedValues = match old.as_ref() { - None => return rebuild_and_reflow(), + None => return RestyleDamage::rebuild_and_reflow(), Some(cv) => &**cv, }; @@ -195,7 +200,7 @@ pub fn compute_damage(old: &Option>, new: &ComputedValues) - // If the layer requirements of this flow have changed due to the value // of the transform, then reflow is required to rebuild the layers. if old.transform_requires_layer() != new.transform_requires_layer() { - damage.insert(rebuild_and_reflow()); + damage.insert(RestyleDamage::rebuild_and_reflow()); } // FIXME: test somehow that we checked every CSS property @@ -256,7 +261,7 @@ impl<'a> LayoutDamageComputation for &'a mut Flow { fn reflow_entire_document(self) { let self_base = flow::mut_base(self); - self_base.restyle_damage.insert(rebuild_and_reflow()); + self_base.restyle_damage.insert(RestyleDamage::rebuild_and_reflow()); self_base.restyle_damage.remove(RECONSTRUCT_FLOW); for kid in self_base.children.iter_mut() { kid.reflow_entire_document(); diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs index 6a0038053cb..e219a64f30d 100644 --- a/components/layout/traversal.rs +++ b/components/layout/traversal.rs @@ -10,13 +10,13 @@ use css::matching::{ElementMatchMethods, MatchMethods, StyleSharingResult}; use flow::{PostorderFlowTraversal, PreorderFlowTraversal}; use flow::{self, Flow}; use gfx::display_list::OpaqueNode; -use incremental::{self, BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW, REPAINT, RestyleDamage}; +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 style::context::StyleContext; -use style::dom::UnsafeNode; +use style::dom::{TRestyleDamage, UnsafeNode}; use style::matching::ApplicableDeclarations; use util::opts; use util::tid::tid; @@ -209,8 +209,7 @@ fn recalc_style_at<'a, 'ln, N: LayoutNode<'ln>> (context: &'a DomTraversalContex }, None => { if node.has_changed() { - node.to_threadsafe().set_restyle_damage( - incremental::rebuild_and_reflow()) + node.set_restyle_damage(N::ConcreteRestyleDamage::rebuild_and_reflow()) } None }, diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs index 147fda916e3..4a3ca347848 100644 --- a/components/layout/wrapper.rs +++ b/components/layout/wrapper.rs @@ -132,6 +132,7 @@ impl<'ln> ServoLayoutNode<'ln> { impl<'ln> TNode<'ln> for ServoLayoutNode<'ln> { type ConcreteElement = ServoLayoutElement<'ln>; type ConcreteDocument = ServoLayoutDocument<'ln>; + type ConcreteRestyleDamage = RestyleDamage; fn to_unsafe(&self) -> UnsafeNode { unsafe { @@ -237,6 +238,14 @@ impl<'ln> TNode<'ln> for ServoLayoutNode<'ln> { unsafe { self.mutate_layout_data().map(|d| transmute(d)) } } + fn restyle_damage(self) -> RestyleDamage { + self.borrow_layout_data().unwrap().restyle_damage + } + + fn set_restyle_damage(self, damage: RestyleDamage) { + self.mutate_layout_data().unwrap().restyle_damage = damage; + } + fn parent_node(&self) -> Option> { unsafe { self.node.parent_node_ref().map(|node| self.new_with_this_lifetime(&node)) @@ -714,16 +723,9 @@ pub trait ThreadSafeLayoutNode<'ln> : Clone + Copy + Sized { fn is_ignorable_whitespace(&self) -> bool; - /// Get the description of how to account for recent style changes. - /// This is a simple bitfield and fine to copy by value. - fn restyle_damage(self) -> RestyleDamage { - self.borrow_layout_data().unwrap().restyle_damage - } + fn restyle_damage(self) -> RestyleDamage; - /// Set the restyle damage field. - fn set_restyle_damage(self, damage: RestyleDamage) { - self.mutate_layout_data().unwrap().restyle_damage = damage; - } + fn set_restyle_damage(self, damage: RestyleDamage); /// Returns the layout data flags for this node. fn flags(self) -> LayoutDataFlags; @@ -909,6 +911,14 @@ impl<'ln> ThreadSafeLayoutNode<'ln> for ServoThreadSafeLayoutNode<'ln> { } } + fn restyle_damage(self) -> RestyleDamage { + self.node.restyle_damage() + } + + fn set_restyle_damage(self, damage: RestyleDamage) { + self.node.set_restyle_damage(damage) + } + fn flags(self) -> LayoutDataFlags { unsafe { (*self.node.borrow_layout_data_unchecked().unwrap()).flags diff --git a/components/style/dom.rs b/components/style/dom.rs index 4567e986bae..40933b03a7e 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -5,13 +5,15 @@ #![allow(unsafe_code)] use data::PrivateStyleData; -use properties::{PropertyDeclaration, PropertyDeclarationBlock}; +use properties::{ComputedValues, PropertyDeclaration, PropertyDeclarationBlock}; use restyle_hints::{ElementSnapshot, RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint}; use selectors::matching::DeclarationBlock; use selectors::states::ElementState; use smallvec::VecLike; use std::cell::{Ref, RefMut}; use std::marker::PhantomData; +use std::ops::BitOr; +use std::sync::Arc; use string_cache::{Atom, Namespace}; /// Opaque type stored in type-unsafe work queues for parallel layout. @@ -39,10 +41,15 @@ impl OpaqueNode { } } +pub trait TRestyleDamage : BitOr + Copy { + fn compute(old: &Option>, new: &ComputedValues) -> Self; + fn rebuild_and_reflow() -> Self; +} pub trait TNode<'ln> : Sized + Copy + Clone { type ConcreteElement: TElement<'ln, ConcreteNode = Self, ConcreteDocument = Self::ConcreteDocument>; type ConcreteDocument: TDocument<'ln, ConcreteNode = Self, ConcreteElement = Self::ConcreteElement>; + type ConcreteRestyleDamage: TRestyleDamage; fn to_unsafe(&self) -> UnsafeNode; unsafe fn from_unsafe(n: &UnsafeNode) -> Self; @@ -134,6 +141,12 @@ pub trait TNode<'ln> : Sized + Copy + Clone { #[inline(always)] fn mutate_data(&self) -> Option>; + /// Get the description of how to account for recent style changes. + fn restyle_damage(self) -> Self::ConcreteRestyleDamage; + + /// Set the restyle damage field. + fn set_restyle_damage(self, damage: Self::ConcreteRestyleDamage); + fn parent_node(&self) -> Option; fn first_child(&self) -> Option; From 08f2a24552a0ee61769792272848e397292f48bb Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Thu, 31 Dec 2015 12:43:51 -0800 Subject: [PATCH 3/5] Remove the dependency of css/matching.rs on concrete RestyleDamage. We can make this easier by inlining helper method implementations in the traits themselves, which makes the code more compact as a nice side-effect. --- components/layout/animation.rs | 11 +- components/layout/css/matching.rs | 194 +++++++++++------------------- components/layout/traversal.rs | 2 +- 3 files changed, 79 insertions(+), 128 deletions(-) diff --git a/components/layout/animation.rs b/components/layout/animation.rs index 4a8932ad2e8..bf705465709 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -6,7 +6,7 @@ use flow::{self, Flow}; use gfx::display_list::OpaqueNode; -use incremental::{self, RestyleDamage}; +use incremental::RestyleDamage; use msg::constellation_msg::{AnimationState, ConstellationChan, PipelineId}; use script::layout_interface::Animation; use script_traits::LayoutMsg as ConstellationMsg; @@ -15,6 +15,7 @@ use std::collections::hash_map::Entry; use std::sync::mpsc::{Sender, Receiver}; use std::sync::{Arc, Mutex}; use style::animation::{GetMod, PropertyAnimation}; +use style::dom::TRestyleDamage; use style::properties::ComputedValues; use time; @@ -142,9 +143,9 @@ pub fn recalc_style_for_animations(flow: &mut Flow, /// Updates a single animation and associated style based on the current time. If `damage` is /// provided, inserts the appropriate restyle damage. -pub fn update_style_for_animation(animation: &Animation, - style: &mut Arc, - damage: Option<&mut RestyleDamage>) { +pub fn update_style_for_animation(animation: &Animation, + style: &mut Arc, + damage: Option<&mut ConcreteRestyleDamage>) { let now = time::precise_time_s(); let mut progress = (now - animation.start_time) / animation.duration(); if progress > 1.0 { @@ -157,7 +158,7 @@ pub fn update_style_for_animation(animation: &Animation, let mut new_style = (*style).clone(); animation.property_animation.update(&mut *Arc::make_mut(&mut new_style), progress); if let Some(damage) = damage { - damage.insert(incremental::compute_damage(&Some((*style).clone()), &new_style)); + *damage = *damage | ConcreteRestyleDamage::compute(&Some((*style).clone()), &new_style); } *style = new_style diff --git a/components/layout/css/matching.rs b/components/layout/css/matching.rs index bffd6188344..7e4931be473 100644 --- a/components/layout/css/matching.rs +++ b/components/layout/css/matching.rs @@ -8,18 +8,15 @@ use animation; use context::SharedLayoutContext; -use data::PrivateLayoutData; -use incremental::{self, RestyleDamage}; use msg::ParseErrorReporter; use script::layout_interface::Animation; use selectors::bloom::BloomFilter; use selectors::parser::PseudoElement; use selectors::{Element}; -use std::mem::transmute; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; use style::data::PrivateStyleData; -use style::dom::{TElement, TNode}; +use style::dom::{TElement, TNode, TRestyleDamage}; use style::matching::{ApplicableDeclarations, ApplicableDeclarationsCache}; use style::matching::{StyleSharingCandidate, StyleSharingCandidateCache}; use style::properties::{ComputedValues, cascade}; @@ -28,54 +25,15 @@ use util::arc_ptr_eq; use util::opts; /// The results of attempting to share a style. -pub enum StyleSharingResult { +pub enum StyleSharingResult { /// We didn't find anybody to share the style with. CannotShare, /// The node's style can be shared. The integer specifies the index in the LRU cache that was /// hit and the damage that was done. - StyleWasShared(usize, RestyleDamage), + StyleWasShared(usize, ConcreteRestyleDamage), } -pub trait ElementMatchMethods<'le, ConcreteElement: TElement<'le>> { - fn match_element(&self, - stylist: &Stylist, - parent_bf: Option<&BloomFilter>, - applicable_declarations: &mut ApplicableDeclarations) - -> bool; - - /// Attempts to share a style with another node. This method is unsafe because it depends on - /// the `style_sharing_candidate_cache` having only live nodes in it, and we have no way to - /// guarantee that at the type system level yet. - unsafe fn share_style_if_possible(&self, - style_sharing_candidate_cache: - &mut StyleSharingCandidateCache, - parent: Option) - -> StyleSharingResult; -} - -pub trait MatchMethods<'ln, ConcreteNode: TNode<'ln>> { - /// Inserts and removes the matching `Descendant` selectors from a bloom - /// filter. This is used to speed up CSS selector matching to remove - /// unnecessary tree climbs for `Descendant` queries. - /// - /// A bloom filter of the local names, namespaces, IDs, and classes is kept. - /// Therefore, each node must have its matching selectors inserted _after_ - /// its own selector matching and _before_ its children start. - fn insert_into_bloom_filter(&self, bf: &mut BloomFilter); - - /// After all the children are done css selector matching, this must be - /// called to reset the bloom filter after an `insert`. - fn remove_from_bloom_filter(&self, bf: &mut BloomFilter); - - unsafe fn cascade_node(&self, - layout_context: &SharedLayoutContext, - parent: Option, - applicable_declarations: &ApplicableDeclarations, - applicable_declarations_cache: &mut ApplicableDeclarationsCache, - new_animations_sender: &Mutex>); -} - -trait PrivateMatchMethods { +trait PrivateMatchMethods<'ln>: TNode<'ln> { fn cascade_node_pseudo_element(&self, layout_context: &SharedLayoutContext, parent_style: Option<&Arc>, @@ -86,33 +44,7 @@ trait PrivateMatchMethods { new_animations_sender: &Mutex>, shareable: bool, animate_properties: bool) - -> RestyleDamage; - fn update_animations_for_cascade(&self, - layout_context: &SharedLayoutContext, - style: &mut Option>) - -> bool; -} - -trait PrivateElementMatchMethods<'le, ConcreteElement: TElement<'le>> { - fn share_style_with_candidate_if_possible(&self, - parent_node: Option, - candidate: &StyleSharingCandidate) - -> Option>; -} - -impl<'ln, ConcreteNode> PrivateMatchMethods for ConcreteNode - where ConcreteNode: TNode<'ln> { - fn cascade_node_pseudo_element(&self, - layout_context: &SharedLayoutContext, - parent_style: Option<&Arc>, - applicable_declarations: &[DeclarationBlock], - style: &mut Option>, - applicable_declarations_cache: - &mut ApplicableDeclarationsCache, - new_animations_sender: &Mutex>, - shareable: bool, - animate_properties: bool) - -> RestyleDamage { + -> Self::ConcreteRestyleDamage { let mut cacheable = true; if animate_properties { cacheable = !self.update_animations_for_cascade(layout_context, style) && cacheable; @@ -162,7 +94,7 @@ impl<'ln, ConcreteNode> PrivateMatchMethods for ConcreteNode // Calculate style difference. let this_style = Arc::new(this_style); - let damage = incremental::compute_damage(style, &*this_style); + let damage = Self::ConcreteRestyleDamage::compute(style, &*this_style); // Cache the resolved style if it was cacheable. if cacheable { @@ -212,7 +144,7 @@ impl<'ln, ConcreteNode> PrivateMatchMethods for ConcreteNode if had_running_animations { let mut all_running_animations = layout_context.style_context.running_animations.write().unwrap(); for running_animation in all_running_animations.get(&this_opaque).unwrap() { - animation::update_style_for_animation(running_animation, style, None); + animation::update_style_for_animation::(running_animation, style, None); } all_running_animations.remove(&this_opaque); } @@ -221,11 +153,11 @@ impl<'ln, ConcreteNode> PrivateMatchMethods for ConcreteNode } } -impl<'le, ConcreteElement> PrivateElementMatchMethods<'le, ConcreteElement> - for ConcreteElement - where ConcreteElement: TElement<'le> { +impl<'ln, N: TNode<'ln>> PrivateMatchMethods<'ln> for N {} + +trait PrivateElementMatchMethods<'le>: TElement<'le> { fn share_style_with_candidate_if_possible(&self, - parent_node: Option, + parent_node: Option, candidate: &StyleSharingCandidate) -> Option> { let parent_node = match parent_node { @@ -254,9 +186,9 @@ impl<'le, ConcreteElement> PrivateElementMatchMethods<'le, ConcreteElement> } } -impl<'le, ConcreteElement> ElementMatchMethods<'le, ConcreteElement> - for ConcreteElement - where ConcreteElement: TElement<'le> { +impl<'le, E: TElement<'le>> PrivateElementMatchMethods<'le> for E {} + +pub trait ElementMatchMethods<'le> : TElement<'le> { fn match_element(&self, stylist: &Stylist, parent_bf: Option<&BloomFilter>, @@ -286,11 +218,14 @@ impl<'le, ConcreteElement> ElementMatchMethods<'le, ConcreteElement> applicable_declarations.after.is_empty() } + /// Attempts to share a style with another node. This method is unsafe because it depends on + /// the `style_sharing_candidate_cache` having only live nodes in it, and we have no way to + /// guarantee that at the type system level yet. unsafe fn share_style_if_possible(&self, style_sharing_candidate_cache: &mut StyleSharingCandidateCache, - parent: Option) - -> StyleSharingResult { + parent: Option) + -> StyleSharingResult<>::ConcreteRestyleDamage> { if opts::get().disable_share_style_cache { return StyleSharingResult::CannotShare } @@ -308,7 +243,7 @@ impl<'le, ConcreteElement> ElementMatchMethods<'le, ConcreteElement> // Yay, cache hit. Share the style. let node = self.as_node(); let style = &mut node.mutate_data().unwrap().style; - let damage = incremental::compute_damage(style, &*shared_style); + let damage = <>::ConcreteNode as TNode<'le>>::ConcreteRestyleDamage::compute(style, &*shared_style); *style = Some(shared_style); return StyleSharingResult::StyleWasShared(i, damage) } @@ -320,9 +255,9 @@ impl<'le, ConcreteElement> ElementMatchMethods<'le, ConcreteElement> } } -impl<'ln, ConcreteNode> MatchMethods<'ln, ConcreteNode> - for ConcreteNode - where ConcreteNode: TNode<'ln> { +impl<'le, E: TElement<'le>> ElementMatchMethods<'le> for E {} + +pub trait MatchMethods<'ln> : TNode<'ln> { // The below two functions are copy+paste because I can't figure out how to // write a function which takes a generic function. I don't think it can // be done. @@ -338,6 +273,13 @@ impl<'ln, ConcreteNode> MatchMethods<'ln, ConcreteNode> // - `SimpleSelector::ID` // - `SimpleSelector::Class` + /// Inserts and removes the matching `Descendant` selectors from a bloom + /// filter. This is used to speed up CSS selector matching to remove + /// unnecessary tree climbs for `Descendant` queries. + /// + /// A bloom filter of the local names, namespaces, IDs, and classes is kept. + /// Therefore, each node must have its matching selectors inserted _after_ + /// its own selector matching and _before_ its children start. fn insert_into_bloom_filter(&self, bf: &mut BloomFilter) { // Only elements are interesting. if let Some(element) = self.as_element() { @@ -350,6 +292,8 @@ impl<'ln, ConcreteNode> MatchMethods<'ln, ConcreteNode> } } + /// After all the children are done css selector matching, this must be + /// called to reset the bloom filter after an `insert`. fn remove_from_bloom_filter(&self, bf: &mut BloomFilter) { // Only elements are interesting. if let Some(element) = self.as_element() { @@ -364,7 +308,7 @@ impl<'ln, ConcreteNode> MatchMethods<'ln, ConcreteNode> unsafe fn cascade_node(&self, layout_context: &SharedLayoutContext, - parent: Option, + parent: Option, applicable_declarations: &ApplicableDeclarations, applicable_declarations_cache: &mut ApplicableDeclarationsCache, new_animations_sender: &Mutex>) { @@ -381,52 +325,58 @@ impl<'ln, ConcreteNode> MatchMethods<'ln, ConcreteNode> } }; - let mut data_ref = self.mutate_data().unwrap(); - let mut data = &mut *data_ref; if self.is_text_node() { // Text nodes get a copy of the parent style. This ensures // that during fragment construction any non-inherited // CSS properties (such as vertical-align) are correctly // set on the fragment(s). + let mut data_ref = self.mutate_data().unwrap(); + let mut data = &mut *data_ref; let cloned_parent_style = parent_style.unwrap().clone(); data.style = Some(cloned_parent_style); } else { - let mut damage = self.cascade_node_pseudo_element( - layout_context, - parent_style, - &applicable_declarations.normal, - &mut data.style, - applicable_declarations_cache, - new_animations_sender, - applicable_declarations.normal_shareable, - true); - if !applicable_declarations.before.is_empty() { - damage = damage | self.cascade_node_pseudo_element( + let mut damage; + { + let mut data_ref = self.mutate_data().unwrap(); + let mut data = &mut *data_ref; + damage = self.cascade_node_pseudo_element( layout_context, - Some(data.style.as_ref().unwrap()), - &*applicable_declarations.before, - &mut data.before_style, + parent_style, + &applicable_declarations.normal, + &mut data.style, applicable_declarations_cache, new_animations_sender, - false, - false); - } - if !applicable_declarations.after.is_empty() { - damage = damage | self.cascade_node_pseudo_element( - layout_context, - Some(data.style.as_ref().unwrap()), - &*applicable_declarations.after, - &mut data.after_style, - applicable_declarations_cache, - new_animations_sender, - false, - false); + applicable_declarations.normal_shareable, + true); + if !applicable_declarations.before.is_empty() { + damage = damage | self.cascade_node_pseudo_element( + layout_context, + Some(data.style.as_ref().unwrap()), + &*applicable_declarations.before, + &mut data.before_style, + applicable_declarations_cache, + new_animations_sender, + false, + false); + } + if !applicable_declarations.after.is_empty() { + damage = damage | self.cascade_node_pseudo_element( + layout_context, + Some(data.style.as_ref().unwrap()), + &*applicable_declarations.after, + &mut data.after_style, + applicable_declarations_cache, + new_animations_sender, + false, + false); + } } - // FIXME(bholley): This is the only dependency in this file on non-style - // stuff. - let layout_data: &mut PrivateLayoutData = transmute(data); - layout_data.restyle_damage = damage; + // This method needs to borrow the data as mutable, so make sure data_ref goes out of + // scope first. + self.set_restyle_damage(damage); } } } + +impl<'ln, N: TNode<'ln>> MatchMethods<'ln> for N {} diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs index e219a64f30d..20387952608 100644 --- a/components/layout/traversal.rs +++ b/components/layout/traversal.rs @@ -231,7 +231,7 @@ fn recalc_style_at<'a, 'ln, N: LayoutNode<'ln>> (context: &'a DomTraversalContex } StyleSharingResult::StyleWasShared(index, damage) => { style_sharing_candidate_cache.touch(index); - node.to_threadsafe().set_restyle_damage(damage); + node.set_restyle_damage(damage); } } } From 94b0789a5f32f042232ce5b85305d0e9be30246e Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Thu, 31 Dec 2015 12:51:26 -0800 Subject: [PATCH 4/5] Remove the dependency of css/matching.rs on SharedLayoutContext. --- components/layout/css/matching.rs | 41 +++++++++++++++---------------- components/layout/traversal.rs | 2 +- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/components/layout/css/matching.rs b/components/layout/css/matching.rs index 7e4931be473..958241d0978 100644 --- a/components/layout/css/matching.rs +++ b/components/layout/css/matching.rs @@ -7,7 +7,6 @@ #![allow(unsafe_code)] use animation; -use context::SharedLayoutContext; use msg::ParseErrorReporter; use script::layout_interface::Animation; use selectors::bloom::BloomFilter; @@ -15,6 +14,7 @@ use selectors::parser::PseudoElement; use selectors::{Element}; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; +use style::context::SharedStyleContext; use style::data::PrivateStyleData; use style::dom::{TElement, TNode, TRestyleDamage}; use style::matching::{ApplicableDeclarations, ApplicableDeclarationsCache}; @@ -35,7 +35,7 @@ pub enum StyleSharingResult { trait PrivateMatchMethods<'ln>: TNode<'ln> { fn cascade_node_pseudo_element(&self, - layout_context: &SharedLayoutContext, + context: &SharedStyleContext, parent_style: Option<&Arc>, applicable_declarations: &[DeclarationBlock], style: &mut Option>, @@ -47,7 +47,7 @@ trait PrivateMatchMethods<'ln>: TNode<'ln> { -> Self::ConcreteRestyleDamage { let mut cacheable = true; if animate_properties { - cacheable = !self.update_animations_for_cascade(layout_context, style) && cacheable; + cacheable = !self.update_animations_for_cascade(context, style) && cacheable; } let mut this_style; @@ -58,22 +58,22 @@ trait PrivateMatchMethods<'ln>: TNode<'ln> { None => None, Some(ref style) => Some(&**style), }; - let (the_style, is_cacheable) = cascade(layout_context.style_context.viewport_size, + let (the_style, is_cacheable) = cascade(context.viewport_size, applicable_declarations, shareable, Some(&***parent_style), cached_computed_values, - layout_context.style_context.error_reporter.clone()); + context.error_reporter.clone()); cacheable = cacheable && is_cacheable; this_style = the_style } None => { - let (the_style, is_cacheable) = cascade(layout_context.style_context.viewport_size, + let (the_style, is_cacheable) = cascade(context.viewport_size, applicable_declarations, shareable, None, None, - layout_context.style_context.error_reporter.clone()); + context.error_reporter.clone()); cacheable = cacheable && is_cacheable; this_style = the_style } @@ -108,7 +108,7 @@ trait PrivateMatchMethods<'ln>: TNode<'ln> { } fn update_animations_for_cascade(&self, - layout_context: &SharedLayoutContext, + context: &SharedStyleContext, style: &mut Option>) -> bool { let style = match *style { @@ -120,7 +120,7 @@ trait PrivateMatchMethods<'ln>: TNode<'ln> { let this_opaque = self.opaque(); let had_animations_to_expire; { - let all_expired_animations = layout_context.style_context.expired_animations.read().unwrap(); + let all_expired_animations = context.expired_animations.read().unwrap(); let animations_to_expire = all_expired_animations.get(&this_opaque); had_animations_to_expire = animations_to_expire.is_some(); if let Some(ref animations) = animations_to_expire { @@ -131,18 +131,17 @@ trait PrivateMatchMethods<'ln>: TNode<'ln> { } if had_animations_to_expire { - layout_context.style_context.expired_animations.write().unwrap().remove(&this_opaque); + context.expired_animations.write().unwrap().remove(&this_opaque); } // Merge any running transitions into the current style, and cancel them. - let had_running_animations = layout_context.style_context - .running_animations - .read() - .unwrap() - .get(&this_opaque) - .is_some(); + let had_running_animations = context.running_animations + .read() + .unwrap() + .get(&this_opaque) + .is_some(); if had_running_animations { - let mut all_running_animations = layout_context.style_context.running_animations.write().unwrap(); + let mut all_running_animations = context.running_animations.write().unwrap(); for running_animation in all_running_animations.get(&this_opaque).unwrap() { animation::update_style_for_animation::(running_animation, style, None); } @@ -307,7 +306,7 @@ pub trait MatchMethods<'ln> : TNode<'ln> { } unsafe fn cascade_node(&self, - layout_context: &SharedLayoutContext, + context: &SharedStyleContext, parent: Option, applicable_declarations: &ApplicableDeclarations, applicable_declarations_cache: &mut ApplicableDeclarationsCache, @@ -340,7 +339,7 @@ pub trait MatchMethods<'ln> : TNode<'ln> { let mut data_ref = self.mutate_data().unwrap(); let mut data = &mut *data_ref; damage = self.cascade_node_pseudo_element( - layout_context, + context, parent_style, &applicable_declarations.normal, &mut data.style, @@ -350,7 +349,7 @@ pub trait MatchMethods<'ln> : TNode<'ln> { true); if !applicable_declarations.before.is_empty() { damage = damage | self.cascade_node_pseudo_element( - layout_context, + context, Some(data.style.as_ref().unwrap()), &*applicable_declarations.before, &mut data.before_style, @@ -361,7 +360,7 @@ pub trait MatchMethods<'ln> : TNode<'ln> { } if !applicable_declarations.after.is_empty() { damage = damage | self.cascade_node_pseudo_element( - layout_context, + context, Some(data.style.as_ref().unwrap()), &*applicable_declarations.after, &mut data.after_style, diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs index 20387952608..ded5d81118c 100644 --- a/components/layout/traversal.rs +++ b/components/layout/traversal.rs @@ -217,7 +217,7 @@ fn recalc_style_at<'a, 'ln, N: LayoutNode<'ln>> (context: &'a DomTraversalContex // Perform the CSS cascade. unsafe { - node.cascade_node(context.layout_context.shared, + node.cascade_node(&context.layout_context.shared.style_context, parent_opt, &applicable_declarations, &mut context.layout_context.applicable_declarations_cache(), From 513a75d86a164f9c92bc31050305fbc7df6c8dfe Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Thu, 31 Dec 2015 13:38:51 -0800 Subject: [PATCH 5/5] Hoist the rest of css/matching.rs into style/. --- components/layout/animation.rs | 66 +----- components/layout/css/matching.rs | 381 ------------------------------ components/layout/lib.rs | 4 - components/layout/traversal.rs | 3 +- components/servo/Cargo.lock | 1 + components/style/Cargo.toml | 1 + components/style/animation.rs | 64 ++++- components/style/lib.rs | 1 + components/style/matching.rs | 375 ++++++++++++++++++++++++++++- ports/cef/Cargo.lock | 1 + ports/gonk/Cargo.lock | 1 + 11 files changed, 440 insertions(+), 458 deletions(-) delete mode 100644 components/layout/css/matching.rs diff --git a/components/layout/animation.rs b/components/layout/animation.rs index bf705465709..8728977dc12 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -12,49 +12,10 @@ use script::layout_interface::Animation; use script_traits::LayoutMsg as ConstellationMsg; use std::collections::HashMap; use std::collections::hash_map::Entry; -use std::sync::mpsc::{Sender, Receiver}; -use std::sync::{Arc, Mutex}; -use style::animation::{GetMod, PropertyAnimation}; -use style::dom::TRestyleDamage; -use style::properties::ComputedValues; +use std::sync::mpsc::Receiver; +use style::animation::update_style_for_animation; use time; -/// Inserts transitions into the queue of running animations as applicable for the given style -/// difference. This is called from the layout worker threads. Returns true if any animations were -/// kicked off and false otherwise. -pub fn start_transitions_if_applicable(new_animations_sender: &Mutex>, - node: OpaqueNode, - old_style: &ComputedValues, - new_style: &mut ComputedValues) - -> bool { - let mut had_animations = false; - for i in 0..new_style.get_animation().transition_property.0.len() { - // Create any property animations, if applicable. - let property_animations = PropertyAnimation::from_transition(i, old_style, new_style); - for property_animation in property_animations { - // Set the property to the initial value. - property_animation.update(new_style, 0.0); - - // Kick off the animation. - let now = time::precise_time_s(); - let animation_style = new_style.get_animation(); - let start_time = - now + (animation_style.transition_delay.0.get_mod(i).seconds() as f64); - new_animations_sender.lock().unwrap().send(Animation { - node: node, - property_animation: property_animation, - start_time: start_time, - end_time: start_time + - (animation_style.transition_duration.0.get_mod(i).seconds() as f64), - }).unwrap(); - - had_animations = true - } - } - - had_animations -} - /// Processes any new animations that were discovered after style recalculation. /// Also expire any old animations that have completed, inserting them into `expired_animations`. pub fn update_animation_state(constellation_chan: &ConstellationChan, @@ -140,26 +101,3 @@ pub fn recalc_style_for_animations(flow: &mut Flow, recalc_style_for_animations(kid, animations) } } - -/// Updates a single animation and associated style based on the current time. If `damage` is -/// provided, inserts the appropriate restyle damage. -pub fn update_style_for_animation(animation: &Animation, - style: &mut Arc, - damage: Option<&mut ConcreteRestyleDamage>) { - let now = time::precise_time_s(); - let mut progress = (now - animation.start_time) / animation.duration(); - if progress > 1.0 { - progress = 1.0 - } - if progress <= 0.0 { - return - } - - let mut new_style = (*style).clone(); - animation.property_animation.update(&mut *Arc::make_mut(&mut new_style), progress); - if let Some(damage) = damage { - *damage = *damage | ConcreteRestyleDamage::compute(&Some((*style).clone()), &new_style); - } - - *style = new_style -} diff --git a/components/layout/css/matching.rs b/components/layout/css/matching.rs deleted file mode 100644 index 958241d0978..00000000000 --- a/components/layout/css/matching.rs +++ /dev/null @@ -1,381 +0,0 @@ -/* 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/. */ - -//! High-level interface to CSS selector matching. - -#![allow(unsafe_code)] - -use animation; -use msg::ParseErrorReporter; -use script::layout_interface::Animation; -use selectors::bloom::BloomFilter; -use selectors::parser::PseudoElement; -use selectors::{Element}; -use std::sync::mpsc::Sender; -use std::sync::{Arc, Mutex}; -use style::context::SharedStyleContext; -use style::data::PrivateStyleData; -use style::dom::{TElement, TNode, TRestyleDamage}; -use style::matching::{ApplicableDeclarations, ApplicableDeclarationsCache}; -use style::matching::{StyleSharingCandidate, StyleSharingCandidateCache}; -use style::properties::{ComputedValues, cascade}; -use style::selector_matching::{DeclarationBlock, Stylist}; -use util::arc_ptr_eq; -use util::opts; - -/// The results of attempting to share a style. -pub enum StyleSharingResult { - /// We didn't find anybody to share the style with. - CannotShare, - /// The node's style can be shared. The integer specifies the index in the LRU cache that was - /// hit and the damage that was done. - StyleWasShared(usize, ConcreteRestyleDamage), -} - -trait PrivateMatchMethods<'ln>: TNode<'ln> { - fn cascade_node_pseudo_element(&self, - context: &SharedStyleContext, - parent_style: Option<&Arc>, - applicable_declarations: &[DeclarationBlock], - style: &mut Option>, - applicable_declarations_cache: - &mut ApplicableDeclarationsCache, - new_animations_sender: &Mutex>, - shareable: bool, - animate_properties: bool) - -> Self::ConcreteRestyleDamage { - let mut cacheable = true; - if animate_properties { - cacheable = !self.update_animations_for_cascade(context, style) && cacheable; - } - - let mut this_style; - match parent_style { - Some(ref parent_style) => { - let cache_entry = applicable_declarations_cache.find(applicable_declarations); - let cached_computed_values = match cache_entry { - None => None, - Some(ref style) => Some(&**style), - }; - let (the_style, is_cacheable) = cascade(context.viewport_size, - applicable_declarations, - shareable, - Some(&***parent_style), - cached_computed_values, - context.error_reporter.clone()); - cacheable = cacheable && is_cacheable; - this_style = the_style - } - None => { - let (the_style, is_cacheable) = cascade(context.viewport_size, - applicable_declarations, - shareable, - None, - None, - context.error_reporter.clone()); - cacheable = cacheable && is_cacheable; - this_style = the_style - } - }; - - // Trigger transitions if necessary. This will reset `this_style` back to its old value if - // it did trigger a transition. - if animate_properties { - if let Some(ref style) = *style { - let animations_started = - animation::start_transitions_if_applicable(new_animations_sender, - self.opaque(), - &**style, - &mut this_style); - cacheable = cacheable && !animations_started - } - } - - // Calculate style difference. - let this_style = Arc::new(this_style); - let damage = Self::ConcreteRestyleDamage::compute(style, &*this_style); - - // Cache the resolved style if it was cacheable. - if cacheable { - applicable_declarations_cache.insert(applicable_declarations.to_vec(), - this_style.clone()); - } - - // Write in the final style and return the damage done to our caller. - *style = Some(this_style); - damage - } - - fn update_animations_for_cascade(&self, - context: &SharedStyleContext, - style: &mut Option>) - -> bool { - let style = match *style { - None => return false, - Some(ref mut style) => style, - }; - - // Finish any expired transitions. - let this_opaque = self.opaque(); - let had_animations_to_expire; - { - let all_expired_animations = context.expired_animations.read().unwrap(); - let animations_to_expire = all_expired_animations.get(&this_opaque); - had_animations_to_expire = animations_to_expire.is_some(); - if let Some(ref animations) = animations_to_expire { - for animation in *animations { - animation.property_animation.update(&mut *Arc::make_mut(style), 1.0); - } - } - } - - if had_animations_to_expire { - context.expired_animations.write().unwrap().remove(&this_opaque); - } - - // Merge any running transitions into the current style, and cancel them. - let had_running_animations = context.running_animations - .read() - .unwrap() - .get(&this_opaque) - .is_some(); - if had_running_animations { - let mut all_running_animations = context.running_animations.write().unwrap(); - for running_animation in all_running_animations.get(&this_opaque).unwrap() { - animation::update_style_for_animation::(running_animation, style, None); - } - all_running_animations.remove(&this_opaque); - } - - had_animations_to_expire || had_running_animations - } -} - -impl<'ln, N: TNode<'ln>> PrivateMatchMethods<'ln> for N {} - -trait PrivateElementMatchMethods<'le>: TElement<'le> { - fn share_style_with_candidate_if_possible(&self, - parent_node: Option, - candidate: &StyleSharingCandidate) - -> Option> { - let parent_node = match parent_node { - Some(ref parent_node) if parent_node.as_element().is_some() => parent_node, - Some(_) | None => return None, - }; - - let parent_data: Option<&PrivateStyleData> = unsafe { - parent_node.borrow_data_unchecked().map(|d| &*d) - }; - if let Some(parent_data_ref) = parent_data { - // Check parent style. - let parent_style = (*parent_data_ref).style.as_ref().unwrap(); - if !arc_ptr_eq(parent_style, &candidate.parent_style) { - return None - } - - // Check tag names, classes, etc. - if !candidate.can_share_style_with(self) { - return None - } - - return Some(candidate.style.clone()) - } - None - } -} - -impl<'le, E: TElement<'le>> PrivateElementMatchMethods<'le> for E {} - -pub trait ElementMatchMethods<'le> : TElement<'le> { - fn match_element(&self, - stylist: &Stylist, - parent_bf: Option<&BloomFilter>, - applicable_declarations: &mut ApplicableDeclarations) - -> bool { - let style_attribute = self.style_attribute().as_ref(); - - applicable_declarations.normal_shareable = - stylist.push_applicable_declarations(self, - parent_bf, - style_attribute, - None, - &mut applicable_declarations.normal); - stylist.push_applicable_declarations(self, - parent_bf, - None, - Some(PseudoElement::Before), - &mut applicable_declarations.before); - stylist.push_applicable_declarations(self, - parent_bf, - None, - Some(PseudoElement::After), - &mut applicable_declarations.after); - - applicable_declarations.normal_shareable && - applicable_declarations.before.is_empty() && - applicable_declarations.after.is_empty() - } - - /// Attempts to share a style with another node. This method is unsafe because it depends on - /// the `style_sharing_candidate_cache` having only live nodes in it, and we have no way to - /// guarantee that at the type system level yet. - unsafe fn share_style_if_possible(&self, - style_sharing_candidate_cache: - &mut StyleSharingCandidateCache, - parent: Option) - -> StyleSharingResult<>::ConcreteRestyleDamage> { - if opts::get().disable_share_style_cache { - return StyleSharingResult::CannotShare - } - - if self.style_attribute().is_some() { - return StyleSharingResult::CannotShare - } - if self.get_attr(&ns!(), &atom!("id")).is_some() { - return StyleSharingResult::CannotShare - } - - for (i, &(ref candidate, ())) in style_sharing_candidate_cache.iter().enumerate() { - match self.share_style_with_candidate_if_possible(parent.clone(), candidate) { - Some(shared_style) => { - // Yay, cache hit. Share the style. - let node = self.as_node(); - let style = &mut node.mutate_data().unwrap().style; - let damage = <>::ConcreteNode as TNode<'le>>::ConcreteRestyleDamage::compute(style, &*shared_style); - *style = Some(shared_style); - return StyleSharingResult::StyleWasShared(i, damage) - } - None => {} - } - } - - StyleSharingResult::CannotShare - } -} - -impl<'le, E: TElement<'le>> ElementMatchMethods<'le> for E {} - -pub trait MatchMethods<'ln> : TNode<'ln> { - // The below two functions are copy+paste because I can't figure out how to - // write a function which takes a generic function. I don't think it can - // be done. - // - // Ideally, I'd want something like: - // - // > fn with_really_simple_selectors(&self, f: |&H|); - - - // In terms of `SimpleSelector`s, these two functions will insert and remove: - // - `SimpleSelector::LocalName` - // - `SimpleSelector::Namepace` - // - `SimpleSelector::ID` - // - `SimpleSelector::Class` - - /// Inserts and removes the matching `Descendant` selectors from a bloom - /// filter. This is used to speed up CSS selector matching to remove - /// unnecessary tree climbs for `Descendant` queries. - /// - /// A bloom filter of the local names, namespaces, IDs, and classes is kept. - /// Therefore, each node must have its matching selectors inserted _after_ - /// its own selector matching and _before_ its children start. - fn insert_into_bloom_filter(&self, bf: &mut BloomFilter) { - // Only elements are interesting. - if let Some(element) = self.as_element() { - bf.insert(element.get_local_name()); - bf.insert(element.get_namespace()); - element.get_id().map(|id| bf.insert(&id)); - - // TODO: case-sensitivity depends on the document type and quirks mode - element.each_class(|class| bf.insert(class)); - } - } - - /// After all the children are done css selector matching, this must be - /// called to reset the bloom filter after an `insert`. - fn remove_from_bloom_filter(&self, bf: &mut BloomFilter) { - // Only elements are interesting. - if let Some(element) = self.as_element() { - bf.remove(element.get_local_name()); - bf.remove(element.get_namespace()); - element.get_id().map(|id| bf.remove(&id)); - - // TODO: case-sensitivity depends on the document type and quirks mode - element.each_class(|class| bf.remove(class)); - } - } - - unsafe fn cascade_node(&self, - context: &SharedStyleContext, - parent: Option, - applicable_declarations: &ApplicableDeclarations, - applicable_declarations_cache: &mut ApplicableDeclarationsCache, - new_animations_sender: &Mutex>) { - // Get our parent's style. This must be unsafe so that we don't touch the parent's - // borrow flags. - // - // FIXME(pcwalton): Isolate this unsafety into the `wrapper` module to allow - // enforced safe, race-free access to the parent style. - let parent_style = match parent { - None => None, - Some(parent_node) => { - let parent_style = (*parent_node.borrow_data_unchecked().unwrap()).style.as_ref().unwrap(); - Some(parent_style) - } - }; - - if self.is_text_node() { - // Text nodes get a copy of the parent style. This ensures - // that during fragment construction any non-inherited - // CSS properties (such as vertical-align) are correctly - // set on the fragment(s). - let mut data_ref = self.mutate_data().unwrap(); - let mut data = &mut *data_ref; - let cloned_parent_style = parent_style.unwrap().clone(); - data.style = Some(cloned_parent_style); - } else { - let mut damage; - { - let mut data_ref = self.mutate_data().unwrap(); - let mut data = &mut *data_ref; - damage = self.cascade_node_pseudo_element( - context, - parent_style, - &applicable_declarations.normal, - &mut data.style, - applicable_declarations_cache, - new_animations_sender, - applicable_declarations.normal_shareable, - true); - if !applicable_declarations.before.is_empty() { - damage = damage | self.cascade_node_pseudo_element( - context, - Some(data.style.as_ref().unwrap()), - &*applicable_declarations.before, - &mut data.before_style, - applicable_declarations_cache, - new_animations_sender, - false, - false); - } - if !applicable_declarations.after.is_empty() { - damage = damage | self.cascade_node_pseudo_element( - context, - Some(data.style.as_ref().unwrap()), - &*applicable_declarations.after, - &mut data.after_style, - applicable_declarations_cache, - new_animations_sender, - false, - false); - } - } - - // This method needs to borrow the data as mutable, so make sure data_ref goes out of - // scope first. - self.set_restyle_damage(damage); - } - } -} - -impl<'ln, N: TNode<'ln>> MatchMethods<'ln> for N {} diff --git a/components/layout/lib.rs b/components/layout/lib.rs index e1d2f77f32b..2b17e3b677d 100644 --- a/components/layout/lib.rs +++ b/components/layout/lib.rs @@ -96,7 +96,3 @@ mod table_wrapper; mod text; mod traversal; mod wrapper; - -mod css { - pub mod matching; -} diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs index ded5d81118c..b740e7dbcf3 100644 --- a/components/layout/traversal.rs +++ b/components/layout/traversal.rs @@ -6,7 +6,6 @@ use construct::FlowConstructor; use context::LayoutContext; -use css::matching::{ElementMatchMethods, MatchMethods, StyleSharingResult}; use flow::{PostorderFlowTraversal, PreorderFlowTraversal}; use flow::{self, Flow}; use gfx::display_list::OpaqueNode; @@ -17,7 +16,7 @@ use std::cell::RefCell; use std::mem; use style::context::StyleContext; use style::dom::{TRestyleDamage, UnsafeNode}; -use style::matching::ApplicableDeclarations; +use style::matching::{ApplicableDeclarations, ElementMatchMethods, MatchMethods, StyleSharingResult}; use util::opts; use util::tid::tid; use wrapper::{LayoutNode, ThreadSafeLayoutNode}; diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock index 3d6d7e60012..264fbbdc470 100644 --- a/components/servo/Cargo.lock +++ b/components/servo/Cargo.lock @@ -1790,6 +1790,7 @@ dependencies = [ "smallvec 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "style_traits 0.0.1", + "time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", "url 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "util 0.0.1", ] diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index b39bc4d759a..608df090869 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -38,5 +38,6 @@ string_cache = "0.2" euclid = {version = "0.4", features = ["plugins"]} serde = "0.6" serde_macros = "0.6" +time = "0.1" url = "0.5.2" diff --git a/components/style/animation.rs b/components/style/animation.rs index 374b5396bee..10f96b19b1c 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -4,7 +4,7 @@ use app_units::Au; use cssparser::{Color, RGBA}; -use dom::OpaqueNode; +use dom::{OpaqueNode, TRestyleDamage}; use euclid::point::Point2D; use properties::ComputedValues; use properties::longhands::background_position::computed_value::T as BackgroundPosition; @@ -26,6 +26,9 @@ use properties::longhands::visibility::computed_value::T as Visibility; use properties::longhands::z_index::computed_value::T as ZIndex; use std::cmp::Ordering; use std::iter::repeat; +use std::sync::mpsc::Sender; +use std::sync::{Arc, Mutex}; +use time; use util::bezier::Bezier; use values::CSSFloat; use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone}; @@ -922,3 +925,62 @@ impl GetMod for Vec { &(*self)[i % self.len()] } } + +/// Inserts transitions into the queue of running animations as applicable for the given style +/// difference. This is called from the layout worker threads. Returns true if any animations were +/// kicked off and false otherwise. +pub fn start_transitions_if_applicable(new_animations_sender: &Mutex>, + node: OpaqueNode, + old_style: &ComputedValues, + new_style: &mut ComputedValues) + -> bool { + let mut had_animations = false; + for i in 0..new_style.get_animation().transition_property.0.len() { + // Create any property animations, if applicable. + let property_animations = PropertyAnimation::from_transition(i, old_style, new_style); + for property_animation in property_animations { + // Set the property to the initial value. + property_animation.update(new_style, 0.0); + + // Kick off the animation. + let now = time::precise_time_s(); + let animation_style = new_style.get_animation(); + let start_time = + now + (animation_style.transition_delay.0.get_mod(i).seconds() as f64); + new_animations_sender.lock().unwrap().send(Animation { + node: node, + property_animation: property_animation, + start_time: start_time, + end_time: start_time + + (animation_style.transition_duration.0.get_mod(i).seconds() as f64), + }).unwrap(); + + had_animations = true + } + } + + had_animations +} + +/// Updates a single animation and associated style based on the current time. If `damage` is +/// provided, inserts the appropriate restyle damage. +pub fn update_style_for_animation(animation: &Animation, + style: &mut Arc, + damage: Option<&mut ConcreteRestyleDamage>) { + let now = time::precise_time_s(); + let mut progress = (now - animation.start_time) / animation.duration(); + if progress > 1.0 { + progress = 1.0 + } + if progress <= 0.0 { + return + } + + let mut new_style = (*style).clone(); + animation.property_animation.update(&mut *Arc::make_mut(&mut new_style), progress); + if let Some(damage) = damage { + *damage = *damage | ConcreteRestyleDamage::compute(&Some((*style).clone()), &new_style); + } + + *style = new_style +} diff --git a/components/style/lib.rs b/components/style/lib.rs index 33e6d515994..e010a92f0b2 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -40,6 +40,7 @@ extern crate smallvec; #[macro_use(atom, ns)] extern crate string_cache; #[macro_use] extern crate style_traits; +extern crate time; extern crate url; extern crate util; diff --git a/components/style/matching.rs b/components/style/matching.rs index 3e91425cec9..b8f555edc02 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -2,22 +2,31 @@ * 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/. */ -use dom::{TElement, TNode}; -use properties::{ComputedValues, PropertyDeclaration}; -use selector_matching::DeclarationBlock; +#![allow(unsafe_code)] + +use animation::{self, Animation}; +use context::SharedStyleContext; +use data::PrivateStyleData; +use dom::{TElement, TNode, TRestyleDamage}; +use properties::{ComputedValues, PropertyDeclaration, cascade}; +use selector_matching::{DeclarationBlock, Stylist}; +use selectors::Element; +use selectors::bloom::BloomFilter; use selectors::matching::{CommonStyleAffectingAttributeMode, CommonStyleAffectingAttributes}; use selectors::matching::{common_style_affecting_attributes, rare_style_affecting_attributes}; +use selectors::parser::PseudoElement; use smallvec::SmallVec; use std::hash::{Hash, Hasher}; use std::slice::Iter; -use std::sync::Arc; +use std::sync::mpsc::Sender; +use std::sync::{Arc, Mutex}; use string_cache::{Atom, Namespace}; use util::arc_ptr_eq; use util::cache::{LRUCache, SimpleHashCache}; +use util::opts; use util::vec::ForgetfulSink; -/// Pieces of layout/css/matching.rs, which will eventually be merged -/// into this file. +/// High-level interface to CSS selector matching. fn create_common_style_affecting_attributes_from_element<'le, E: TElement<'le>>(element: &E) -> CommonStyleAffectingAttributes { @@ -341,4 +350,358 @@ impl StyleSharingCandidateCache { } } +/// The results of attempting to share a style. +pub enum StyleSharingResult { + /// We didn't find anybody to share the style with. + CannotShare, + /// The node's style can be shared. The integer specifies the index in the LRU cache that was + /// hit and the damage that was done. + StyleWasShared(usize, ConcreteRestyleDamage), +} +trait PrivateMatchMethods<'ln>: TNode<'ln> { + fn cascade_node_pseudo_element(&self, + context: &SharedStyleContext, + parent_style: Option<&Arc>, + applicable_declarations: &[DeclarationBlock], + style: &mut Option>, + applicable_declarations_cache: + &mut ApplicableDeclarationsCache, + new_animations_sender: &Mutex>, + shareable: bool, + animate_properties: bool) + -> Self::ConcreteRestyleDamage { + let mut cacheable = true; + if animate_properties { + cacheable = !self.update_animations_for_cascade(context, style) && cacheable; + } + + let mut this_style; + match parent_style { + Some(ref parent_style) => { + let cache_entry = applicable_declarations_cache.find(applicable_declarations); + let cached_computed_values = match cache_entry { + None => None, + Some(ref style) => Some(&**style), + }; + let (the_style, is_cacheable) = cascade(context.viewport_size, + applicable_declarations, + shareable, + Some(&***parent_style), + cached_computed_values, + context.error_reporter.clone()); + cacheable = cacheable && is_cacheable; + this_style = the_style + } + None => { + let (the_style, is_cacheable) = cascade(context.viewport_size, + applicable_declarations, + shareable, + None, + None, + context.error_reporter.clone()); + cacheable = cacheable && is_cacheable; + this_style = the_style + } + }; + + // Trigger transitions if necessary. This will reset `this_style` back to its old value if + // it did trigger a transition. + if animate_properties { + if let Some(ref style) = *style { + let animations_started = + animation::start_transitions_if_applicable(new_animations_sender, + self.opaque(), + &**style, + &mut this_style); + cacheable = cacheable && !animations_started + } + } + + // Calculate style difference. + let this_style = Arc::new(this_style); + let damage = Self::ConcreteRestyleDamage::compute(style, &*this_style); + + // Cache the resolved style if it was cacheable. + if cacheable { + applicable_declarations_cache.insert(applicable_declarations.to_vec(), + this_style.clone()); + } + + // Write in the final style and return the damage done to our caller. + *style = Some(this_style); + damage + } + + fn update_animations_for_cascade(&self, + context: &SharedStyleContext, + style: &mut Option>) + -> bool { + let style = match *style { + None => return false, + Some(ref mut style) => style, + }; + + // Finish any expired transitions. + let this_opaque = self.opaque(); + let had_animations_to_expire; + { + let all_expired_animations = context.expired_animations.read().unwrap(); + let animations_to_expire = all_expired_animations.get(&this_opaque); + had_animations_to_expire = animations_to_expire.is_some(); + if let Some(ref animations) = animations_to_expire { + for animation in *animations { + animation.property_animation.update(&mut *Arc::make_mut(style), 1.0); + } + } + } + + if had_animations_to_expire { + context.expired_animations.write().unwrap().remove(&this_opaque); + } + + // Merge any running transitions into the current style, and cancel them. + let had_running_animations = context.running_animations + .read() + .unwrap() + .get(&this_opaque) + .is_some(); + if had_running_animations { + let mut all_running_animations = context.running_animations.write().unwrap(); + for running_animation in all_running_animations.get(&this_opaque).unwrap() { + animation::update_style_for_animation::(running_animation, style, None); + } + all_running_animations.remove(&this_opaque); + } + + had_animations_to_expire || had_running_animations + } +} + +impl<'ln, N: TNode<'ln>> PrivateMatchMethods<'ln> for N {} + +trait PrivateElementMatchMethods<'le>: TElement<'le> { + fn share_style_with_candidate_if_possible(&self, + parent_node: Option, + candidate: &StyleSharingCandidate) + -> Option> { + let parent_node = match parent_node { + Some(ref parent_node) if parent_node.as_element().is_some() => parent_node, + Some(_) | None => return None, + }; + + let parent_data: Option<&PrivateStyleData> = unsafe { + parent_node.borrow_data_unchecked().map(|d| &*d) + }; + + if let Some(parent_data_ref) = parent_data { + // Check parent style. + let parent_style = (*parent_data_ref).style.as_ref().unwrap(); + if !arc_ptr_eq(parent_style, &candidate.parent_style) { + return None + } + // Check tag names, classes, etc. + if !candidate.can_share_style_with(self) { + return None + } + return Some(candidate.style.clone()) + } + None + } +} + +impl<'le, E: TElement<'le>> PrivateElementMatchMethods<'le> for E {} + +pub trait ElementMatchMethods<'le> : TElement<'le> { + fn match_element(&self, + stylist: &Stylist, + parent_bf: Option<&BloomFilter>, + applicable_declarations: &mut ApplicableDeclarations) + -> bool { + let style_attribute = self.style_attribute().as_ref(); + + applicable_declarations.normal_shareable = + stylist.push_applicable_declarations(self, + parent_bf, + style_attribute, + None, + &mut applicable_declarations.normal); + stylist.push_applicable_declarations(self, + parent_bf, + None, + Some(PseudoElement::Before), + &mut applicable_declarations.before); + stylist.push_applicable_declarations(self, + parent_bf, + None, + Some(PseudoElement::After), + &mut applicable_declarations.after); + + applicable_declarations.normal_shareable && + applicable_declarations.before.is_empty() && + applicable_declarations.after.is_empty() + } + + /// Attempts to share a style with another node. This method is unsafe because it depends on + /// the `style_sharing_candidate_cache` having only live nodes in it, and we have no way to + /// guarantee that at the type system level yet. + unsafe fn share_style_if_possible(&self, + style_sharing_candidate_cache: + &mut StyleSharingCandidateCache, + parent: Option) + -> StyleSharingResult<>::ConcreteRestyleDamage> { + if opts::get().disable_share_style_cache { + return StyleSharingResult::CannotShare + } + + if self.style_attribute().is_some() { + return StyleSharingResult::CannotShare + } + if self.get_attr(&ns!(), &atom!("id")).is_some() { + return StyleSharingResult::CannotShare + } + + for (i, &(ref candidate, ())) in style_sharing_candidate_cache.iter().enumerate() { + match self.share_style_with_candidate_if_possible(parent.clone(), candidate) { + Some(shared_style) => { + // Yay, cache hit. Share the style. + let node = self.as_node(); + let style = &mut node.mutate_data().unwrap().style; + let damage = <>::ConcreteNode as TNode<'le>> + ::ConcreteRestyleDamage::compute(style, &*shared_style); + *style = Some(shared_style); + return StyleSharingResult::StyleWasShared(i, damage) + } + None => {} + } + } + + StyleSharingResult::CannotShare + } +} + +impl<'le, E: TElement<'le>> ElementMatchMethods<'le> for E {} + +pub trait MatchMethods<'ln> : TNode<'ln> { + // The below two functions are copy+paste because I can't figure out how to + // write a function which takes a generic function. I don't think it can + // be done. + // + // Ideally, I'd want something like: + // + // > fn with_really_simple_selectors(&self, f: |&H|); + + + // In terms of `SimpleSelector`s, these two functions will insert and remove: + // - `SimpleSelector::LocalName` + // - `SimpleSelector::Namepace` + // - `SimpleSelector::ID` + // - `SimpleSelector::Class` + + /// Inserts and removes the matching `Descendant` selectors from a bloom + /// filter. This is used to speed up CSS selector matching to remove + /// unnecessary tree climbs for `Descendant` queries. + /// + /// A bloom filter of the local names, namespaces, IDs, and classes is kept. + /// Therefore, each node must have its matching selectors inserted _after_ + /// its own selector matching and _before_ its children start. + fn insert_into_bloom_filter(&self, bf: &mut BloomFilter) { + // Only elements are interesting. + if let Some(element) = self.as_element() { + bf.insert(element.get_local_name()); + bf.insert(element.get_namespace()); + element.get_id().map(|id| bf.insert(&id)); + + // TODO: case-sensitivity depends on the document type and quirks mode + element.each_class(|class| bf.insert(class)); + } + } + + /// After all the children are done css selector matching, this must be + /// called to reset the bloom filter after an `insert`. + fn remove_from_bloom_filter(&self, bf: &mut BloomFilter) { + // Only elements are interesting. + if let Some(element) = self.as_element() { + bf.remove(element.get_local_name()); + bf.remove(element.get_namespace()); + element.get_id().map(|id| bf.remove(&id)); + + // TODO: case-sensitivity depends on the document type and quirks mode + element.each_class(|class| bf.remove(class)); + } + } + + unsafe fn cascade_node(&self, + context: &SharedStyleContext, + parent: Option, + applicable_declarations: &ApplicableDeclarations, + applicable_declarations_cache: &mut ApplicableDeclarationsCache, + new_animations_sender: &Mutex>) { + // Get our parent's style. This must be unsafe so that we don't touch the parent's + // borrow flags. + // + // FIXME(pcwalton): Isolate this unsafety into the `wrapper` module to allow + // enforced safe, race-free access to the parent style. + let parent_style = match parent { + None => None, + Some(parent_node) => { + let parent_style = (*parent_node.borrow_data_unchecked().unwrap()).style.as_ref().unwrap(); + Some(parent_style) + } + }; + + if self.is_text_node() { + // Text nodes get a copy of the parent style. This ensures + // that during fragment construction any non-inherited + // CSS properties (such as vertical-align) are correctly + // set on the fragment(s). + let mut data_ref = self.mutate_data().unwrap(); + let mut data = &mut *data_ref; + let cloned_parent_style = parent_style.unwrap().clone(); + data.style = Some(cloned_parent_style); + } else { + let mut damage; + { + let mut data_ref = self.mutate_data().unwrap(); + let mut data = &mut *data_ref; + damage = self.cascade_node_pseudo_element( + context, + parent_style, + &applicable_declarations.normal, + &mut data.style, + applicable_declarations_cache, + new_animations_sender, + applicable_declarations.normal_shareable, + true); + if !applicable_declarations.before.is_empty() { + damage = damage | self.cascade_node_pseudo_element( + context, + Some(data.style.as_ref().unwrap()), + &*applicable_declarations.before, + &mut data.before_style, + applicable_declarations_cache, + new_animations_sender, + false, + false); + } + if !applicable_declarations.after.is_empty() { + damage = damage | self.cascade_node_pseudo_element( + context, + Some(data.style.as_ref().unwrap()), + &*applicable_declarations.after, + &mut data.after_style, + applicable_declarations_cache, + new_animations_sender, + false, + false); + } + } + + // This method needs to borrow the data as mutable, so make sure data_ref goes out of + // scope first. + self.set_restyle_damage(damage); + } + } +} + +impl<'ln, N: TNode<'ln>> MatchMethods<'ln> for N {} diff --git a/ports/cef/Cargo.lock b/ports/cef/Cargo.lock index 2289d09cbc4..3d008ce8a9b 100644 --- a/ports/cef/Cargo.lock +++ b/ports/cef/Cargo.lock @@ -1742,6 +1742,7 @@ dependencies = [ "smallvec 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "style_traits 0.0.1", + "time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", "url 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "util 0.0.1", ] diff --git a/ports/gonk/Cargo.lock b/ports/gonk/Cargo.lock index 2a8ae352aa6..71e3966c3bd 100644 --- a/ports/gonk/Cargo.lock +++ b/ports/gonk/Cargo.lock @@ -1708,6 +1708,7 @@ dependencies = [ "smallvec 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "style_traits 0.0.1", + "time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", "url 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "util 0.0.1", ]