servo: Merge #14175 - Separate selector matching from property cascading (from bholley:separate_cascade); r=emilio
authorBobby Holley <bobbyholley@gmail.com>
Fri, 11 Nov 2016 16:35:34 -0600
changeset 340134 c6bf11ef9bee8e2a0254e1e33bdff247d0384025
parent 340133 5581173b31c71590b3592ed95cc11a942a17f600
child 340135 42e2ed5aead91333b4af02eda1628a445137e48b
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
servo: Merge #14175 - Separate selector matching from property cascading (from bholley:separate_cascade); r=emilio This builds on @emilio's rule tree work. The goal is to raise the boundary between rule node computation and property cascading higher up the callstack, so that the new traversal architecture can decide to do one but not the other. Source-Repo: https://github.com/servo/servo Source-Revision: 3b2e3dcfb98464d24ad721a03009a1d31f54b31d
servo/components/style/matching.rs
servo/components/style/traversal.rs
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -13,17 +13,17 @@ use cache::LRUCache;
 use cascade_info::CascadeInfo;
 use context::{SharedStyleContext, StyleContext};
 use data::{ElementData, ElementStyles, PseudoStyles};
 use dom::{TElement, TNode, TRestyleDamage, UnsafeNode};
 use properties::{CascadeFlags, ComputedValues, SHAREABLE, cascade};
 use properties::longhands::display::computed_value as display;
 use rule_tree::StrongRuleNode;
 use selector_impl::{PseudoElement, RestyleDamage, TheSelectorImpl};
-use selector_matching::{ApplicableDeclarationBlock, Stylist};
+use selector_matching::ApplicableDeclarationBlock;
 use selectors::MatchAttr;
 use selectors::bloom::BloomFilter;
 use selectors::matching::{AFFECTED_BY_PSEUDO_ELEMENTS, MatchingReason, StyleRelations};
 use sink::ForgetfulSink;
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
 use std::mem;
 use std::slice::IterMut;
@@ -45,39 +45,29 @@ fn create_common_style_affecting_attribu
                     flags.insert(flag)
                 }
             }
         }
     }
     flags
 }
 
-pub struct ApplicableDeclarations {
-    pub normal: Vec<ApplicableDeclarationBlock>,
-    pub per_pseudo: HashMap<PseudoElement,
-                            Vec<ApplicableDeclarationBlock>,
-                            BuildHasherDefault<::fnv::FnvHasher>>,
-
-    /// Whether the `normal` declarations are shareable with other nodes.
-    pub normal_shareable: bool,
+type PseudoRuleNodes = HashMap<PseudoElement, StrongRuleNode,
+                               BuildHasherDefault<::fnv::FnvHasher>>;
+pub struct MatchResults {
+    pub primary: StrongRuleNode,
+    pub relations: StyleRelations,
+    pub per_pseudo: PseudoRuleNodes,
 }
 
-impl ApplicableDeclarations {
-    pub fn new() -> Self {
-        let mut applicable_declarations = ApplicableDeclarations {
-            normal: Vec::with_capacity(16),
-            per_pseudo: HashMap::with_hasher(Default::default()),
-            normal_shareable: false,
-        };
-
-        TheSelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
-            applicable_declarations.per_pseudo.insert(pseudo, vec![]);
-        });
-
-        applicable_declarations
+impl MatchResults {
+    /// Returns true if the primary rule node is shareable with other nodes.
+    pub fn primary_is_shareable(&self) -> bool {
+        use traversal::relations_are_shareable;
+        relations_are_shareable(&self.relations)
     }
 }
 
 /// Information regarding a candidate.
 ///
 /// TODO: We can stick a lot more info here.
 #[derive(Debug)]
 struct StyleSharingCandidate {
@@ -396,45 +386,40 @@ trait PrivateMatchMethods: TElement {
     /// Actually cascades style for a node or a pseudo-element of a node.
     ///
     /// Note that animations only apply to nodes or ::before or ::after
     /// pseudo-elements.
     fn cascade_node_pseudo_element<'a, Ctx>(&self,
                                             context: &Ctx,
                                             parent_style: Option<&Arc<ComputedValues>>,
                                             old_style: Option<&Arc<ComputedValues>>,
-                                            applicable_declarations: &mut Vec<ApplicableDeclarationBlock>,
+                                            rule_node: &StrongRuleNode,
                                             booleans: CascadeBooleans)
-                                            -> (Arc<ComputedValues>, StrongRuleNode)
+                                            -> Arc<ComputedValues>
         where Ctx: StyleContext<'a>
     {
         let shared_context = context.shared_context();
-        let rule_node =
-            shared_context.stylist.rule_tree
-                          .insert_ordered_rules(
-                              applicable_declarations.drain(..).map(|d| (d.source, d.importance)));
-
         let mut cascade_info = CascadeInfo::new();
         let mut cascade_flags = CascadeFlags::empty();
         if booleans.shareable {
             cascade_flags.insert(SHAREABLE)
         }
 
         let this_style = match parent_style {
             Some(ref parent_style) => {
                 cascade(shared_context.viewport_size,
-                        &rule_node,
+                        rule_node,
                         Some(&***parent_style),
                         Some(&mut cascade_info),
                         shared_context.error_reporter.clone(),
                         cascade_flags)
             }
             None => {
                 cascade(shared_context.viewport_size,
-                        &rule_node,
+                        rule_node,
                         None,
                         Some(&mut cascade_info),
                         shared_context.error_reporter.clone(),
                         cascade_flags)
             }
         };
         cascade_info.finish(&self.as_node());
 
@@ -456,17 +441,17 @@ trait PrivateMatchMethods: TElement {
                     this_opaque,
                     self.as_node().to_unsafe(),
                     &**style,
                     &mut this_style,
                     &shared_context.timer);
             }
         }
 
-        (this_style, rule_node)
+        this_style
     }
 
     fn update_animations_for_cascade(&self,
                                      context: &SharedStyleContext,
                                      style: &mut Arc<ComputedValues>) -> bool {
         // Finish any expired transitions.
         let this_opaque = self.as_node().opaque();
         let had_animations_to_expire =
@@ -511,55 +496,73 @@ trait PrivateMatchMethods: TElement {
             Self::ConcreteNode::from_unsafe(&candidate.node).as_element().unwrap()
         };
 
         element_matches_candidate(self, candidate, &candidate_element,
                                   shared_context)
     }
 }
 
+fn compute_rule_node<'a, Ctx>(context: &Ctx,
+                              applicable_declarations: &mut Vec<ApplicableDeclarationBlock>)
+                              -> StrongRuleNode
+    where Ctx: StyleContext<'a>
+{
+    let shared_context = context.shared_context();
+    let rules = applicable_declarations.drain(..).map(|d| (d.source, d.importance));
+    let rule_node = shared_context.stylist.rule_tree.insert_ordered_rules(rules);
+    rule_node
+}
+
 impl<E: TElement> PrivateMatchMethods for E {}
 
 pub trait MatchMethods : TElement {
-    fn match_element(&self,
-                     stylist: &Stylist,
-                     parent_bf: Option<&BloomFilter>,
-                     mut applicable_declarations: &mut ApplicableDeclarations)
-                     -> StyleRelations {
-        use traversal::relations_are_shareable;
-
+    fn match_element<'a, Ctx>(&self, context: &Ctx, parent_bf: Option<&BloomFilter>)
+                              -> MatchResults
+        where Ctx: StyleContext<'a>
+    {
+        let mut applicable_declarations: Vec<ApplicableDeclarationBlock> = Vec::with_capacity(16);
+        let stylist = &context.shared_context().stylist;
         let style_attribute = self.style_attribute();
 
-        let mut relations =
+        // Compute the primary rule node.
+        let mut primary_relations =
             stylist.push_applicable_declarations(self,
                                                  parent_bf,
                                                  style_attribute,
                                                  None,
-                                                 &mut applicable_declarations.normal,
+                                                 &mut applicable_declarations,
+                                                 MatchingReason::ForStyling);
+        let primary_rule_node = compute_rule_node(context, &mut applicable_declarations);
+
+        // Compute the pseudo rule nodes.
+        let mut per_pseudo: PseudoRuleNodes = HashMap::with_hasher(Default::default());
+        TheSelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
+            debug_assert!(applicable_declarations.is_empty());
+            stylist.push_applicable_declarations(self, parent_bf, None,
+                                                 Some(&pseudo.clone()),
+                                                 &mut applicable_declarations,
                                                  MatchingReason::ForStyling);
 
-        applicable_declarations.normal_shareable = relations_are_shareable(&relations);
-
-        TheSelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
-            stylist.push_applicable_declarations(self,
-                                                 parent_bf,
-                                                 None,
-                                                 Some(&pseudo.clone()),
-                                                 applicable_declarations.per_pseudo.entry(pseudo).or_insert(vec![]),
-                                                 MatchingReason::ForStyling);
+            if !applicable_declarations.is_empty() {
+                let rule_node = compute_rule_node(context, &mut applicable_declarations);
+                per_pseudo.insert(pseudo, rule_node);
+            }
         });
 
-        let has_pseudos =
-            applicable_declarations.per_pseudo.values().any(|v| !v.is_empty());
-
-        if has_pseudos {
-            relations |= AFFECTED_BY_PSEUDO_ELEMENTS;
+        // If we have any pseudo elements, indicate so in the primary StyleRelations.
+        if !per_pseudo.is_empty() {
+            primary_relations |= AFFECTED_BY_PSEUDO_ELEMENTS;
         }
 
-        relations
+        MatchResults {
+            primary: primary_rule_node,
+            relations: primary_relations,
+            per_pseudo: per_pseudo,
+        }
     }
 
     /// 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,
@@ -707,58 +710,58 @@ pub trait MatchMethods : TElement {
             }
         }
     }
 
     unsafe fn cascade_node<'a, Ctx>(&self,
                                     context: &Ctx,
                                     mut data: AtomicRefMut<ElementData>,
                                     parent: Option<Self>,
-                                    mut applicable_declarations: ApplicableDeclarations)
+                                    primary_rule_node: StrongRuleNode,
+                                    pseudo_rule_nodes: PseudoRuleNodes,
+                                    primary_is_shareable: bool)
         where Ctx: StyleContext<'a>
     {
         // Get our parent's style.
         let parent_data = parent.as_ref().map(|x| x.borrow_data().unwrap());
         let parent_style = parent_data.as_ref().map(|x| &x.current_styles().primary);
 
         let mut new_styles;
 
         let damage = {
-            let shareable = applicable_declarations.normal_shareable;
-
             let (old_primary, old_pseudos) = match data.previous_styles_mut() {
                 None => (None, None),
                 Some(previous) => {
                     // Update animations before the cascade. This may modify the
                     // value of the old primary style.
                     self.update_animations_for_cascade(context.shared_context(),
                                                        &mut previous.primary);
                     (Some(&previous.primary), Some(&mut previous.pseudos))
                 }
             };
 
-            let (new_style, rule_node) =
+            let new_style =
                 self.cascade_node_pseudo_element(context,
                                                  parent_style,
                                                  old_primary,
-                                                 &mut applicable_declarations.normal,
+                                                 &primary_rule_node,
                                                  CascadeBooleans {
-                                                     shareable: shareable,
+                                                     shareable: primary_is_shareable,
                                                      animate: true,
                                                  });
 
-            new_styles = ElementStyles::new(new_style, rule_node);
+            new_styles = ElementStyles::new(new_style, primary_rule_node);
 
             let damage =
                 self.compute_damage_and_cascade_pseudos(old_primary,
                                                         old_pseudos,
                                                         &new_styles.primary,
                                                         &mut new_styles.pseudos,
                                                         context,
-                                                        &mut applicable_declarations);
+                                                        pseudo_rule_nodes);
 
             self.as_node().set_can_be_fragmented(parent.map_or(false, |p| {
                 p.as_node().can_be_fragmented() ||
                 parent_style.unwrap().is_multicol()
             }));
 
             damage
         };
@@ -770,17 +773,17 @@ pub trait MatchMethods : TElement {
     }
 
     fn compute_damage_and_cascade_pseudos<'a, Ctx>(&self,
                                                    old_primary: Option<&Arc<ComputedValues>>,
                                                    mut old_pseudos: Option<&mut PseudoStyles>,
                                                    new_primary: &Arc<ComputedValues>,
                                                    new_pseudos: &mut PseudoStyles,
                                                    context: &Ctx,
-                                                   applicable_declarations: &mut ApplicableDeclarations)
+                                                   mut pseudo_rule_nodes: PseudoRuleNodes)
                                                    -> RestyleDamage
         where Ctx: StyleContext<'a>
     {
         // Here we optimise the case of the style changing but both the
         // previous and the new styles having display: none. In this
         // case, we can always optimize the traversal, regardless of the
         // restyle hint.
         let this_display = new_primary.get_box().clone_display();
@@ -812,42 +815,40 @@ pub trait MatchMethods : TElement {
         if new_primary.get_box().clone_display() == display::T::none {
             return damage;
         }
 
         let rebuild_and_reflow = RestyleDamage::rebuild_and_reflow();
 
         debug_assert!(new_pseudos.is_empty());
         <Self as MatchAttr>::Impl::each_eagerly_cascaded_pseudo_element(|pseudo| {
-            let mut applicable_declarations_for_this_pseudo =
-                applicable_declarations.per_pseudo.get_mut(&pseudo).unwrap();
-
-            let has_declarations =
-                !applicable_declarations_for_this_pseudo.is_empty();
+            let maybe_rule_node = pseudo_rule_nodes.remove(&pseudo);
 
             // Grab the old pseudo style for analysis.
             let mut maybe_old_pseudo_style_and_rule_node =
                 old_pseudos.as_mut().and_then(|x| x.remove(&pseudo));
 
-            if has_declarations {
+            if maybe_rule_node.is_some() {
+                let new_rule_node = maybe_rule_node.unwrap();
+
                 // We have declarations, so we need to cascade. Compute parameters.
                 let animate = <Self as MatchAttr>::Impl::pseudo_is_before_or_after(&pseudo);
                 if animate {
                     if let Some((ref mut old_pseudo_style, _)) = maybe_old_pseudo_style_and_rule_node {
                         // Update animations before the cascade. This may modify
                         // the value of old_pseudo_style.
                         self.update_animations_for_cascade(context.shared_context(),
                                                            old_pseudo_style);
                     }
                 }
 
-                let (new_pseudo_style, new_rule_node) =
+                let new_pseudo_style =
                     self.cascade_node_pseudo_element(context, Some(new_primary),
                                                      maybe_old_pseudo_style_and_rule_node.as_ref().map(|s| &s.0),
-                                                     &mut applicable_declarations_for_this_pseudo,
+                                                     &new_rule_node,
                                                      CascadeBooleans {
                                                          shareable: false,
                                                          animate: animate,
                                                      });
 
                 // Compute restyle damage unless we've already maxed it out.
                 if damage != rebuild_and_reflow {
                     damage = damage | match maybe_old_pseudo_style_and_rule_node {
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Traversing the DOM tree; the bloom filter.
 
 use atomic_refcell::{AtomicRefCell, AtomicRefMut};
 use context::{LocalStyleContext, SharedStyleContext, StyleContext};
 use data::ElementData;
 use dom::{OpaqueNode, StylingMode, TElement, TNode, UnsafeNode};
-use matching::{ApplicableDeclarations, MatchMethods, StyleSharingResult};
+use matching::{MatchMethods, StyleSharingResult};
 use selectors::bloom::BloomFilter;
 use selectors::matching::StyleRelations;
 use std::cell::RefCell;
 use std::mem;
 use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
 use tid::tid;
 use util::opts;
 
@@ -280,26 +280,24 @@ fn ensure_element_styled_internal<'a, E,
     }
 
     // Otherwise, our style might be out of date. Time to do selector matching
     // if appropriate and cascade the node.
     //
     // Note that we could add the bloom filter's complexity here, but that's
     // probably not necessary since we're likely to be matching only a few
     // nodes, at best.
-    let mut applicable_declarations = ApplicableDeclarations::new();
     let data = prepare_for_styling(element, element.get_data().unwrap());
-    let stylist = &context.shared_context().stylist;
-
-    element.match_element(&**stylist,
-                          None,
-                          &mut applicable_declarations);
-
+    let match_results = element.match_element(context, None);
     unsafe {
-        element.cascade_node(context, data, parent, applicable_declarations);
+        let shareable = match_results.primary_is_shareable();
+        element.cascade_node(context, data, parent,
+                             match_results.primary,
+                             match_results.per_pseudo,
+                             shareable);
     }
 }
 
 /// Calculates the style for a single node.
 #[inline]
 #[allow(unsafe_code)]
 pub fn recalc_style_at<'a, E, C, D>(context: &'a C,
                                     root: OpaqueNode,
@@ -325,44 +323,39 @@ pub fn recalc_style_at<'a, E, C, D>(cont
         } else {
             unsafe { element.share_style_if_possible(style_sharing_candidate_cache,
                                                      context.shared_context(), &mut data) }
         };
 
         // Otherwise, match and cascade selectors.
         match sharing_result {
             StyleSharingResult::CannotShare => {
-                let mut applicable_declarations = ApplicableDeclarations::new();
-
-                let relations;
+                let match_results;
                 let shareable_element = {
                     if opts::get().style_sharing_stats {
                         STYLE_SHARING_CACHE_MISSES.fetch_add(1, Ordering::Relaxed);
                     }
 
                     // Perform the CSS selector matching.
-                    let stylist = &context.shared_context().stylist;
-
-                    relations = element.match_element(&**stylist,
-                                                      Some(&*bf),
-                                                      &mut applicable_declarations);
-
-                    debug!("Result of selector matching: {:?}", relations);
-
-                    if relations_are_shareable(&relations) {
+                    match_results = element.match_element(context, Some(&*bf));
+                    if match_results.primary_is_shareable() {
                         Some(element)
                     } else {
                         None
                     }
                 };
+                let relations = match_results.relations;
 
                 // Perform the CSS cascade.
                 unsafe {
+                    let shareable = match_results.primary_is_shareable();
                     element.cascade_node(context, data, element.parent_element(),
-                                         applicable_declarations);
+                                         match_results.primary,
+                                         match_results.per_pseudo,
+                                         shareable);
                 }
 
                 // Add ourselves to the LRU cache.
                 if let Some(element) = shareable_element {
                     style_sharing_candidate_cache.insert_if_possible(&element,
                                                                      &element.borrow_data()
                                                                              .unwrap()
                                                                              .current_styles()