servo: Merge #16250 - Don't use a HashMap for pseudo-element styles (from bholley:pseudo_repr); r=emilio
authorBobby Holley <bobbyholley@gmail.com>
Mon, 03 Apr 2017 18:04:34 -0500
changeset 401510 39123bfbe799c9126489a7f0c9d263568897cc21
parent 401509 35dfc263aa7b2ba3f0df0df8c1f6ada8cf132854
child 401511 777453c1b91a3ec2cdb14a21122ede3fcb769dd7
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #16250 - Don't use a HashMap for pseudo-element styles (from bholley:pseudo_repr); r=emilio Reviewed in https://bugzilla.mozilla.org/show_bug.cgi?id=1335708 @bors-servo try Source-Repo: https://github.com/servo/servo Source-Revision: eee25e23132b0f5d4cb50e5af4691b6e4bf75978
servo/components/script_layout_interface/wrapper_traits.rs
servo/components/style/data.rs
servo/components/style/gecko/selector_parser.rs
servo/components/style/matching.rs
servo/components/style/selector_parser.rs
servo/components/style/servo/selector_parser.rs
servo/components/style/stylist.rs
--- a/servo/components/script_layout_interface/wrapper_traits.rs
+++ b/servo/components/script_layout_interface/wrapper_traits.rs
@@ -334,30 +334,30 @@ pub trait ThreadSafeLayoutElement: Clone
     fn get_pseudo_element_type(&self) -> PseudoElementType<Option<display::T>>;
 
     #[inline]
     fn get_before_pseudo(&self) -> Option<Self> {
         if self.get_style_data()
                .unwrap()
                .borrow()
                .styles().pseudos
-               .contains_key(&PseudoElement::Before) {
+               .has(&PseudoElement::Before) {
             Some(self.with_pseudo(PseudoElementType::Before(None)))
         } else {
             None
         }
     }
 
     #[inline]
     fn get_after_pseudo(&self) -> Option<Self> {
         if self.get_style_data()
                .unwrap()
                .borrow()
                .styles().pseudos
-               .contains_key(&PseudoElement::After) {
+               .has(&PseudoElement::After) {
             Some(self.with_pseudo(PseudoElementType::After(None)))
         } else {
             None
         }
     }
 
     #[inline]
     fn get_details_summary_pseudo(&self) -> Option<Self> {
@@ -392,57 +392,53 @@ pub trait ThreadSafeLayoutElement: Clone
     fn style(&self, context: &SharedStyleContext) -> Arc<ServoComputedValues> {
         match self.get_pseudo_element_type() {
             PseudoElementType::Normal => self.get_style_data().unwrap().borrow()
                                              .styles().primary.values().clone(),
             other => {
                 // Precompute non-eagerly-cascaded pseudo-element styles if not
                 // cached before.
                 let style_pseudo = other.style_pseudo_element();
+                let mut data = self.get_style_data().unwrap().borrow_mut();
                 match style_pseudo.cascade_type() {
                     // Already computed during the cascade.
-                    PseudoElementCascadeType::Eager => {},
+                    PseudoElementCascadeType::Eager => {
+                        data.styles().pseudos.get(&style_pseudo)
+                            .unwrap().values().clone()
+                    },
                     PseudoElementCascadeType::Precomputed => {
-                        if !self.get_style_data()
-                                .unwrap()
-                                .borrow()
-                                .styles().pseudos.contains_key(&style_pseudo) {
-                            let mut data = self.get_style_data().unwrap().borrow_mut();
+                        if !data.styles().cached_pseudos.contains_key(&style_pseudo) {
                             let new_style =
                                 context.stylist.precomputed_values_for_pseudo(
                                     &context.guards,
                                     &style_pseudo,
                                     Some(data.styles().primary.values()),
                                     CascadeFlags::empty());
-                            data.styles_mut().pseudos
+                            data.styles_mut().cached_pseudos
                                 .insert(style_pseudo.clone(), new_style);
                         }
+                        data.styles().cached_pseudos.get(&style_pseudo)
+                            .unwrap().values().clone()
                     }
                     PseudoElementCascadeType::Lazy => {
-                        if !self.get_style_data()
-                                .unwrap()
-                                .borrow()
-                                .styles().pseudos.contains_key(&style_pseudo) {
-                            let mut data = self.get_style_data().unwrap().borrow_mut();
+                        if !data.styles().cached_pseudos.contains_key(&style_pseudo) {
                             let new_style =
                                 context.stylist
                                        .lazily_compute_pseudo_element_style(
                                            &context.guards,
                                            unsafe { &self.unsafe_get() },
                                            &style_pseudo,
                                            data.styles().primary.values());
-                            data.styles_mut().pseudos
+                            data.styles_mut().cached_pseudos
                                 .insert(style_pseudo.clone(), new_style.unwrap());
                         }
+                        data.styles().cached_pseudos.get(&style_pseudo)
+                            .unwrap().values().clone()
                     }
                 }
-
-                self.get_style_data().unwrap().borrow()
-                    .styles().pseudos.get(&style_pseudo)
-                    .unwrap().values().clone()
             }
         }
     }
 
     #[inline]
     fn selected_style(&self) -> Arc<ServoComputedValues> {
         let data = self.get_style_data().unwrap().borrow();
         data.styles().pseudos
--- a/servo/components/style/data.rs
+++ b/servo/components/style/data.rs
@@ -6,21 +6,21 @@
 
 #![deny(missing_docs)]
 
 use dom::TElement;
 use properties::ComputedValues;
 use properties::longhands::display::computed_value as display;
 use restyle_hints::{RESTYLE_CSS_ANIMATIONS, RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint};
 use rule_tree::StrongRuleNode;
-use selector_parser::{PseudoElement, RestyleDamage, Snapshot};
-use std::collections::HashMap;
+use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, RestyleDamage, Snapshot};
+#[cfg(feature = "servo")] use std::collections::HashMap;
 use std::fmt;
-use std::hash::BuildHasherDefault;
-use std::ops::{Deref, DerefMut};
+#[cfg(feature = "servo")] use std::hash::BuildHasherDefault;
+use std::ops::Deref;
 use std::sync::Arc;
 use stylist::Stylist;
 use thread_state;
 
 /// The structure that represents the result of style computation. This is
 /// effectively a tuple of rules and computed values, that is, the rule node,
 /// and the result of computing that rule node's rules, the `ComputedValues`.
 #[derive(Clone)]
@@ -68,60 +68,117 @@ impl ComputedStyle {
 // We manually implement Debug for ComputedStyle so that we can avoid the
 // verbose stringification of ComputedValues for normal logging.
 impl fmt::Debug for ComputedStyle {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(f, "ComputedStyle {{ rules: {:?}, values: {{..}} }}", self.rules)
     }
 }
 
-type PseudoStylesInner = HashMap<PseudoElement, ComputedStyle,
-                                 BuildHasherDefault<::fnv::FnvHasher>>;
+/// A list of styles for eagerly-cascaded pseudo-elements. Lazily-allocated.
+#[derive(Clone, Debug)]
+pub struct EagerPseudoStyles(Option<Box<[Option<ComputedStyle>]>>);
+
+impl EagerPseudoStyles {
+    /// Returns whether there are any pseudo styles.
+    pub fn is_empty(&self) -> bool {
+        self.0.is_some()
+    }
+
+    /// Returns a reference to the style for a given eager pseudo, if it exists.
+    pub fn get(&self, pseudo: &PseudoElement) -> Option<&ComputedStyle> {
+        debug_assert!(pseudo.is_eager());
+        self.0.as_ref().and_then(|p| p[pseudo.eager_index()].as_ref())
+    }
+
+    /// Returns a mutable reference to the style for a given eager pseudo, if it exists.
+    pub fn get_mut(&mut self, pseudo: &PseudoElement) -> Option<&mut ComputedStyle> {
+        debug_assert!(pseudo.is_eager());
+        self.0.as_mut().and_then(|p| p[pseudo.eager_index()].as_mut())
+    }
+
+    /// Returns true if the EagerPseudoStyles has a ComputedStyle for |pseudo|.
+    pub fn has(&self, pseudo: &PseudoElement) -> bool {
+        self.get(pseudo).is_some()
+    }
+
+    /// Inserts a pseudo-element. The pseudo-element must not already exist.
+    pub fn insert(&mut self, pseudo: &PseudoElement, style: ComputedStyle) {
+        debug_assert!(!self.has(pseudo));
+        if self.0.is_none() {
+            self.0 = Some(vec![None; EAGER_PSEUDO_COUNT].into_boxed_slice());
+        }
+        self.0.as_mut().unwrap()[pseudo.eager_index()] = Some(style);
+    }
 
-/// A set of styles for a given element's pseudo-elements.
-///
-/// This is a map from pseudo-element to `ComputedStyle`.
-///
-/// TODO(emilio): This should probably be a small array by default instead of a
-/// full-blown `HashMap`.
-#[derive(Clone, Debug)]
-pub struct PseudoStyles(PseudoStylesInner);
+    /// Removes a pseudo-element style if it exists, and returns it.
+    pub fn take(&mut self, pseudo: &PseudoElement) -> Option<ComputedStyle> {
+        let result = match self.0.as_mut() {
+            None => return None,
+            Some(arr) => arr[pseudo.eager_index()].take(),
+        };
+        let empty = self.0.as_ref().unwrap().iter().all(|x| x.is_none());
+        if empty {
+            self.0 = None;
+        }
+        result
+    }
 
-impl PseudoStyles {
-    /// Construct an empty set of `PseudoStyles`.
-    pub fn empty() -> Self {
-        PseudoStyles(HashMap::with_hasher(Default::default()))
+    /// Returns a list of the pseudo-elements.
+    pub fn keys(&self) -> Vec<PseudoElement> {
+        let mut v = Vec::new();
+        if let Some(ref arr) = self.0 {
+            for i in 0..EAGER_PSEUDO_COUNT {
+                if arr[i].is_some() {
+                    v.push(PseudoElement::from_eager_index(i));
+                }
+            }
+        }
+        v
+    }
+
+    /// Sets the rule node for a given pseudo-element, which must already have an entry.
+    ///
+    /// Returns true if the rule node changed.
+    pub fn set_rules(&mut self, pseudo: &PseudoElement, rules: StrongRuleNode) -> bool {
+        debug_assert!(self.has(pseudo));
+        let mut style = self.get_mut(pseudo).unwrap();
+        let changed = style.rules != rules;
+        style.rules = rules;
+        changed
     }
 }
 
-impl Deref for PseudoStyles {
-    type Target = PseudoStylesInner;
-    fn deref(&self) -> &Self::Target { &self.0 }
-}
-
-impl DerefMut for PseudoStyles {
-    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
-}
+/// A cache of precomputed and lazy pseudo-elements, used by servo. This isn't
+/// a very efficient design, but is the result of servo having previously used
+/// the eager pseudo map (when it was a map) for this cache.
+#[cfg(feature = "servo")]
+type PseudoElementCache = HashMap<PseudoElement, ComputedStyle, BuildHasherDefault<::fnv::FnvHasher>>;
+#[cfg(feature = "gecko")]
+type PseudoElementCache = ();
 
 /// The styles associated with a node, including the styles for any
 /// pseudo-elements.
 #[derive(Clone, Debug)]
 pub struct ElementStyles {
     /// The element's style.
     pub primary: ComputedStyle,
-    /// The map of styles for the element's pseudos.
-    pub pseudos: PseudoStyles,
+    /// A list of the styles for the element's eagerly-cascaded pseudo-elements.
+    pub pseudos: EagerPseudoStyles,
+    /// NB: This is an empty field for gecko.
+    pub cached_pseudos: PseudoElementCache,
 }
 
 impl ElementStyles {
     /// Trivially construct a new `ElementStyles`.
     pub fn new(primary: ComputedStyle) -> Self {
         ElementStyles {
             primary: primary,
-            pseudos: PseudoStyles::empty(),
+            pseudos: EagerPseudoStyles(None),
+            cached_pseudos: PseudoElementCache::default(),
         }
     }
 
     /// Whether this element `display` value is `none`.
     pub fn is_display_none(&self) -> bool {
         self.primary.values().get_box().clone_display() == display::T::none
     }
 }
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -37,29 +37,90 @@ use string_cache::{Atom, Namespace, Weak
 /// This should allow us to avoid random FFI overhead when cloning/dropping
 /// pseudos.
 ///
 /// Also, we can further optimize PartialEq and hash comparing/hashing only the
 /// atoms.
 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
 pub struct PseudoElement(Atom, bool);
 
+/// List of eager pseudos. Keep this in sync with the count below.
+macro_rules! each_eager_pseudo {
+    ($macro_name:ident, $atom_macro:ident) => {
+        $macro_name!($atom_macro!(":after"), 0);
+        $macro_name!($atom_macro!(":before"), 1);
+    }
+}
+
+/// The number of eager pseudo-elements (just ::before and ::after).
+pub const EAGER_PSEUDO_COUNT: usize = 2;
+
+
 impl PseudoElement {
+    /// Gets the canonical index of this eagerly-cascaded pseudo-element.
+    #[inline]
+    pub fn eager_index(&self) -> usize {
+        macro_rules! case {
+            ($atom:expr, $idx:expr) => { if *self.as_atom() == $atom { return $idx; } }
+        }
+        each_eager_pseudo!(case, atom);
+        panic!("Not eager")
+    }
+
+    /// Creates a pseudo-element from an eager index.
+    #[inline]
+    pub fn from_eager_index(i: usize) -> Self {
+        macro_rules! case {
+            ($atom:expr, $idx:expr) => { if i == $idx { return PseudoElement($atom, false); } }
+        }
+        each_eager_pseudo!(case, atom);
+        panic!("Not eager")
+    }
+
     /// Get the pseudo-element as an atom.
     #[inline]
     pub fn as_atom(&self) -> &Atom {
         &self.0
     }
 
     /// Whether this pseudo-element is an anonymous box.
     #[inline]
     fn is_anon_box(&self) -> bool {
         self.1
     }
 
+    /// Whether this pseudo-element is ::before or ::after.
+    #[inline]
+    pub fn is_before_or_after(&self) -> bool {
+        *self.as_atom() == atom!(":before") ||
+        *self.as_atom() == atom!(":after")
+    }
+
+    /// Whether this pseudo-element is eagerly-cascaded.
+    #[inline]
+    pub fn is_eager(&self) -> bool {
+        macro_rules! case {
+            ($atom:expr, $idx:expr) => { if *self.as_atom() == $atom { return true; } }
+        }
+        each_eager_pseudo!(case, atom);
+        return false;
+    }
+
+    /// Whether this pseudo-element is lazily-cascaded.
+    #[inline]
+    pub fn is_lazy(&self) -> bool {
+        !self.is_eager() && !self.is_precomputed()
+    }
+
+    /// Whether this pseudo-element is precomputed.
+    #[inline]
+    pub fn is_precomputed(&self) -> bool {
+        self.is_anon_box()
+    }
+
     /// Construct a pseudo-element from an `Atom`, receiving whether it is also
     /// an anonymous box, and don't check it on release builds.
     ///
     /// On debug builds we assert it's the result we expect.
     #[inline]
     pub fn from_atom_unchecked(atom: Atom, is_anon_box: bool) -> Self {
         if cfg!(debug_assertions) {
             // Do the check on debug regardless.
@@ -393,47 +454,54 @@ impl SelectorImpl {
     /// Returns the kind of cascade type that a given pseudo is going to use.
     ///
     /// In Gecko we only compute ::before and ::after eagerly. We save the rules
     /// for anonymous boxes separately, so we resolve them as precomputed
     /// pseudos.
     ///
     /// We resolve the others lazily, see `Servo_ResolvePseudoStyle`.
     pub fn pseudo_element_cascade_type(pseudo: &PseudoElement) -> PseudoElementCascadeType {
-        if Self::pseudo_is_before_or_after(pseudo) {
+        if pseudo.is_eager() {
+            debug_assert!(!pseudo.is_anon_box());
             return PseudoElementCascadeType::Eager
         }
 
         if pseudo.is_anon_box() {
             return PseudoElementCascadeType::Precomputed
         }
 
         PseudoElementCascadeType::Lazy
     }
 
+    /// A helper to traverse each eagerly cascaded pseudo-element, executing
+    /// `fun` on it.
+    #[inline]
+    pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
+        where F: FnMut(PseudoElement),
+    {
+        macro_rules! case {
+            ($atom:expr, $idx:expr) => { fun(PseudoElement($atom, false)); }
+        }
+        each_eager_pseudo!(case, atom);
+    }
+
+
     #[inline]
     /// Executes a function for each pseudo-element.
     pub fn each_pseudo_element<F>(mut fun: F)
         where F: FnMut(PseudoElement),
     {
         macro_rules! pseudo_element {
             ($pseudo_str_with_colon:expr, $atom:expr, $is_anon_box:expr) => {{
                 fun(PseudoElement($atom, $is_anon_box));
             }}
         }
 
         include!("generated/gecko_pseudo_element_helper.rs")
     }
 
     #[inline]
-    /// Returns whether the given pseudo-element is `::before` or `::after`.
-    pub fn pseudo_is_before_or_after(pseudo: &PseudoElement) -> bool {
-        *pseudo.as_atom() == atom!(":before") ||
-        *pseudo.as_atom() == atom!(":after")
-    }
-
-    #[inline]
     /// Returns the relevant state flag for a given non-tree-structural
     /// pseudo-class.
     pub fn pseudo_class_state_flag(pc: &NonTSPseudoClass) -> ElementState {
         pc.state_flag()
     }
 }
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -15,23 +15,21 @@ use cascade_info::CascadeInfo;
 use context::{SequentialTask, SharedStyleContext, StyleContext};
 use data::{ComputedStyle, ElementData, ElementStyles, RestyleData};
 use dom::{AnimationRules, SendElement, TElement, TNode};
 use properties::{CascadeFlags, ComputedValues, SHAREABLE, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade};
 use properties::longhands::display::computed_value as display;
 use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RESTYLE_CSS_ANIMATIONS, RestyleHint};
 use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode};
 use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
-use selectors::MatchAttr;
 use selectors::bloom::BloomFilter;
 use selectors::matching::{ElementSelectorFlags, StyleRelations};
 use selectors::matching::AFFECTED_BY_PSEUDO_ELEMENTS;
 use servo_config::opts;
 use sink::ForgetfulSink;
-use std::collections::hash_map::Entry;
 use std::sync::Arc;
 use stylist::ApplicableDeclarationBlock;
 
 /// Determines the amount of relations where we're going to share style.
 #[inline]
 fn relations_are_shareable(relations: &StyleRelations) -> bool {
     use selectors::matching::*;
     !relations.intersects(AFFECTED_BY_ID_SELECTOR |
@@ -898,62 +896,56 @@ pub trait MatchMethods : TElement {
         } else if data.styles().primary.rules != primary_rule_node {
             data.styles_mut().primary.rules = primary_rule_node;
             rule_nodes_changed = true;
         }
 
         // Compute rule nodes for eagerly-cascaded pseudo-elements.
         let mut matches_different_pseudos = false;
         SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
-            let mut per_pseudo = &mut data.styles_mut().pseudos;
+            let mut pseudos = &mut data.styles_mut().pseudos;
             debug_assert!(applicable_declarations.is_empty());
-            let pseudo_animation_rules = if <Self as MatchAttr>::Impl::pseudo_is_before_or_after(&pseudo) {
+            let pseudo_animation_rules = if pseudo.is_before_or_after() {
                 self.get_animation_rules(Some(&pseudo))
             } else {
                 AnimationRules(None, None)
             };
             stylist.push_applicable_declarations(self,
                                                  Some(bloom_filter),
                                                  None, pseudo_animation_rules,
                                                  Some(&pseudo),
                                                  &guards,
                                                  &mut applicable_declarations,
                                                  &mut set_selector_flags);
 
             if !applicable_declarations.is_empty() {
                 let new_rules =
                     compute_rule_node::<Self>(rule_tree, &mut applicable_declarations);
-                match per_pseudo.entry(pseudo) {
-                    Entry::Occupied(mut e) => {
-                        if e.get().rules != new_rules {
-                            e.get_mut().rules = new_rules;
-                            rule_nodes_changed = true;
-                        }
-                    },
-                    Entry::Vacant(e) => {
-                        e.insert(ComputedStyle::new_partial(new_rules));
-                        matches_different_pseudos = true;
-                    }
+                if pseudos.has(&pseudo) {
+                    rule_nodes_changed = pseudos.set_rules(&pseudo, new_rules);
+                } else {
+                    pseudos.insert(&pseudo, ComputedStyle::new_partial(new_rules));
+                    matches_different_pseudos = true;
                 }
-            } else if per_pseudo.remove(&pseudo).is_some() {
+            } else if pseudos.take(&pseudo).is_some() {
                 matches_different_pseudos = true;
             }
         });
 
         if matches_different_pseudos {
             rule_nodes_changed = true;
             if let Some(r) = data.get_restyle_mut() {
                 // Any changes to the matched pseudo-elements trigger
                 // reconstruction.
                 r.damage |= RestyleDamage::reconstruct();
             }
         }
 
         // If we have any pseudo elements, indicate so in the primary StyleRelations.
-        if !data.styles().pseudos.is_empty() {
+        if data.styles().pseudos.is_empty() {
             primary_relations |= AFFECTED_BY_PSEUDO_ELEMENTS;
         }
 
         MatchResults {
             primary_relations: Some(primary_relations),
             rule_nodes_changed: rule_nodes_changed,
         }
     }
@@ -990,21 +982,20 @@ pub trait MatchMethods : TElement {
             if hint.contains(RESTYLE_CSS_ANIMATIONS) {
                 debug_assert!(context.shared.animation_only_restyle);
 
                 let animation_rule = self.get_animation_rule(None);
                 replace_rule_node(CascadeLevel::Animations,
                                   animation_rule.as_ref(),
                                   primary_rules);
 
-                let iter = element_styles.pseudos.iter_mut().filter(|&(p, _)|
-                    <Self as MatchAttr>::Impl::pseudo_is_before_or_after(p));
-                for (pseudo, ref mut computed) in iter {
-                    let animation_rule = self.get_animation_rule(Some(pseudo));
-                    let pseudo_rules = &mut computed.rules;
+                let pseudos = &mut element_styles.pseudos;
+                for pseudo in pseudos.keys().iter().filter(|p| p.is_before_or_after()) {
+                    let animation_rule = self.get_animation_rule(Some(&pseudo));
+                    let pseudo_rules = &mut pseudos.get_mut(&pseudo).unwrap().rules;
                     replace_rule_node(CascadeLevel::Animations,
                                       animation_rule.as_ref(),
                                       pseudo_rules);
                 }
             } else if hint.contains(RESTYLE_STYLE_ATTRIBUTE) {
                 let style_attribute = self.style_attribute();
                 replace_rule_node(CascadeLevel::StyleAttributeNormal,
                                   style_attribute,
@@ -1193,31 +1184,30 @@ pub trait MatchMethods : TElement {
         // Check whether the primary style is display:none.
         let display_none = data.styles().primary.values().get_box().clone_display() ==
                            display::T::none;
 
         // Cascade each pseudo-element.
         //
         // Note that we've already set up the map of matching pseudo-elements
         // in match_element (and handled the damage implications of changing
-        // which pseudos match), so now we can just iterate the map. This does
-        // mean collecting the keys, so that the borrow checker will let us pass
-        // the mutable |data| to the inner cascade function.
-        let matched_pseudos: Vec<PseudoElement> =
-            data.styles().pseudos.keys().cloned().collect();
+        // which pseudos match), so now we can just iterate what we have. This
+        // does mean collecting owned pseudos, so that the borrow checker will
+        // let us pass the mutable |data| to the inner cascade function.
+        let matched_pseudos = data.styles().pseudos.keys();
         for pseudo in matched_pseudos {
             // If the new primary style is display:none, we don't need pseudo
             // styles, but we still need to clear any stale values.
             if display_none {
                 data.styles_mut().pseudos.get_mut(&pseudo).unwrap().values = None;
                 continue;
             }
 
             // Only ::before and ::after are animatable.
-            let animate = <Self as MatchAttr>::Impl::pseudo_is_before_or_after(&pseudo);
+            let animate = pseudo.is_before_or_after();
             self.cascade_primary_or_pseudo(context, data, Some(&pseudo),
                                            &mut possibly_expired_animations,
                                            CascadeBooleans {
                                                shareable: false,
                                                animate: animate,
                                            });
         }
     }
--- a/servo/components/style/selector_parser.rs
+++ b/servo/components/style/selector_parser.rs
@@ -97,75 +97,39 @@ pub enum PseudoElementCascadeType {
     /// an optimisation since they are private pseudo-elements (like
     /// `::-servo-details-content`).
     ///
     /// This pseudo-elements are resolved on the fly using *only* global rules
     /// (rules of the form `*|*`), and applying them to the parent style.
     Precomputed,
 }
 
-impl PseudoElementCascadeType {
-    /// Simple accessor to check whether the cascade type is eager.
-    #[inline]
-    pub fn is_eager(&self) -> bool {
-        *self == PseudoElementCascadeType::Eager
-    }
-
-    /// Simple accessor to check whether the cascade type is lazy.
-    #[inline]
-    pub fn is_lazy(&self) -> bool {
-        *self == PseudoElementCascadeType::Lazy
-    }
-
-    /// Simple accessor to check whether the cascade type is precomputed.
-    #[inline]
-    pub fn is_precomputed(&self) -> bool {
-        *self == PseudoElementCascadeType::Precomputed
-    }
-}
-
 /// An extension to rust-selector's `Element` trait.
 pub trait ElementExt: Element<Impl=SelectorImpl> + Debug {
     /// Whether this element is a `link`.
     fn is_link(&self) -> bool;
 
     /// Whether this element should match user and author rules.
     ///
     /// We use this for Native Anonymous Content in Gecko.
     fn matches_user_and_author_rules(&self) -> bool;
 }
 
 impl SelectorImpl {
-    /// A helper to traverse each eagerly cascaded pseudo-element, executing
-    /// `fun` on it.
-    ///
-    /// TODO(emilio): We can optimize this for Gecko using the pseudo-element
-    /// macro, and we should consider doing that for Servo too.
-    #[inline]
-    pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
-        where F: FnMut(PseudoElement),
-    {
-        Self::each_pseudo_element(|pseudo| {
-            if Self::pseudo_element_cascade_type(&pseudo).is_eager() {
-                fun(pseudo)
-            }
-        })
-    }
-
     /// A helper to traverse each precomputed pseudo-element, executing `fun` on
     /// it.
     ///
     /// The optimization comment in `each_eagerly_cascaded_pseudo_element` also
     /// applies here.
     #[inline]
     pub fn each_precomputed_pseudo_element<F>(mut fun: F)
         where F: FnMut(PseudoElement),
     {
         Self::each_pseudo_element(|pseudo| {
-            if Self::pseudo_element_cascade_type(&pseudo).is_precomputed() {
+            if pseudo.is_precomputed() {
                 fun(pseudo)
             }
         })
     }
 }
 
 /// Checks whether we can share style if an element matches a given
 /// attribute-selector that checks for existence (`[attr_name]`) easily.
--- a/servo/components/style/servo/selector_parser.rs
+++ b/servo/components/style/servo/selector_parser.rs
@@ -14,27 +14,31 @@ use restyle_hints::ElementSnapshot;
 use selector_parser::{ElementExt, PseudoElementCascadeType, SelectorParser};
 use selector_parser::{attr_equals_selector_is_shareable, attr_exists_selector_is_shareable};
 use selectors::{Element, MatchAttrGeneric};
 use selectors::matching::StyleRelations;
 use selectors::parser::{AttrSelector, SelectorMethods};
 use std::borrow::Cow;
 use std::fmt;
 use std::fmt::Debug;
+use std::mem;
 
 /// A pseudo-element, both public and private.
 ///
 /// NB: If you add to this list, be sure to update `each_pseudo_element` too.
 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
+#[repr(usize)]
 pub enum PseudoElement {
+    // Eager pseudos. Keep these first so that eager_index() works.
+    After = 0,
     Before,
-    After,
     Selection,
+    // Non-eager pseudos.
     DetailsSummary,
     DetailsContent,
     ServoText,
     ServoInputText,
     ServoTableWrapper,
     ServoAnonymousTableWrapper,
     ServoAnonymousTable,
     ServoAnonymousTableRow,
@@ -43,18 +47,18 @@ pub enum PseudoElement {
     ServoInlineBlockWrapper,
     ServoInlineAbsolute,
 }
 
 impl ToCss for PseudoElement {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         use self::PseudoElement::*;
         dest.write_str(match *self {
+            After => "::after",
             Before => "::before",
-            After => "::after",
             Selection => "::selection",
             DetailsSummary => "::-servo-details-summary",
             DetailsContent => "::-servo-details-content",
             ServoText => "::-servo-text",
             ServoInputText => "::-servo-input-text",
             ServoTableWrapper => "::-servo-table-wrapper",
             ServoAnonymousTableWrapper => "::-servo-anonymous-table-wrapper",
             ServoAnonymousTable => "::-servo-anonymous-table",
@@ -62,35 +66,70 @@ impl ToCss for PseudoElement {
             ServoAnonymousTableCell => "::-servo-anonymous-table-cell",
             ServoAnonymousBlock => "::-servo-anonymous-block",
             ServoInlineBlockWrapper => "::-servo-inline-block-wrapper",
             ServoInlineAbsolute => "::-servo-inline-absolute",
         })
     }
 }
 
+/// The number of eager pseudo-elements. Keep this in sync with cascade_type.
+pub const EAGER_PSEUDO_COUNT: usize = 3;
+
 impl PseudoElement {
+    /// Gets the canonical index of this eagerly-cascaded pseudo-element.
+    #[inline]
+    pub fn eager_index(&self) -> usize {
+        debug_assert!(self.is_eager());
+        self.clone() as usize
+    }
+
+    /// Creates a pseudo-element from an eager index.
+    #[inline]
+    pub fn from_eager_index(i: usize) -> Self {
+        assert!(i < EAGER_PSEUDO_COUNT);
+        let result: PseudoElement = unsafe { mem::transmute(i) };
+        debug_assert!(result.is_eager());
+        result
+    }
+
     /// Whether the current pseudo element is :before or :after.
     #[inline]
     pub fn is_before_or_after(&self) -> bool {
-        match *self {
-            PseudoElement::Before |
-            PseudoElement::After => true,
-            _ => false,
-        }
+        matches!(*self, PseudoElement::After | PseudoElement::Before)
+    }
+
+    /// Whether this pseudo-element is eagerly-cascaded.
+    #[inline]
+    pub fn is_eager(&self) -> bool {
+        self.cascade_type() == PseudoElementCascadeType::Eager
+    }
+
+    /// Whether this pseudo-element is lazily-cascaded.
+    #[inline]
+    pub fn is_lazy(&self) -> bool {
+        self.cascade_type() == PseudoElementCascadeType::Lazy
+    }
+
+    /// Whether this pseudo-element is precomputed.
+    #[inline]
+    pub fn is_precomputed(&self) -> bool {
+        self.cascade_type() == PseudoElementCascadeType::Precomputed
     }
 
     /// Returns which kind of cascade type has this pseudo.
     ///
     /// For more info on cascade types, see docs/components/style.md
+    ///
+    /// Note: Keep this in sync with EAGER_PSEUDO_COUNT.
     #[inline]
     pub fn cascade_type(&self) -> PseudoElementCascadeType {
         match *self {
+            PseudoElement::After |
             PseudoElement::Before |
-            PseudoElement::After |
             PseudoElement::Selection => PseudoElementCascadeType::Eager,
             PseudoElement::DetailsSummary => PseudoElementCascadeType::Lazy,
             PseudoElement::DetailsContent |
             PseudoElement::ServoText |
             PseudoElement::ServoInputText |
             PseudoElement::ServoTableWrapper |
             PseudoElement::ServoAnonymousTableWrapper |
             PseudoElement::ServoAnonymousTable |
@@ -364,16 +403,27 @@ impl<'a> ::selectors::Parser for Selecto
 
 impl SelectorImpl {
     /// Returns the pseudo-element cascade type of the given `pseudo`.
     #[inline]
     pub fn pseudo_element_cascade_type(pseudo: &PseudoElement) -> PseudoElementCascadeType {
         pseudo.cascade_type()
     }
 
+    /// A helper to traverse each eagerly cascaded pseudo-element, executing
+    /// `fun` on it.
+    #[inline]
+    pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
+        where F: FnMut(PseudoElement),
+    {
+        for i in 0..EAGER_PSEUDO_COUNT {
+            fun(PseudoElement::from_eager_index(i));
+        }
+    }
+
     /// Executes `fun` for each pseudo-element.
     #[inline]
     pub fn each_pseudo_element<F>(mut fun: F)
         where F: FnMut(PseudoElement),
     {
         fun(PseudoElement::Before);
         fun(PseudoElement::After);
         fun(PseudoElement::DetailsContent);
@@ -391,22 +441,16 @@ impl SelectorImpl {
         fun(PseudoElement::ServoInlineAbsolute);
     }
 
     /// Returns the pseudo-class state flag for selector matching.
     #[inline]
     pub fn pseudo_class_state_flag(pc: &NonTSPseudoClass) -> ElementState {
         pc.state_flag()
     }
-
-    /// Returns whether this pseudo is either :before or :after.
-    #[inline]
-    pub fn pseudo_is_before_or_after(pseudo: &PseudoElement) -> bool {
-        pseudo.is_before_or_after()
-    }
 }
 
 /// Servo's version of an element snapshot.
 #[derive(Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct ServoElementSnapshot {
     /// The stored state of the element.
     pub state: Option<ElementState>,
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -340,17 +340,17 @@ impl Stylist {
     /// values. The flow constructor uses this flag when constructing anonymous
     /// flows.
     pub fn precomputed_values_for_pseudo(&self,
                                          guards: &StylesheetGuards,
                                          pseudo: &PseudoElement,
                                          parent: Option<&Arc<ComputedValues>>,
                                          cascade_flags: CascadeFlags)
                                          -> ComputedStyle {
-        debug_assert!(SelectorImpl::pseudo_element_cascade_type(pseudo).is_precomputed());
+        debug_assert!(pseudo.is_precomputed());
 
         let rule_node = match self.precomputed_pseudo_element_decls.get(pseudo) {
             Some(declarations) => {
                 // FIXME(emilio): When we've taken rid of the cascade we can just
                 // use into_iter.
                 self.rule_tree.insert_ordered_rules(
                     declarations.into_iter().map(|a| (a.source.clone(), a.level)))
             }
@@ -430,17 +430,17 @@ impl Stylist {
                                                   element: &E,
                                                   pseudo: &PseudoElement,
                                                   parent: &Arc<ComputedValues>)
                                                   -> Option<ComputedStyle>
         where E: TElement +
                  fmt::Debug +
                  PresentationalHintsSynthetizer
     {
-        debug_assert!(SelectorImpl::pseudo_element_cascade_type(pseudo).is_lazy());
+        debug_assert!(pseudo.is_lazy());
         if self.pseudos_map.get(pseudo).is_none() {
             return None;
         }
 
         let mut declarations = vec![];
 
         // Apply the selector flags. We should be in sequential mode
         // already, so we can directly apply the parent flags.
@@ -598,19 +598,17 @@ impl Stylist {
                  fmt::Debug +
                  PresentationalHintsSynthetizer,
               V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock>,
               F: FnMut(&E, ElementSelectorFlags),
     {
         debug_assert!(!self.is_device_dirty);
         debug_assert!(style_attribute.is_none() || pseudo_element.is_none(),
                       "Style attributes do not apply to pseudo-elements");
-        debug_assert!(pseudo_element.is_none() ||
-                      !SelectorImpl::pseudo_element_cascade_type(pseudo_element.as_ref().unwrap())
-                        .is_precomputed());
+        debug_assert!(pseudo_element.as_ref().map_or(true, |p| !p.is_precomputed()));
 
         let map = match pseudo_element {
             Some(ref pseudo) => self.pseudos_map.get(pseudo).unwrap(),
             None => &self.element_map,
         };
 
         let mut relations = StyleRelations::empty();