Bug 1379505: Rewrite restyling to split between resolving styles and handling changes. draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Mon, 10 Jul 2017 03:23:41 +0200
changeset 606876 5ad55b20512a7c601b06f5da755e5586decbc2e8
parent 606875 7c1e3a43c05be81a599f7732210f5094c43862d3
child 606877 667d0c4d755b5821f74e41918f459a27b5e9e8a4
push id67817
push userbmo:emilio+bugs@crisal.io
push dateTue, 11 Jul 2017 14:32:38 +0000
bugs1379505
milestone56.0a1
Bug 1379505: Rewrite restyling to split between resolving styles and handling changes. MozReview-Commit-ID: 4BzjbLbFebF
servo/components/style/context.rs
servo/components/style/matching.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/sharing/mod.rs
servo/components/style/style_resolver.rs
servo/components/style/stylist.rs
servo/components/style/traversal.rs
servo/ports/geckolib/glue.rs
--- a/servo/components/style/context.rs
+++ b/servo/components/style/context.rs
@@ -2,30 +2,29 @@
  * 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/. */
 
 //! The context within which style is calculated.
 
 #[cfg(feature = "servo")] use animation::Animation;
 use animation::PropertyAnimation;
 use app_units::Au;
-use arrayvec::ArrayVec;
 use bloom::StyleBloom;
 use cache::LRUCache;
 use data::{EagerPseudoStyles, ElementData};
 use dom::{OpaqueNode, TNode, TElement, SendElement};
 use euclid::Size2D;
 use fnv::FnvHashMap;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")] use gecko_bindings::structs;
 #[cfg(feature = "servo")] use parking_lot::RwLock;
 use properties::ComputedValues;
 use rule_tree::StrongRuleNode;
-use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, SnapshotMap};
-use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode};
+use selector_parser::{EAGER_PSEUDO_COUNT, SnapshotMap};
+use selectors::matching::ElementSelectorFlags;
 use shared_lock::StylesheetGuards;
 use sharing::{ValidationData, StyleSharingCandidateCache};
 use std::fmt;
 use std::ops::Add;
 #[cfg(feature = "servo")] use std::sync::Mutex;
 #[cfg(feature = "servo")] use std::sync::mpsc::Sender;
 use stylearc::Arc;
 use stylist::Stylist;
@@ -157,173 +156,37 @@ impl<'a> SharedStyleContext<'a> {
 
 /// The structure holds various intermediate inputs that are eventually used by
 /// by the cascade.
 ///
 /// The matching and cascading process stores them in this format temporarily
 /// within the `CurrentElementInfo`. At the end of the cascade, they are folded
 /// down into the main `ComputedValues` to reduce memory usage per element while
 /// still remaining accessible.
-#[derive(Clone)]
+#[derive(Clone, Default)]
 pub struct CascadeInputs {
     /// The rule node representing the ordered list of rules matched for this
     /// node.
-    rules: Option<StrongRuleNode>,
+    pub rules: Option<StrongRuleNode>,
 
     /// The rule node representing the ordered list of rules matched for this
     /// node if visited, only computed if there's a relevant link for this
     /// element. A element's "relevant link" is the element being matched if it
     /// is a link or the nearest ancestor link.
-    visited_rules: Option<StrongRuleNode>,
-
-    /// The element's computed values if visited, only computed if there's a
-    /// relevant link for this element. A element's "relevant link" is the
-    /// element being matched if it is a link or the nearest ancestor link.
-    ///
-    /// We also store a reference to this inside the regular ComputedValues to
-    /// avoid refactoring all APIs to become aware of multiple ComputedValues
-    /// objects.
-    visited_values: Option<Arc<ComputedValues>>,
-}
-
-impl Default for CascadeInputs {
-    fn default() -> Self {
-        CascadeInputs {
-            rules: None,
-            visited_rules: None,
-            visited_values: None,
-        }
-    }
+    pub visited_rules: Option<StrongRuleNode>,
 }
 
 impl CascadeInputs {
     /// Construct inputs from previous cascade results, if any.
     pub fn new_from_style(style: &Arc<ComputedValues>) -> Self {
         CascadeInputs {
             rules: style.rules.clone(),
             visited_rules: style.get_visited_style().and_then(|v| v.rules.clone()),
-            // Values will be re-cascaded if necessary, so this can be None.
-            visited_values: None,
         }
     }
-
-    /// Whether there are any rules.  Rules will be present after unvisited
-    /// matching or pulled from a previous cascade if no matching is expected.
-    pub fn has_rules(&self) -> bool {
-        self.rules.is_some()
-    }
-
-    /// Gets a mutable reference to the rule node, if any.
-    pub fn get_rules_mut(&mut self) -> Option<&mut StrongRuleNode> {
-        self.rules.as_mut()
-    }
-
-    /// Gets a reference to the rule node, if any.
-    pub fn get_rules(&self) -> Option<&StrongRuleNode> {
-        self.rules.as_ref()
-    }
-
-    /// Gets a reference to the rule node. Panic if the element does not have
-    /// rule node.
-    pub fn rules(&self) -> &StrongRuleNode {
-        self.rules.as_ref().unwrap()
-    }
-
-    /// Sets the rule node depending on visited mode.
-    /// Returns whether the rules changed.
-    pub fn set_rules(&mut self,
-                     visited_handling: VisitedHandlingMode,
-                     rules: StrongRuleNode)
-                     -> bool {
-        match visited_handling {
-            VisitedHandlingMode::AllLinksVisitedAndUnvisited => {
-                unreachable!("We should never try to selector match with \
-                             AllLinksVisitedAndUnvisited");
-            },
-            VisitedHandlingMode::AllLinksUnvisited => self.set_unvisited_rules(rules),
-            VisitedHandlingMode::RelevantLinkVisited => self.set_visited_rules(rules),
-        }
-    }
-
-    /// Sets the unvisited rule node, and returns whether it changed.
-    fn set_unvisited_rules(&mut self, rules: StrongRuleNode) -> bool {
-        if let Some(ref old_rules) = self.rules {
-            if *old_rules == rules {
-                return false
-            }
-        }
-        self.rules = Some(rules);
-        true
-    }
-
-    /// Whether there are any visited rules.  Visited rules will be present
-    /// after visited matching or pulled from a previous cascade (assuming there
-    /// was a relevant link at the time) if no matching is expected.
-    pub fn has_visited_rules(&self) -> bool {
-        self.visited_rules.is_some()
-    }
-
-    /// Gets a reference to the visited rule node, if any.
-    pub fn get_visited_rules(&self) -> Option<&StrongRuleNode> {
-        self.visited_rules.as_ref()
-    }
-
-    /// Gets a mutable reference to the visited rule node, if any.
-    pub fn get_visited_rules_mut(&mut self) -> Option<&mut StrongRuleNode> {
-        self.visited_rules.as_mut()
-    }
-
-    /// Gets a reference to the visited rule node. Panic if the element does not
-    /// have visited rule node.
-    pub fn visited_rules(&self) -> &StrongRuleNode {
-        self.visited_rules.as_ref().unwrap()
-    }
-
-    /// Sets the visited rule node, and returns whether it changed.
-    fn set_visited_rules(&mut self, rules: StrongRuleNode) -> bool {
-        if let Some(ref old_rules) = self.visited_rules {
-            if *old_rules == rules {
-                return false
-            }
-        }
-        self.visited_rules = Some(rules);
-        true
-    }
-
-    /// Takes the visited rule node.
-    pub fn take_visited_rules(&mut self) -> Option<StrongRuleNode> {
-        self.visited_rules.take()
-    }
-
-    /// Whether there are any visited values.
-    pub fn has_visited_values(&self) -> bool {
-        self.visited_values.is_some()
-    }
-
-    /// Gets a reference to the visited computed values. Panic if the element
-    /// does not have visited computed values.
-    pub fn visited_values(&self) -> &Arc<ComputedValues> {
-        self.visited_values.as_ref().unwrap()
-    }
-
-    /// Sets the visited computed values.
-    pub fn set_visited_values(&mut self, values: Arc<ComputedValues>) {
-        self.visited_values = Some(values);
-    }
-
-    /// Take the visited computed values.
-    pub fn take_visited_values(&mut self) -> Option<Arc<ComputedValues>> {
-        self.visited_values.take()
-    }
-
-    /// Clone the visited computed values Arc.  Used to store a reference to the
-    /// visited values inside the regular values.
-    pub fn clone_visited_values(&self) -> Option<Arc<ComputedValues>> {
-        self.visited_values.clone()
-    }
 }
 
 // We manually implement Debug for CascadeInputs so that we can avoid the
 // verbose stringification of ComputedValues for normal logging.
 impl fmt::Debug for CascadeInputs {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(f, "CascadeInputs {{ rules: {:?}, visited_rules: {:?}, .. }}",
                self.rules, self.visited_rules)
@@ -358,233 +221,46 @@ impl EagerPseudoCascadeInputs {
             let mut inputs: [Option<CascadeInputs>; EAGER_PSEUDO_COUNT] = Default::default();
             for i in 0..EAGER_PSEUDO_COUNT {
                 inputs[i] = styles[i].as_ref().map(|s| CascadeInputs::new_from_style(s));
             }
             inputs
         }))
     }
 
-    /// Returns whether there are any pseudo inputs.
-    pub fn is_empty(&self) -> bool {
-        self.0.is_none()
-    }
-
-    /// Returns a reference to the inputs for a given eager pseudo, if they exist.
-    pub fn get(&self, pseudo: &PseudoElement) -> Option<&CascadeInputs> {
-        debug_assert!(pseudo.is_eager());
-        self.0.as_ref().and_then(|p| p[pseudo.eager_index()].as_ref())
-    }
-
-    /// Returns a mutable reference to the inputs for a given eager pseudo, if they exist.
-    pub fn get_mut(&mut self, pseudo: &PseudoElement) -> Option<&mut CascadeInputs> {
-        debug_assert!(pseudo.is_eager());
-        self.0.as_mut().and_then(|p| p[pseudo.eager_index()].as_mut())
-    }
-
-    /// Returns true if the EagerPseudoCascadeInputs has a inputs 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, inputs: CascadeInputs) {
-        debug_assert!(!self.has(pseudo));
-        if self.0.is_none() {
-            self.0 = Some(Default::default());
-        }
-        self.0.as_mut().unwrap()[pseudo.eager_index()] = Some(inputs);
-    }
-
-    /// Removes a pseudo-element inputs if they exist, and returns it.
-    pub fn take(&mut self, pseudo: &PseudoElement) -> Option<CascadeInputs> {
-        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
-    }
-
-    /// Returns a list of the pseudo-elements.
-    pub fn keys(&self) -> ArrayVec<[PseudoElement; EAGER_PSEUDO_COUNT]> {
-        let mut v = ArrayVec::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
-    }
-
-    /// Adds the unvisited rule node for a given pseudo-element, which may or
-    /// may not exist.
-    ///
-    /// Returns true if the pseudo-element is new.
-    fn add_unvisited_rules(&mut self,
-                           pseudo: &PseudoElement,
-                           rules: StrongRuleNode)
-                           -> bool {
-        if let Some(mut inputs) = self.get_mut(pseudo) {
-            inputs.set_unvisited_rules(rules);
-            return false
-        }
-        let mut inputs = CascadeInputs::default();
-        inputs.set_unvisited_rules(rules);
-        self.insert(pseudo, inputs);
-        true
-    }
-
-    /// Remove the unvisited rule node for a given pseudo-element, which may or
-    /// may not exist. Since removing the rule node implies we don't need any
-    /// other data for the pseudo, take the entire pseudo if found.
-    ///
-    /// Returns true if the pseudo-element was removed.
-    fn remove_unvisited_rules(&mut self, pseudo: &PseudoElement) -> bool {
-        self.take(pseudo).is_some()
-    }
-
-    /// Adds the visited rule node for a given pseudo-element.  It is assumed to
-    /// already exist because unvisited inputs should have been added first.
-    ///
-    /// Returns true if the pseudo-element is new.  (Always false, but returns a
-    /// bool for parity with `add_unvisited_rules`.)
-    fn add_visited_rules(&mut self,
-                         pseudo: &PseudoElement,
-                         rules: StrongRuleNode)
-                         -> bool {
-        debug_assert!(self.has(pseudo));
-        let mut inputs = self.get_mut(pseudo).unwrap();
-        inputs.set_visited_rules(rules);
-        false
-    }
-
-    /// Remove the visited rule node for a given pseudo-element, which may or
-    /// may not exist.
-    ///
-    /// Returns true if the psuedo-element was removed. (Always false, but
-    /// returns a bool for parity with `remove_unvisited_rules`.)
-    fn remove_visited_rules(&mut self, pseudo: &PseudoElement) -> bool {
-        if let Some(mut inputs) = self.get_mut(pseudo) {
-            inputs.take_visited_rules();
-        }
-        false
-    }
-
-    /// Adds a rule node for a given pseudo-element, which may or may not exist.
-    /// The type of rule node depends on the visited mode.
-    ///
-    /// Returns true if the pseudo-element is new.
-    pub fn add_rules(&mut self,
-                     pseudo: &PseudoElement,
-                     visited_handling: VisitedHandlingMode,
-                     rules: StrongRuleNode)
-                     -> bool {
-        match visited_handling {
-            VisitedHandlingMode::AllLinksVisitedAndUnvisited => {
-                unreachable!("We should never try to selector match with \
-                             AllLinksVisitedAndUnvisited");
-            },
-            VisitedHandlingMode::AllLinksUnvisited => {
-                self.add_unvisited_rules(&pseudo, rules)
-            },
-            VisitedHandlingMode::RelevantLinkVisited => {
-                self.add_visited_rules(&pseudo, rules)
-            },
-        }
-    }
-
-    /// Removes a rule node for a given pseudo-element, which may or may not
-    /// exist. The type of rule node depends on the visited mode.
-    ///
-    /// Returns true if the psuedo-element was removed.
-    pub fn remove_rules(&mut self,
-                        pseudo: &PseudoElement,
-                        visited_handling: VisitedHandlingMode)
-                        -> bool {
-        match visited_handling {
-            VisitedHandlingMode::AllLinksVisitedAndUnvisited => {
-                unreachable!("We should never try to selector match with \
-                             AllLinksVisitedAndUnvisited");
-            },
-            VisitedHandlingMode::AllLinksUnvisited => {
-                self.remove_unvisited_rules(&pseudo)
-            },
-            VisitedHandlingMode::RelevantLinkVisited => {
-                self.remove_visited_rules(&pseudo)
-            },
-        }
+    /// Returns the list of rules, if they exist.
+    pub fn into_array(self) -> Option<[Option<CascadeInputs>; EAGER_PSEUDO_COUNT]> {
+        self.0
     }
 }
 
 /// The cascade inputs associated with a node, including those for any
 /// pseudo-elements.
 ///
 /// The matching and cascading process stores them in this format temporarily
 /// within the `CurrentElementInfo`. At the end of the cascade, they are folded
 /// down into the main `ComputedValues` to reduce memory usage per element while
 /// still remaining accessible.
 #[derive(Clone, Debug)]
 pub struct ElementCascadeInputs {
     /// The element's cascade inputs.
-    pub primary: Option<CascadeInputs>,
+    pub primary: CascadeInputs,
     /// A list of the inputs for the element's eagerly-cascaded pseudo-elements.
     pub pseudos: EagerPseudoCascadeInputs,
 }
 
-impl Default for ElementCascadeInputs {
-    /// Construct an empty `ElementCascadeInputs`.
-    fn default() -> Self {
-        ElementCascadeInputs {
-            primary: None,
-            pseudos: EagerPseudoCascadeInputs(None),
-        }
-    }
-}
-
 impl ElementCascadeInputs {
     /// Construct inputs from previous cascade results, if any.
     pub fn new_from_element_data(data: &ElementData) -> Self {
-        if !data.has_styles() {
-            return ElementCascadeInputs::default()
-        }
+        debug_assert!(data.has_styles());
         ElementCascadeInputs {
-            primary: Some(CascadeInputs::new_from_style(data.styles.primary())),
+            primary: CascadeInputs::new_from_style(data.styles.primary()),
             pseudos: EagerPseudoCascadeInputs::new_from_style(&data.styles.pseudos),
         }
     }
-
-    /// Returns whether we have primary inputs.
-    pub fn has_primary(&self) -> bool {
-        self.primary.is_some()
-    }
-
-    /// Gets the primary inputs. Panic if unavailable.
-    pub fn primary(&self) -> &CascadeInputs {
-        self.primary.as_ref().unwrap()
-    }
-
-    /// Gets the mutable primary inputs. Panic if unavailable.
-    pub fn primary_mut(&mut self) -> &mut CascadeInputs {
-        self.primary.as_mut().unwrap()
-    }
-
-    /// Ensure primary inputs exist and create them if they do not.
-    /// Returns a mutable reference to the primary inputs.
-    pub fn ensure_primary(&mut self) -> &mut CascadeInputs {
-        if self.primary.is_none() {
-            self.primary = Some(CascadeInputs::default());
-        }
-        self.primary.as_mut().unwrap()
-    }
 }
 
 /// Information about the current element being processed. We group this
 /// together into a single struct within ThreadLocalStyleContext so that we can
 /// instantiate and destroy it easily at the beginning and end of element
 /// processing.
 pub struct CurrentElementInfo {
     /// The element being processed. Currently we use an OpaqueNode since we
@@ -593,21 +269,16 @@ pub struct CurrentElementInfo {
     element: OpaqueNode,
     /// Whether the element is being styled for the first time.
     is_initial_style: bool,
     /// Lazy cache of the different data used for style sharing.
     pub validation_data: ValidationData,
     /// A Vec of possibly expired animations. Used only by Servo.
     #[allow(dead_code)]
     pub possibly_expired_animations: Vec<PropertyAnimation>,
-    /// Temporary storage for various intermediate inputs that are eventually
-    /// used by by the cascade. At the end of the cascade, they are folded down
-    /// into the main `ComputedValues` to reduce memory usage per element while
-    /// still remaining accessible.
-    pub cascade_inputs: ElementCascadeInputs,
 }
 
 /// Statistics gathered during the traversal. We gather statistics on each
 /// thread and then combine them after the threads join via the Add
 /// implementation below.
 #[derive(Default)]
 pub struct TraversalStatistics {
     /// The total number of elements traversed.
@@ -901,17 +572,16 @@ impl<E: TElement> ThreadLocalStyleContex
     /// Notes when the style system starts traversing an element.
     pub fn begin_element(&mut self, element: E, data: &ElementData) {
         debug_assert!(self.current_element_info.is_none());
         self.current_element_info = Some(CurrentElementInfo {
             element: element.as_node().opaque(),
             is_initial_style: !data.has_styles(),
             validation_data: ValidationData::default(),
             possibly_expired_animations: Vec::new(),
-            cascade_inputs: ElementCascadeInputs::default(),
         });
     }
 
     /// Notes when the style system finishes traversing an element.
     pub fn end_element(&mut self, element: E) {
         debug_assert!(self.current_element_info.is_some());
         debug_assert!(self.current_element_info.as_ref().unwrap().element ==
                       element.as_node().opaque());
@@ -946,34 +616,16 @@ impl<E: TElement> Drop for ThreadLocalSt
 /// shared style context, and a mutable reference to a local one.
 pub struct StyleContext<'a, E: TElement + 'a> {
     /// The shared style context reference.
     pub shared: &'a SharedStyleContext<'a>,
     /// The thread-local style context (mutable) reference.
     pub thread_local: &'a mut ThreadLocalStyleContext<E>,
 }
 
-impl<'a, E: TElement + 'a> StyleContext<'a, E> {
-    /// Returns a reference to the cascade inputs.  Panics if there is no
-    /// `CurrentElementInfo`.
-    pub fn cascade_inputs(&self) -> &ElementCascadeInputs {
-        &self.thread_local.current_element_info
-             .as_ref().unwrap()
-             .cascade_inputs
-    }
-
-    /// Returns a mutable reference to the cascade inputs.  Panics if there is
-    /// no `CurrentElementInfo`.
-    pub fn cascade_inputs_mut(&mut self) -> &mut ElementCascadeInputs {
-        &mut self.thread_local.current_element_info
-                 .as_mut().unwrap()
-                 .cascade_inputs
-    }
-}
-
 /// Why we're doing reflow.
 #[derive(PartialEq, Copy, Clone, Debug)]
 pub enum ReflowGoal {
     /// We're reflowing in order to send a display list to the screen.
     ForDisplay,
     /// We're reflowing in order to satisfy a script query. No display list will be created.
     ForScriptQuery,
 }
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -2,57 +2,30 @@
  * 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)]
 #![deny(missing_docs)]
 
-use applicable_declarations::ApplicableDeclarationList;
-use cascade_info::CascadeInfo;
-use context::{CascadeInputs, SelectorFlagsMap, SharedStyleContext, StyleContext};
+use context::{CascadeInputs, ElementCascadeInputs, SelectorFlagsMap, SharedStyleContext, StyleContext};
 use data::{ElementData, ElementStyles, RestyleData};
-use dom::{TElement, TNode};
-use font_metrics::FontMetricsProvider;
+use dom::TElement;
 use invalidation::element::restyle_hints::{RESTYLE_CSS_ANIMATIONS, RESTYLE_CSS_TRANSITIONS};
 use invalidation::element::restyle_hints::{RESTYLE_SMIL, RESTYLE_STYLE_ATTRIBUTE};
 use invalidation::element::restyle_hints::RestyleHint;
-use log::LogLevel::Trace;
-use properties::{AnimationRules, CascadeFlags, ComputedValues};
-use properties::{IS_ROOT_ELEMENT, PROHIBIT_DISPLAY_CONTENTS, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP};
-use properties::{VISITED_DEPENDENT_ONLY, cascade};
+use properties::ComputedValues;
 use properties::longhands::display::computed_value as display;
 use rule_tree::{CascadeLevel, StrongRuleNode};
-use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
-use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, StyleRelations};
-use selectors::matching::VisitedHandlingMode;
-use sharing::StyleSharingBehavior;
+use selector_parser::{PseudoElement, RestyleDamage};
+use selectors::matching::ElementSelectorFlags;
 use stylearc::Arc;
 use stylist::RuleInclusion;
 
-/// Whether we are cascading for an eager pseudo-element or something else.
-///
-/// Controls where we inherit styles from, and whether display:contents is
-/// prohibited.
-#[derive(PartialEq, Copy, Clone, Debug)]
-enum CascadeTarget {
-    /// Inherit from the parent element, as normal CSS dictates, _or_ from the
-    /// closest non-Native Anonymous element in case this is Native Anonymous
-    /// Content. display:contents is allowed.
-    Normal,
-    /// Inherit from the primary style, this is used while computing eager
-    /// pseudos, like ::before and ::after when we're traversing the parent.
-    /// Also prohibits display:contents from having an effect.
-    ///
-    /// TODO(emilio) display:contents really should apply to ::before/::after.
-    /// https://github.com/w3c/csswg-drafts/issues/1345
-    EagerPseudo,
-}
-
 /// Represents the result of comparing an element's old and new style.
 pub struct StyleDifference {
     /// The resulting damage.
     pub damage: RestyleDamage,
 
     /// Whether any styles changed.
     pub change: StyleChange,
 }
@@ -73,34 +46,35 @@ pub enum StyleChange {
     /// The style hasn't changed.
     Unchanged,
     /// The style has changed.
     Changed,
 }
 
 /// Whether or not newly computed values for an element need to be cascade
 /// to children.
+#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
 pub enum ChildCascadeRequirement {
     /// Old and new computed values were the same, or we otherwise know that
     /// we won't bother recomputing style for children, so we can skip cascading
     /// the new values into child elements.
-    CanSkipCascade,
+    CanSkipCascade = 0,
     /// Old and new computed values were different, so we must cascade the
     /// new values to children.
     ///
     /// FIXME(heycam) Although this is "must" cascade, in the future we should
     /// track whether child elements rely specifically on inheriting particular
     /// property values.  When we do that, we can treat `MustCascadeChildren` as
     /// "must cascade unless we know that changes to these properties can be
     /// ignored".
-    MustCascadeChildren,
+    MustCascadeChildren = 1,
     /// The same as `MustCascadeChildren`, but for the entire subtree.  This is
     /// used to handle root font-size updates needing to recascade the whole
     /// document.
-    MustCascadeDescendants,
+    MustCascadeDescendants = 2,
 }
 
 bitflags! {
     /// Flags that represent the result of replace_rules.
     pub flags RulesChanged: u8 {
         /// Normal rules are changed.
         const NORMAL_RULES_CHANGED = 0x01,
         /// Important rules are changed.
@@ -128,560 +102,56 @@ pub enum CascadeVisitedMode {
     /// Cascade the regular, unvisited styles.
     Unvisited,
     /// Cascade the styles used when an element's relevant link is visited.  A
     /// "relevant link" is the element being matched if it is a link or the
     /// nearest ancestor link.
     Visited,
 }
 
-/// Various helper methods to ease navigating the style storage locations
-/// depending on the current cascade mode.
 impl CascadeVisitedMode {
-    /// Returns whether there is a rule node based on the cascade mode.
-    /// Rules will be present after matching or pulled from a previous cascade
-    /// if no matching is expected.  For visited, this means rules exist only
-    /// if a revelant link existed when matching was last done.
-    fn has_rules(&self, inputs: &CascadeInputs) -> bool {
-        match *self {
-            CascadeVisitedMode::Unvisited => inputs.has_rules(),
-            CascadeVisitedMode::Visited => inputs.has_visited_rules(),
-        }
-    }
-
-    /// Returns the rule node based on the cascade mode.
-    fn rules<'a>(&self, inputs: &'a CascadeInputs) -> &'a StrongRuleNode {
-        match *self {
-            CascadeVisitedMode::Unvisited => inputs.rules(),
-            CascadeVisitedMode::Visited => match inputs.get_visited_rules() {
-                Some(rules) => rules,
-                None => inputs.rules(),
-            }
-        }
-    }
-
-    /// Returns a mutable rules node based on the cascade mode, if any.
-    fn get_rules_mut<'a>(&self, inputs: &'a mut CascadeInputs) -> Option<&'a mut StrongRuleNode> {
-        match *self {
-            CascadeVisitedMode::Unvisited => inputs.get_rules_mut(),
-            CascadeVisitedMode::Visited => inputs.get_visited_rules_mut(),
-        }
-    }
-
-    /// Returns the computed values based on the cascade mode.  In visited mode,
-    /// visited values are only returned if they already exist.  If they don't,
-    /// we fallback to the regular, unvisited styles.
-    pub fn values<'a>(&self, values: &'a Arc<ComputedValues>) -> &'a Arc<ComputedValues> {
-        if *self == CascadeVisitedMode::Visited && values.get_visited_style().is_some() {
-            return values.visited_style();
-        }
-
-        values
-    }
-
-    /// Set the primary computed values based on the cascade mode.
-    fn set_primary_values(&self,
-                          styles: &mut ElementStyles,
-                          inputs: &mut CascadeInputs,
-                          values: Arc<ComputedValues>) {
-        // Unvisited values are stored in permanent storage on `ElementData`.
-        // Visited values are stored temporarily in `CascadeInputs` and then
-        // folded into the unvisited values when they cascade.
-        match *self {
-            CascadeVisitedMode::Unvisited => styles.primary = Some(values),
-            CascadeVisitedMode::Visited => inputs.set_visited_values(values),
-        }
-    }
-
-    /// Set the primary computed values based on the cascade mode.
-    fn set_pseudo_values(&self,
-                         styles: &mut ElementStyles,
-                         inputs: &mut CascadeInputs,
-                         pseudo: &PseudoElement,
-                         values: Arc<ComputedValues>) {
-        // Unvisited values are stored in permanent storage on `ElementData`.
-        // Visited values are stored temporarily in `CascadeInputs` and then
-        // folded into the unvisited values when they cascade.
-        match *self {
-            CascadeVisitedMode::Unvisited => styles.pseudos.set(pseudo, values),
-            CascadeVisitedMode::Visited => inputs.set_visited_values(values),
-        }
-    }
-
-    /// Take the primary computed values based on the cascade mode.
-    fn take_primary_values(&self,
-                           styles: &mut ElementStyles,
-                           inputs: &mut CascadeInputs)
-                           -> Option<Arc<ComputedValues>> {
-        // Unvisited values are stored in permanent storage on `ElementData`.
-        // Visited values are stored temporarily in `CascadeInputs` and then
-        // folded into the unvisited values when they cascade.
-        match *self {
-            CascadeVisitedMode::Unvisited => styles.primary.take(),
-            CascadeVisitedMode::Visited => inputs.take_visited_values(),
-        }
-    }
-
-    /// Take the pseudo computed values based on the cascade mode.
-    fn take_pseudo_values(&self,
-                          styles: &mut ElementStyles,
-                          inputs: &mut CascadeInputs,
-                          pseudo: &PseudoElement)
-                          -> Option<Arc<ComputedValues>> {
-        // Unvisited values are stored in permanent storage on `ElementData`.
-        // Visited values are stored temporarily in `CascadeInputs` and then
-        // folded into the unvisited values when they cascade.
-        match *self {
-            CascadeVisitedMode::Unvisited => styles.pseudos.take(pseudo),
-            CascadeVisitedMode::Visited => inputs.take_visited_values(),
-        }
-    }
-
-    /// Returns whether there might be visited values that should be inserted
-    /// within the regular computed values based on the cascade mode.
-    pub fn visited_values_for_insertion(&self) -> bool {
-        *self == CascadeVisitedMode::Unvisited
-    }
-
-    /// Returns whether animations should be processed based on the cascade
-    /// mode.  At the moment, it appears we don't need to support animating
-    /// visited styles.
-    fn should_process_animations(&self) -> bool {
-        *self == CascadeVisitedMode::Unvisited
-    }
-
-    /// Returns whether we should accumulate restyle damage based on the cascade
-    /// mode.  At the moment, it appears we don't need to do so for visited
-    /// styles.  TODO: Verify this is correct as part of
-    /// https://bugzilla.mozilla.org/show_bug.cgi?id=1364484.
-    fn should_accumulate_damage(&self) -> bool {
-        *self == CascadeVisitedMode::Unvisited
-    }
-
     /// Returns whether the cascade should filter to only visited dependent
     /// properties based on the cascade mode.
     pub fn visited_dependent_only(&self) -> bool {
         *self == CascadeVisitedMode::Visited
     }
 }
 
 trait PrivateMatchMethods: TElement {
-    /// Returns the closest parent element that doesn't have a display: contents
-    /// style (and thus generates a box).
-    ///
-    /// This is needed to correctly handle blockification of flex and grid
-    /// items.
-    ///
-    /// Returns itself if the element has no parent. In practice this doesn't
-    /// happen because the root element is blockified per spec, but it could
-    /// happen if we decide to not blockify for roots of disconnected subtrees,
-    /// which is a kind of dubious beahavior.
-    fn layout_parent(&self) -> Self {
-        let mut current = self.clone();
-        loop {
-            current = match current.traversal_parent() {
-                Some(el) => el,
-                None => return current,
-            };
-
-            let is_display_contents =
-                current.borrow_data().unwrap().styles.primary().is_display_contents();
-
-            if !is_display_contents {
-                return current;
-            }
-        }
-    }
-
-    /// Get the ComputedValues (if any) for our inheritance parent.
-    fn get_inherited_style_and_parent(&self) -> ParentElementAndStyle<Self> {
-        let parent_el = self.inheritance_parent();
-        let parent_data = parent_el.as_ref().and_then(|e| e.borrow_data());
-        let parent_style = parent_data.as_ref().map(|d| {
-            // Sometimes Gecko eagerly styles things without processing
-            // pending restyles first. In general we'd like to avoid this,
-            // but there can be good reasons (for example, needing to
-            // construct a frame for some small piece of newly-added
-            // content in order to do something specific with that frame,
-            // but not wanting to flush all of layout).
-            debug_assert!(cfg!(feature = "gecko") ||
-                          parent_el.unwrap().has_current_styles(d));
-            d.styles.primary()
-        });
-
-        ParentElementAndStyle {
-            element: parent_el,
-            style: parent_style.cloned(),
-        }
-    }
-
-    /// A common path for the cascade used by both primary elements and eager
-    /// pseudo-elements after collecting the appropriate rules to use.
-    ///
-    /// `primary_style` is expected to be Some for eager pseudo-elements.
-    ///
-    /// `parent_info` is our style parent and its primary style, if
-    /// it's already been computed.
-    fn cascade_with_rules(&self,
-                          shared_context: &SharedStyleContext,
-                          font_metrics_provider: &FontMetricsProvider,
-                          rule_node: &StrongRuleNode,
-                          primary_style: Option<&Arc<ComputedValues>>,
-                          cascade_target: CascadeTarget,
-                          cascade_visited: CascadeVisitedMode,
-                          parent_info: Option<&ParentElementAndStyle<Self>>,
-                          visited_values_to_insert: Option<Arc<ComputedValues>>)
-                          -> Arc<ComputedValues> {
-        let mut cascade_info = CascadeInfo::new();
-        let mut cascade_flags = CascadeFlags::empty();
-        if self.skip_root_and_item_based_display_fixup() {
-            cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP)
-        }
-        if cascade_visited.visited_dependent_only() {
-            cascade_flags.insert(VISITED_DEPENDENT_ONLY);
-        }
-        if self.is_native_anonymous() || cascade_target == CascadeTarget::EagerPseudo {
-            cascade_flags.insert(PROHIBIT_DISPLAY_CONTENTS);
-        } else if self.is_root() {
-            cascade_flags.insert(IS_ROOT_ELEMENT);
-        }
-
-        // Grab the inherited values.
-        let parent_el;
-        let element_and_style; // So parent_el and style_to_inherit_from are known live.
-        let style_to_inherit_from = match cascade_target {
-            CascadeTarget::Normal => {
-                let info = match parent_info {
-                    Some(element_and_style) => element_and_style,
-                    None => {
-                        element_and_style = self.get_inherited_style_and_parent();
-                        &element_and_style
-                    }
-                };
-                parent_el = info.element;
-                info.style.as_ref().map(|s| cascade_visited.values(s))
-            }
-            CascadeTarget::EagerPseudo => {
-                parent_el = Some(self.clone());
-                Some(cascade_visited.values(primary_style.unwrap()))
-            }
-        };
-
-        let mut layout_parent_el = parent_el.clone();
-        let layout_parent_data;
-        let mut layout_parent_style = style_to_inherit_from;
-        if style_to_inherit_from.map_or(false, |s| s.is_display_contents()) {
-            layout_parent_el = Some(layout_parent_el.unwrap().layout_parent());
-            layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap();
-            layout_parent_style = Some(cascade_visited.values(layout_parent_data.styles.primary()));
-        }
-
-        let style_to_inherit_from = style_to_inherit_from.map(|x| &**x);
-        let layout_parent_style = layout_parent_style.map(|x| &**x);
-
-        // Propagate the "can be fragmented" bit. It would be nice to
-        // encapsulate this better.
-        //
-        // Note that this is technically not needed for pseudos since we already
-        // do that when we resolve the non-pseudo style, but it doesn't hurt
-        // anyway.
-        //
-        // TODO(emilio): This is servo-only, move somewhere else?
-        if let Some(ref p) = layout_parent_style {
-            let can_be_fragmented =
-                p.is_multicol() ||
-                layout_parent_el.as_ref().unwrap().as_node().can_be_fragmented();
-            unsafe { self.as_node().set_can_be_fragmented(can_be_fragmented); }
-        }
-
-        // Invoke the cascade algorithm.
-        let values =
-            Arc::new(cascade(shared_context.stylist.device(),
-                             rule_node,
-                             &shared_context.guards,
-                             style_to_inherit_from,
-                             layout_parent_style,
-                             visited_values_to_insert,
-                             Some(&mut cascade_info),
-                             font_metrics_provider,
-                             cascade_flags,
-                             shared_context.quirks_mode));
-
-        cascade_info.finish(&self.as_node());
-        values
-    }
-
-    /// A common path for the cascade used by both primary elements and eager
-    /// pseudo-elements.
-    ///
-    /// `primary_style` is expected to be Some for eager pseudo-elements.
-    ///
-    /// `parent_info` is our style parent and its primary style, if
-    /// it's already been computed.
-    fn cascade_internal(&self,
-                        context: &StyleContext<Self>,
-                        primary_style: Option<&Arc<ComputedValues>>,
-                        primary_inputs: &CascadeInputs,
-                        eager_pseudo_inputs: Option<&CascadeInputs>,
-                        parent_info: Option<&ParentElementAndStyle<Self>>,
-                        cascade_visited: CascadeVisitedMode)
-                        -> Arc<ComputedValues> {
-        if let Some(pseudo) = self.implemented_pseudo_element() {
-            debug_assert!(eager_pseudo_inputs.is_none());
-
-            // This is an element-backed pseudo, just grab the styles from the
-            // parent if it's eager, and recascade otherwise.
-            //
-            // We also recascade if the eager pseudo-style has any animation
-            // rules, because we don't cascade those during the eager traversal.
-            //
-            // We could make that a bit better if the complexity cost is not too
-            // big, but given further restyles are posted directly to
-            // pseudo-elements, it doesn't seem worth the effort at a glance.
-            //
-            // For the same reason as described in match_primary, if we are
-            // computing default styles, we aren't guaranteed the parent
-            // will have eagerly computed our styles, so we just handled it
-            // below like a lazy pseudo.
-            let only_default_rules = context.shared.traversal_flags.for_default_styles();
-            if pseudo.is_eager() && !only_default_rules {
-                debug_assert!(pseudo.is_before_or_after());
-                let parent = self.parent_element().unwrap();
-                if !parent.may_have_animations() ||
-                   self.get_animation_rules().is_empty() {
-                    let parent_data = parent.borrow_data().unwrap();
-                    let pseudo_style =
-                        parent_data.styles.pseudos.get(&pseudo).unwrap();
-                    let values = cascade_visited.values(pseudo_style);
-                    return values.clone()
-                }
-            }
-        }
-
-        // Find possible visited computed styles to insert within the regular
-        // computed values we are about to create.
-        let visited_values_to_insert = if cascade_visited.visited_values_for_insertion() {
-            match eager_pseudo_inputs {
-                Some(ref s) => s.clone_visited_values(),
-                None => primary_inputs.clone_visited_values(),
-            }
-        } else {
-            None
-        };
-
-        // Grab the rule node.
-        let inputs = eager_pseudo_inputs.unwrap_or(primary_inputs);
-        // We'd really like to take the rules here to avoid refcount traffic,
-        // but animation's usage of `apply_declarations` make this tricky.
-        // See bug 1375525.
-        let rule_node = cascade_visited.rules(inputs);
-        let cascade_target = if eager_pseudo_inputs.is_some() {
-            CascadeTarget::EagerPseudo
-        } else {
-            CascadeTarget::Normal
-        };
-
-        self.cascade_with_rules(context.shared,
-                                &context.thread_local.font_metrics_provider,
-                                rule_node,
-                                primary_style,
-                                cascade_target,
-                                cascade_visited,
-                                parent_info,
-                                visited_values_to_insert)
-    }
-
-    /// Computes values and damage for the primary style of an element, setting
-    /// them on the ElementData.
-    ///
-    /// `parent_info` is our style parent and its primary style.
-    fn cascade_primary(&self,
-                       context: &mut StyleContext<Self>,
-                       data: &mut ElementData,
-                       important_rules_changed: bool,
-                       parent_info: &ParentElementAndStyle<Self>,
-                       cascade_visited: CascadeVisitedMode)
-                       -> ChildCascadeRequirement {
-        debug!("Cascade primary for {:?}, visited: {:?}", self, cascade_visited);
-
-        let mut old_values = cascade_visited.take_primary_values(
-            &mut data.styles,
-            context.cascade_inputs_mut().primary_mut()
-        );
-
-        let mut new_values = {
-            let primary_inputs = context.cascade_inputs().primary();
-
-            // If there was no relevant link at the time of matching, we won't
-            // have any visited rules, so there may not be anything do for the
-            // visited case. This early return is especially important for the
-            // `cascade_primary_and_pseudos` path since we rely on the state of
-            // some previous matching run.
-            //
-            // Note that we cannot take this early return if our parent has
-            // visited style, because then we too have visited style.
-            if !cascade_visited.has_rules(primary_inputs) && !parent_info.has_visited_style() {
-                return ChildCascadeRequirement::CanSkipCascade
-            }
-
-            // Compute the new values.
-            self.cascade_internal(context,
-                                  None,
-                                  primary_inputs,
-                                  None,
-                                  /* parent_info = */ None,
-                                  cascade_visited)
-        };
-
-        // NB: Animations for pseudo-elements in Gecko are handled while
-        // traversing the pseudo-elements themselves.
-        if !context.shared.traversal_flags.for_animation_only() &&
-           cascade_visited.should_process_animations() {
-            self.process_animations(context,
-                                    &mut old_values,
-                                    &mut new_values,
-                                    important_rules_changed);
-        }
-
-        let mut child_cascade_requirement =
-            ChildCascadeRequirement::CanSkipCascade;
-        if cascade_visited.should_accumulate_damage() {
-            child_cascade_requirement =
-                self.accumulate_damage(&context.shared,
-                                       &mut data.restyle,
-                                       old_values.as_ref().map(|v| v.as_ref()),
-                                       &new_values,
-                                       None);
-
-            // Handle root font-size changes.
-            //
-            // TODO(emilio): This should arguably be outside of the path for
-            // getComputedStyle/getDefaultComputedStyle, but it's unclear how to
-            // do it without duplicating a bunch of code.
-            if self.is_root() && !self.is_native_anonymous() &&
-                !context.shared.traversal_flags.for_default_styles() {
-                let device = context.shared.stylist.device();
-                let new_font_size = new_values.get_font().clone_font_size();
-
-                // If the root font-size changed since last time, and something
-                // in the document did use rem units, ensure we recascade the
-                // entire tree.
-                if old_values.map_or(true, |v| v.get_font().clone_font_size() != new_font_size) {
-                    // FIXME(emilio): This can fire when called from a document
-                    // from the bfcache (bug 1376897).
-                    debug_assert!(self.owner_doc_matches_for_testing(device));
-                    device.set_root_font_size(new_font_size);
-                    if device.used_root_font_size() {
-                        child_cascade_requirement = ChildCascadeRequirement::MustCascadeDescendants;
-                    }
-                }
-            }
-        }
-
-        // If there were visited values to insert, ensure they do in fact exist
-        // inside the new values.
-        debug_assert!(!cascade_visited.visited_values_for_insertion() ||
-                      context.cascade_inputs().primary().has_visited_values() ==
-                      new_values.has_visited_style());
-
-        // Set the new computed values.
-        let primary_inputs = context.cascade_inputs_mut().primary_mut();
-        cascade_visited.set_primary_values(&mut data.styles,
-                                           primary_inputs,
-                                           new_values);
-
-        // Return whether the damage indicates we must cascade new inherited
-        // values into children.
-        child_cascade_requirement
-    }
-
-    /// Computes values and damage for the eager pseudo-element styles of an
-    /// element, setting them on the ElementData.
-    fn cascade_eager_pseudo(&self,
-                            context: &mut StyleContext<Self>,
-                            data: &mut ElementData,
-                            pseudo: &PseudoElement,
-                            cascade_visited: CascadeVisitedMode) {
-        debug_assert!(pseudo.is_eager());
-
-        let old_values = cascade_visited.take_pseudo_values(
-            &mut data.styles,
-            context.cascade_inputs_mut().pseudos.get_mut(pseudo).unwrap(),
-            pseudo
-        );
-
-        let new_values = {
-            let pseudo_inputs = context.cascade_inputs().pseudos
-                                       .get(pseudo).unwrap();
-
-            // If there was no relevant link at the time of matching, we won't
-            // have any visited rules, so there may not be anything do for the
-            // visited case. This early return is especially important for the
-            // `cascade_primary_and_pseudos` path since we rely on the state of
-            // some previous matching run.
-            if !cascade_visited.has_rules(pseudo_inputs) {
-                return
-            }
-
-            // Primary inputs should already have rules populated since it's
-            // always processed before eager pseudos.
-            let primary_inputs = context.cascade_inputs().primary();
-            debug_assert!(cascade_visited.has_rules(primary_inputs));
-
-            self.cascade_internal(context,
-                                  data.styles.get_primary(),
-                                  primary_inputs,
-                                  Some(pseudo_inputs),
-                                  /* parent_info = */ None,
-                                  cascade_visited)
-        };
-
-        if cascade_visited.should_accumulate_damage() {
-            self.accumulate_damage(&context.shared,
-                                   &mut data.restyle,
-                                   old_values.as_ref().map(|v| v.as_ref()),
-                                   &new_values,
-                                   Some(pseudo));
-        }
-
-        let pseudo_inputs = context.cascade_inputs_mut().pseudos
-                                   .get_mut(pseudo).unwrap();
-        cascade_visited.set_pseudo_values(&mut data.styles,
-                                          pseudo_inputs,
-                                          pseudo,
-                                          new_values);
-    }
-
-    /// get_after_change_style removes the transition rules from the ComputedValues.
     /// If there is no transition rule in the ComputedValues, it returns None.
     #[cfg(feature = "gecko")]
-    fn get_after_change_style(&self,
-                              context: &mut StyleContext<Self>,
-                              primary_style: &Arc<ComputedValues>)
-                              -> Option<Arc<ComputedValues>> {
+    fn get_after_change_style(
+        &self,
+        context: &mut StyleContext<Self>,
+        primary_style: &Arc<ComputedValues>
+    ) -> Option<Arc<ComputedValues>> {
+        use style_resolver::StyleResolverForElement;
+
         let rule_node = primary_style.rules();
         let without_transition_rules =
             context.shared.stylist.rule_tree().remove_transition_rule_if_applicable(rule_node);
         if without_transition_rules == *rule_node {
-            // We don't have transition rule in this case, so return None to let the caller
-            // use the original ComputedValues.
+            // We don't have transition rule in this case, so return None to let
+            // the caller use the original ComputedValues.
             return None;
         }
 
-        // This currently passes through visited styles, if they exist.
-        // When fixing bug 868975, compute after change for visited styles as
-        // well, along with updating the rest of the animation processing.
-        Some(self.cascade_with_rules(context.shared,
-                                     &context.thread_local.font_metrics_provider,
-                                     &without_transition_rules,
-                                     Some(primary_style),
-                                     CascadeTarget::Normal,
-                                     CascadeVisitedMode::Unvisited,
-                                     /* parent_info = */ None,
-                                     primary_style.get_visited_style().cloned()))
+        // FIXME(bug 868975): We probably need to transition visited style as
+        // well.
+        let inputs =
+            CascadeInputs {
+                rules: Some(without_transition_rules),
+                visited_rules: primary_style.get_visited_style().and_then(|s| s.rules.clone()),
+            };
+
+        let style =
+            StyleResolverForElement::new(*self, context, RuleInclusion::All)
+                .cascade_style_and_visited_with_default_parents(inputs);
+
+        Some(style)
     }
 
     #[cfg(feature = "gecko")]
     fn needs_animations_update(&self,
                                context: &mut StyleContext<Self>,
                                old_values: Option<&Arc<ComputedValues>>,
                                new_values: &ComputedValues)
                                -> bool {
@@ -814,27 +284,27 @@ trait PrivateMatchMethods: TElement {
                 this_opaque,
                 &**values,
                 new_values,
                 &shared_context.timer,
                 &possibly_expired_animations);
         }
     }
 
+
     /// Computes and applies non-redundant damage.
     #[cfg(feature = "gecko")]
     fn accumulate_damage_for(&self,
                              shared_context: &SharedStyleContext,
                              restyle: &mut RestyleData,
                              old_values: &ComputedValues,
                              new_values: &Arc<ComputedValues>,
                              pseudo: Option<&PseudoElement>)
                              -> ChildCascadeRequirement {
         use properties::computed_value_flags::*;
-
         // Don't accumulate damage if we're in a restyle for reconstruction.
         if shared_context.traversal_flags.for_reconstruct() {
             return ChildCascadeRequirement::MustCascadeChildren;
         }
 
         // If an ancestor is already getting reconstructed by Gecko's top-down
         // frame constructor, no need to apply damage.  Similarly if we already
         // have an explicitly stored ReconstructFrame hint.
@@ -926,467 +396,177 @@ trait PrivateMatchMethods: TElement {
                 }
             }
         }
     }
 }
 
 impl<E: TElement> PrivateMatchMethods for E {}
 
-/// A struct that holds an element we inherit from and its ComputedValues.
-#[derive(Debug)]
-struct ParentElementAndStyle<E: TElement> {
-    /// Our style parent element.
-    element: Option<E>,
-    /// Element's primary ComputedValues.  Not a borrow because we can't prove
-    /// that the thing hanging off element won't change while we're passing this
-    /// struct around.
-    style: Option<Arc<ComputedValues>>,
-}
-
-impl<E: TElement> ParentElementAndStyle<E> {
-    fn has_visited_style(&self) -> bool {
-        self.style.as_ref().map_or(false, |v| { v.get_visited_style().is_some() })
-    }
-}
+/// The public API that elements expose for selector matching.
+pub trait MatchMethods : TElement {
+    /// Returns the closest parent element that doesn't have a display: contents
+    /// style (and thus generates a box).
+    ///
+    /// This is needed to correctly handle blockification of flex and grid
+    /// items.
+    ///
+    /// Returns itself if the element has no parent. In practice this doesn't
+    /// happen because the root element is blockified per spec, but it could
+    /// happen if we decide to not blockify for roots of disconnected subtrees,
+    /// which is a kind of dubious beahavior.
+    fn layout_parent(&self) -> Self {
+        let mut current = self.clone();
+        loop {
+            current = match current.traversal_parent() {
+                Some(el) => el,
+                None => return current,
+            };
 
-/// Collects the outputs of the primary matching process, including the rule
-/// node and other associated data.
-#[derive(Debug)]
-pub struct MatchingResults {
-    /// Whether the rules changed.
-    rules_changed: bool,
-    /// Whether there are any changes of important rules overriding animations.
-    important_rules_overriding_animation_changed: bool,
-    /// Records certains relations between elements noticed during matching (and
-    /// also extended after matching).
-    relations: StyleRelations,
-    /// Whether we encountered a "relevant link" while matching _any_ selector
-    /// for this element. (This differs from `RelevantLinkStatus` which tracks
-    /// the status for the _current_ selector only.)
-    relevant_link_found: bool,
-}
+            let is_display_contents =
+                current.borrow_data().unwrap().styles.primary().is_display_contents();
 
-impl MatchingResults {
-    /// Create `MatchingResults` with only the basic required outputs.
-    fn new(rules_changed: bool, important_rules: bool) -> Self {
-        Self {
-            rules_changed: rules_changed,
-            important_rules_overriding_animation_changed: important_rules,
-            relations: StyleRelations::default(),
-            relevant_link_found: false,
+            if !is_display_contents {
+                return current;
+            }
         }
     }
 
-    /// Create `MatchingResults` from the output fields of `MatchingContext`.
-    fn new_from_context(rules_changed: bool,
-                        important_rules: bool,
-                        context: MatchingContext)
-                        -> Self {
-        Self {
-            rules_changed: rules_changed,
-            important_rules_overriding_animation_changed: important_rules,
-            relations: context.relations,
-            relevant_link_found: context.relevant_link_found,
-        }
-    }
-}
-
-/// The public API that elements expose for selector matching.
-pub trait MatchMethods : TElement {
-    /// Performs selector matching and property cascading on an element and its
-    /// eager pseudos.
-    fn match_and_cascade(
+    /// Updates the styles with the new ones, diffs them, and stores the restyle
+    /// damage.
+    fn finish_restyle(
         &self,
         context: &mut StyleContext<Self>,
-        data: &mut ElementData,
-        sharing: StyleSharingBehavior
+        mut data: &mut ElementData,
+        mut new_styles: ElementStyles,
+        important_rules_changed: bool,
     ) -> ChildCascadeRequirement {
-        debug!("Match and cascade for {:?}", self);
-
-        // Perform selector matching for the primary style.
-        let primary_results =
-            self.match_primary(context, data, VisitedHandlingMode::AllLinksUnvisited);
-        let important_rules_changed =
-            primary_results.important_rules_overriding_animation_changed;
+        use dom::TNode;
+        use std::mem;
+        use std::cmp;
 
-        // If there's a relevant link involved, match and cascade primary styles
-        // as if the link is visited as well.  This is done before the regular
-        // cascade because the visited ComputedValues are placed within the
-        // regular ComputedValues, which is immutable after the cascade.
-        let relevant_link_found = primary_results.relevant_link_found;
-        if relevant_link_found {
-            self.match_primary(context, data, VisitedHandlingMode::RelevantLinkVisited);
-        }
+        debug_assert!(new_styles.primary.is_some(), "How did that happen?");
 
-        // Even if there is no relevant link, we need to cascade visited styles
-        // if our parent has visited styles.
-        let parent_and_styles = self.get_inherited_style_and_parent();
-        if relevant_link_found || parent_and_styles.has_visited_style() {
-            self.cascade_primary(
+        if !context.shared.traversal_flags.for_animation_only() {
+            self.process_animations(
                 context,
-                data,
+                &mut data.styles.primary,
+                &mut new_styles.primary.as_mut().unwrap(),
                 important_rules_changed,
-                &parent_and_styles,
-                CascadeVisitedMode::Visited
             );
         }
 
-        // Cascade properties and compute primary values.
-        let child_cascade_requirement =
-            self.cascade_primary(
-                context,
-                data,
-                important_rules_changed,
-                &parent_and_styles,
-                CascadeVisitedMode::Unvisited
-            );
-
-        // Match and cascade eager pseudo-elements.
-        if !data.styles.is_display_none() {
-            self.match_pseudos(context, data, VisitedHandlingMode::AllLinksUnvisited);
-
-            // If there's a relevant link involved, match and cascade eager
-            // pseudo-element styles as if the link is visited as well.
-            // This runs after matching for regular styles because matching adds
-            // each pseudo as needed to the PseudoMap, and this runs before
-            // cascade for regular styles because the visited ComputedValues
-            // are placed within the regular ComputedValues, which is immutable
-            // after the cascade.
-            if relevant_link_found {
-                self.match_pseudos(context, data, VisitedHandlingMode::RelevantLinkVisited);
-                self.cascade_pseudos(context, data, CascadeVisitedMode::Visited);
-            }
-
-            self.cascade_pseudos(context, data, CascadeVisitedMode::Unvisited);
-        }
-
-        // If the style is shareable, add it to the LRU cache.
-        if sharing == StyleSharingBehavior::Allow {
-            // If we previously tried to match this element against the cache,
-            // the revalidation match results will already be cached. Otherwise
-            // we'll have None, and compute them later on-demand.
-            //
-            // If we do have the results, grab them here to satisfy the borrow
-            // checker.
-            let validation_data =
-                context.thread_local
-                    .current_element_info
-                    .as_mut().unwrap()
-                    .validation_data
-                    .take();
-
-            let dom_depth = context.thread_local.bloom_filter.matching_depth();
-            context.thread_local
-                   .style_sharing_candidate_cache
-                   .insert_if_possible(self,
-                                       data.styles.primary(),
-                                       primary_results.relations,
-                                       validation_data,
-                                       dom_depth);
-        }
-
-        child_cascade_requirement
-    }
+        // First of all, update the styles.
+        let old_styles = mem::replace(&mut data.styles, new_styles);
 
-    /// Performs the cascade, without matching.
-    fn cascade_primary_and_pseudos(&self,
-                                   context: &mut StyleContext<Self>,
-                                   mut data: &mut ElementData,
-                                   important_rules_changed: bool)
-                                   -> ChildCascadeRequirement
-    {
-        // If there's a relevant link involved, cascade styles as if the link is
-        // visited as well. This is done before the regular cascade because the
-        // visited ComputedValues are placed within the regular ComputedValues,
-        // which is immutable after the cascade.  If there aren't any visited
-        // rules, these calls will return without cascading.
-        let parent_and_styles = self.get_inherited_style_and_parent();
-        self.cascade_primary(context, &mut data, important_rules_changed,
-                             &parent_and_styles,
-                             CascadeVisitedMode::Visited);
-        let child_cascade_requirement =
-            self.cascade_primary(context, &mut data, important_rules_changed,
-                                 &parent_and_styles,
-                                 CascadeVisitedMode::Unvisited);
-        self.cascade_pseudos(context, &mut data, CascadeVisitedMode::Visited);
-        self.cascade_pseudos(context, &mut data, CascadeVisitedMode::Unvisited);
-        child_cascade_requirement
-    }
-
-    /// Runs selector matching to (re)compute the primary rule node for this
-    /// element.
-    ///
-    /// Returns `MatchingResults` with the new rules and other associated data
-    /// from the matching process.
-    fn match_primary(
-        &self,
-        context: &mut StyleContext<Self>,
-        data: &mut ElementData,
-        visited_handling: VisitedHandlingMode
-    ) -> MatchingResults {
-        debug!("Match primary for {:?}, visited: {:?}", self, visited_handling);
-
-        let mut primary_inputs = context.thread_local.current_element_info
-                                        .as_mut().unwrap()
-                                        .cascade_inputs.ensure_primary();
+        // Propagate the "can be fragmented" bit. It would be nice to
+        // encapsulate this better.
+        //
+        // Note that this is technically not needed for pseudos since we already
+        // do that when we resolve the non-pseudo style, but it doesn't hurt
+        // anyway.
+        if cfg!(feature = "servo") {
+            let layout_parent =
+                self.inheritance_parent().map(|e| e.layout_parent());
+            let layout_parent_data =
+                layout_parent.as_ref().and_then(|e| e.borrow_data());
+            let layout_parent_style =
+                layout_parent_data.as_ref().map(|d| d.styles.primary());
 
-        let only_default_rules = context.shared.traversal_flags.for_default_styles();
-        let implemented_pseudo = self.implemented_pseudo_element();
-        if let Some(ref pseudo) = implemented_pseudo {
-            // We don't expect to match against a non-canonical pseudo-element.
-            debug_assert_eq!(*pseudo, pseudo.canonical());
-            if pseudo.is_eager() && !only_default_rules {
-                // If it's an eager element-backed pseudo, we can generally just
-                // grab the matched rules from the parent, and then update
-                // animations.
-                //
-                // However, if we're computing default styles, then we might
-                // have traversed to this pseudo-implementing element without
-                // any pseudo styles stored on the parent.  For example, if
-                // document-level style sheets cause the element to exist, due
-                // to ::before rules, then those rules won't be found when
-                // computing default styles on the parent, so we won't have
-                // bothered to store pseudo styles there.  In this case, we just
-                // treat it like a lazily computed pseudo.
-                let parent = self.parent_element().unwrap();
-                let parent_data = parent.borrow_data().unwrap();
-                let pseudo_style =
-                    parent_data.styles.pseudos.get(&pseudo).unwrap();
-                let mut rules = pseudo_style.rules().clone();
-                if parent.may_have_animations() {
-                    let animation_rules = self.get_animation_rules();
-
-                    // Handle animations here.
-                    if let Some(animation_rule) = animation_rules.0 {
-                        let animation_rule_node =
-                            context.shared.stylist.rule_tree()
-                                .update_rule_at_level(CascadeLevel::Animations,
-                                                      Some(&animation_rule),
-                                                      &mut rules,
-                                                      &context.shared.guards);
-                        if let Some(node) = animation_rule_node {
-                            rules = node;
-                        }
-                    }
-
-                    if let Some(animation_rule) = animation_rules.1 {
-                        let animation_rule_node =
-                            context.shared.stylist.rule_tree()
-                                .update_rule_at_level(CascadeLevel::Transitions,
-                                                      Some(&animation_rule),
-                                                      &mut rules,
-                                                      &context.shared.guards);
-                        if let Some(node) = animation_rule_node {
-                            rules = node;
-                        }
-                    }
-                }
-                let important_rules_changed =
-                    self.has_animations() &&
-                    data.has_styles() &&
-                    data.important_rules_are_different(&rules,
-                                                       &context.shared.guards);
-
-                let rules_changed = primary_inputs.set_rules(visited_handling, rules);
-
-                return MatchingResults::new(rules_changed, important_rules_changed)
+            if let Some(ref p) = layout_parent_style {
+                let can_be_fragmented =
+                    p.is_multicol() ||
+                    layout_parent.as_ref().unwrap().as_node().can_be_fragmented();
+                unsafe { self.as_node().set_can_be_fragmented(can_be_fragmented); }
             }
         }
 
-        let mut applicable_declarations = ApplicableDeclarationList::new();
-
-        let stylist = &context.shared.stylist;
-        let style_attribute = self.style_attribute();
-
-        let map = &mut context.thread_local.selector_flags;
-        let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| {
-            self.apply_selector_flags(map, element, flags);
-        };
+        // Don't accumulate damage if we're in a restyle for reconstruction.
+        if context.shared.traversal_flags.for_reconstruct() {
+            return ChildCascadeRequirement::MustCascadeChildren;
+        }
 
-        let rule_inclusion = if only_default_rules {
-            RuleInclusion::DefaultOnly
-        } else {
-            RuleInclusion::All
-        };
-
-        let bloom_filter = context.thread_local.bloom_filter.filter();
-        let mut matching_context =
-            MatchingContext::new_for_visited(MatchingMode::Normal,
-                                             Some(bloom_filter),
-                                             visited_handling,
-                                             context.shared.quirks_mode);
+        let new_primary_style = data.styles.primary.as_ref().unwrap();
 
-        {
-            let smil_override = self.get_smil_override();
-            let animation_rules = self.get_animation_rules();
+        let mut cascade_requirement = ChildCascadeRequirement::CanSkipCascade;
+        if self.is_root() && !self.is_native_anonymous() {
+            let device = context.shared.stylist.device();
+            let new_font_size = new_primary_style.get_font().clone_font_size();
 
-            // Compute the primary rule node.
-            stylist.push_applicable_declarations(self,
-                                                 implemented_pseudo.as_ref(),
-                                                 style_attribute,
-                                                 smil_override,
-                                                 animation_rules,
-                                                 rule_inclusion,
-                                                 &mut applicable_declarations,
-                                                 &mut matching_context,
-                                                 &mut set_selector_flags);
-        }
-        self.unset_dirty_style_attribute();
-
-        let primary_rule_node = stylist.rule_tree().compute_rule_node(
-            &mut applicable_declarations,
-            &context.shared.guards
-        );
-
-        if log_enabled!(Trace) {
-            trace!("Matched rules:");
-            for rn in primary_rule_node.self_and_ancestors() {
-                let source = rn.style_source();
-                if source.is_some() {
-                    trace!(" > {:?}", source);
+            if old_styles.primary.as_ref().map_or(true, |s| s.get_font().clone_font_size() != new_font_size) {
+                debug_assert!(self.owner_doc_matches_for_testing(device));
+                device.set_root_font_size(new_font_size);
+                // If the root font-size changed since last time, and something
+                // in the document did use rem units, ensure we recascade the
+                // entire tree.
+                if device.used_root_font_size() {
+                    cascade_requirement = ChildCascadeRequirement::MustCascadeDescendants;
                 }
             }
         }
 
-        let important_rules_changed =
-            self.has_animations() &&
-            data.has_styles() &&
-            data.important_rules_are_different(
-                &primary_rule_node,
-                &context.shared.guards
-            );
-
-        let rules_changed = primary_inputs.set_rules(visited_handling, primary_rule_node);
-
-        MatchingResults::new_from_context(rules_changed,
-                                          important_rules_changed,
-                                          matching_context)
-    }
-
-    /// Runs selector matching to (re)compute eager pseudo-element rule nodes
-    /// for this element.
-    fn match_pseudos(&self,
-                     context: &mut StyleContext<Self>,
-                     data: &mut ElementData,
-                     visited_handling: VisitedHandlingMode)
-    {
-        debug!("Match pseudos for {:?}, visited: {:?}", self, visited_handling);
-
-        if self.implemented_pseudo_element().is_some() {
-            // Element pseudos can't have any other pseudo.
-            return;
-        }
-
-        let mut applicable_declarations = ApplicableDeclarationList::new();
-
-        // Borrow the stuff we need here so the borrow checker doesn't get mad
-        // at us later in the closure.
-        let stylist = &context.shared.stylist;
-        let guards = &context.shared.guards;
-
-        let rule_inclusion = if context.shared.traversal_flags.for_default_styles() {
-            RuleInclusion::DefaultOnly
-        } else {
-            RuleInclusion::All
+        // Also, don't do anything if there was no style.
+        let old_primary_style = match old_styles.primary {
+            Some(s) => s,
+            None => return ChildCascadeRequirement::MustCascadeChildren,
         };
 
-        // Compute rule nodes for eagerly-cascaded pseudo-elements.
-        let mut matches_different_pseudos = false;
-        SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
-            // For eager pseudo-elements, we only try to match visited rules if
-            // there are also unvisited rules.  (This matches Gecko's behavior
-            // for probing pseudo-elements, and for eager pseudo-elements Gecko
-            // does not try to resolve style if the probe says there isn't any.)
-            if visited_handling == VisitedHandlingMode::RelevantLinkVisited &&
-               !context.cascade_inputs().pseudos.has(&pseudo) {
-                return
-            }
+        cascade_requirement = cmp::max(
+            cascade_requirement,
+            self.accumulate_damage_for(
+                context.shared,
+                &mut data.restyle,
+                &old_primary_style,
+                new_primary_style,
+                None,
+            )
+        );
 
-            if self.may_generate_pseudo(&pseudo, data.styles.primary()) {
-                let bloom_filter = context.thread_local.bloom_filter.filter();
+        if data.styles.pseudos.is_empty() && old_styles.pseudos.is_empty() {
+            return cascade_requirement;
+        }
 
-                let mut matching_context =
-                    MatchingContext::new_for_visited(MatchingMode::ForStatelessPseudoElement,
-                                                     Some(bloom_filter),
-                                                     visited_handling,
-                                                     context.shared.quirks_mode);
-
-                let map = &mut context.thread_local.selector_flags;
-                let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| {
-                    self.apply_selector_flags(map, element, flags);
-                };
+        // If it matched a different number of pseudos, reconstruct.
+        if data.styles.pseudos.is_empty() != old_styles.pseudos.is_empty() {
+            data.restyle.damage |= RestyleDamage::reconstruct();
+            return cascade_requirement;
+        }
 
-                debug_assert!(applicable_declarations.is_empty());
-                // NB: We handle animation rules for ::before and ::after when
-                // traversing them.
-                stylist.push_applicable_declarations(self,
-                                                     Some(&pseudo),
-                                                     None,
-                                                     None,
-                                                     AnimationRules(None, None),
-                                                     rule_inclusion,
-                                                     &mut applicable_declarations,
-                                                     &mut matching_context,
-                                                     &mut set_selector_flags);
-            }
+        let pseudo_styles =
+            old_styles.pseudos.as_array().unwrap().iter().zip(
+            data.styles.pseudos.as_array().unwrap().iter());
 
-            let pseudos = &mut context.thread_local.current_element_info
-                                      .as_mut().unwrap()
-                                      .cascade_inputs.pseudos;
-            if !applicable_declarations.is_empty() {
-                let rules = stylist.rule_tree().compute_rule_node(
-                    &mut applicable_declarations,
-                    &guards
-                );
-                matches_different_pseudos |= !data.styles.pseudos.has(&pseudo);
-                pseudos.add_rules(
-                    &pseudo,
-                    visited_handling,
-                    rules
-                );
-            } else {
-                matches_different_pseudos |= data.styles.pseudos.has(&pseudo);
-                pseudos.remove_rules(
-                    &pseudo,
-                    visited_handling
-                );
-                data.styles.pseudos.take(&pseudo);
+        for (i, (old, new)) in pseudo_styles.enumerate() {
+            match (old, new) {
+                (&Some(ref old), &Some(ref new)) => {
+                    self.accumulate_damage_for(
+                        context.shared,
+                        &mut data.restyle,
+                        old,
+                        new,
+                        Some(&PseudoElement::from_eager_index(i)),
+                    );
+                }
+                (&None, &None) => {},
+                _ => {
+                    data.restyle.damage |= RestyleDamage::reconstruct();
+                    return cascade_requirement;
+                }
             }
-        });
+        }
 
-        if matches_different_pseudos && data.restyle.is_restyle() {
-            // Any changes to the matched pseudo-elements trigger
-            // reconstruction.
-            data.restyle.damage |= RestyleDamage::reconstruct();
-        }
+        cascade_requirement
     }
 
+
     /// Applies selector flags to an element, deferring mutations of the parent
     /// until after the traversal.
     ///
-    /// TODO(emilio): This is somewhat inefficient, because of a variety of
-    /// reasons:
-    ///
-    ///  * It doesn't coalesce flags.
-    ///  * It doesn't look at flags already sent in a task for the main
-    ///    thread to process.
-    ///  * It doesn't take advantage of us knowing that the traversal is
-    ///    sequential.
-    ///
-    /// I suspect (need to measure!) that we don't use to set flags on
-    /// a lot of different elements, but we could end up posting the same
-    /// flag over and over with this approach.
-    ///
-    /// If the number of elements is low, perhaps a small cache with the
-    /// flags already sent would be appropriate.
-    ///
-    /// The sequential task business for this is kind of sad :(.
-    ///
-    /// Anyway, let's do the obvious thing for now.
+    /// TODO(emilio): This is somewhat inefficient, because it doesn't take
+    /// advantage of us knowing that the traversal is sequential.
     fn apply_selector_flags(&self,
                             map: &mut SelectorFlagsMap<Self>,
                             element: &Self,
                             flags: ElementSelectorFlags) {
         // Handle flags that apply to the element.
         let self_flags = flags.for_self();
         if !self_flags.is_empty() {
             if element == self {
@@ -1453,48 +633,63 @@ pub trait MatchMethods : TElement {
     /// Updates the rule nodes without re-running selector matching, using just
     /// the rule tree.
     ///
     /// Returns true if an !important rule was replaced.
     fn replace_rules(
         &self,
         replacements: RestyleHint,
         context: &mut StyleContext<Self>,
+        cascade_inputs: &mut ElementCascadeInputs,
     ) -> bool {
         let mut result = false;
-        result |= self.replace_rules_internal(replacements, context,
-                                              CascadeVisitedMode::Unvisited);
+        result |= self.replace_rules_internal(
+            replacements,
+            context,
+            CascadeVisitedMode::Unvisited,
+            cascade_inputs,
+        );
         if !context.shared.traversal_flags.for_animation_only() {
-            result |= self.replace_rules_internal(replacements, context,
-                                                  CascadeVisitedMode::Visited);
+            result |= self.replace_rules_internal(
+                replacements,
+                context,
+                CascadeVisitedMode::Visited,
+                cascade_inputs
+            );
         }
         result
     }
 
     /// Updates the rule nodes without re-running selector matching, using just
     /// the rule tree, for a specific visited mode.
     ///
     /// Returns true if an !important rule was replaced.
     fn replace_rules_internal(
         &self,
         replacements: RestyleHint,
         context: &mut StyleContext<Self>,
-        cascade_visited: CascadeVisitedMode
+        cascade_visited: CascadeVisitedMode,
+        cascade_inputs: &mut ElementCascadeInputs,
     ) -> bool {
         use properties::PropertyDeclarationBlock;
         use shared_lock::Locked;
 
         debug_assert!(replacements.intersects(RestyleHint::replacements()) &&
                       (replacements & !RestyleHint::replacements()).is_empty());
 
         let stylist = &context.shared.stylist;
         let guards = &context.shared.guards;
 
-        let mut primary_inputs = context.cascade_inputs_mut().primary_mut();
-        let primary_rules = match cascade_visited.get_rules_mut(primary_inputs) {
+        let primary_rules =
+            match cascade_visited {
+                CascadeVisitedMode::Unvisited => cascade_inputs.primary.rules.as_mut(),
+                CascadeVisitedMode::Visited => cascade_inputs.primary.visited_rules.as_mut(),
+            };
+
+        let primary_rules = match primary_rules {
             Some(r) => r,
             None => return false,
         };
 
         let replace_rule_node = |level: CascadeLevel,
                                  pdb: Option<&Arc<Locked<PropertyDeclarationBlock>>>,
                                  path: &mut StrongRuleNode| -> bool {
             let new_node = stylist.rule_tree()
@@ -1513,16 +708,17 @@ pub trait MatchMethods : TElement {
             if replacements.contains(RESTYLE_STYLE_ATTRIBUTE) {
                 let style_attribute = self.style_attribute();
                 result |= replace_rule_node(CascadeLevel::StyleAttributeNormal,
                                             style_attribute,
                                             primary_rules);
                 result |= replace_rule_node(CascadeLevel::StyleAttributeImportant,
                                             style_attribute,
                                             primary_rules);
+                // FIXME(emilio): Still a hack!
                 self.unset_dirty_style_attribute();
             }
             return result;
         }
 
         // Animation restyle hints are processed prior to other restyle
         // hints in the animation-only traversal.
         //
@@ -1559,22 +755,22 @@ pub trait MatchMethods : TElement {
         }
 
         false
     }
 
     /// Given the old and new style of this element, and whether it's a
     /// pseudo-element, compute the restyle damage used to determine which
     /// kind of layout or painting operations we'll need.
-    fn compute_style_difference(&self,
-                                old_values: &ComputedValues,
-                                new_values: &Arc<ComputedValues>,
-                                pseudo: Option<&PseudoElement>)
-                                -> StyleDifference
-    {
+    fn compute_style_difference(
+        &self,
+        old_values: &ComputedValues,
+        new_values: &Arc<ComputedValues>,
+        pseudo: Option<&PseudoElement>
+    ) -> StyleDifference {
         debug_assert!(pseudo.map_or(true, |p| p.is_eager()));
         if let Some(source) = self.existing_style_for_restyle_damage(old_values, pseudo) {
             return RestyleDamage::compute_style_difference(source, new_values)
         }
 
         let new_display = new_values.get_box().clone_display();
         let old_display = old_values.get_box().clone_display();
 
@@ -1637,60 +833,41 @@ pub trait MatchMethods : TElement {
             RestyleDamage::empty()
         };
         // We don't really know if there was a change in any style (since we
         // didn't actually call compute_style_difference) but we return
         // StyleChange::Changed conservatively.
         StyleDifference::new(damage, StyleChange::Changed)
     }
 
-    /// Performs the cascade for the element's eager pseudos.
-    fn cascade_pseudos(&self,
-                       context: &mut StyleContext<Self>,
-                       mut data: &mut ElementData,
-                       cascade_visited: CascadeVisitedMode)
-    {
-        debug!("Cascade pseudos for {:?}, visited: {:?}", self,
-               cascade_visited);
-        // Note that we've already set up the map of matching pseudo-elements
-        // in match_pseudos (and handled the damage implications of changing
-        // 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 cascade function.
-        let matched_pseudos = context.cascade_inputs().pseudos.keys();
-        for pseudo in matched_pseudos {
-            debug!("Cascade pseudo for {:?} {:?}", self, pseudo);
-            self.cascade_eager_pseudo(context, data, &pseudo, cascade_visited);
-        }
-    }
+    /// Returns computed values without animation and transition rules.
+    fn get_base_style(
+        &self,
+        context: &mut StyleContext<Self>,
+        style: &Arc<ComputedValues>,
+    ) -> Arc<ComputedValues> {
+        use style_resolver::StyleResolverForElement;
 
-    /// Returns computed values without animation and transition rules.
-    fn get_base_style(&self,
-                      shared_context: &SharedStyleContext,
-                      font_metrics_provider: &FontMetricsProvider,
-                      primary_style: &Arc<ComputedValues>,
-                      pseudo_style: Option<&Arc<ComputedValues>>)
-                      -> Arc<ComputedValues> {
-        let relevant_style = pseudo_style.unwrap_or(primary_style);
-        let rule_node = relevant_style.rules();
+        let rule_node = style.rules.as_ref().unwrap();
         let without_animation_rules =
-            shared_context.stylist.rule_tree().remove_animation_rules(rule_node);
+            context.shared.stylist.rule_tree().remove_animation_rules(rule_node);
         if without_animation_rules == *rule_node {
-            // Note that unwrapping here is fine, because the style is
-            // only incomplete during the styling process.
-            return relevant_style.clone();
+            // Note that unwrapping here is fine, because the style is only
+            // incomplete during the styling process.
+            return style.clone();
         }
 
         // This currently ignores visited styles, which seems acceptable,
         // as existing browsers don't appear to animate visited styles.
-        self.cascade_with_rules(shared_context,
-                                font_metrics_provider,
-                                &without_animation_rules,
-                                Some(primary_style),
-                                CascadeTarget::Normal,
-                                CascadeVisitedMode::Unvisited,
-                                /* parent_info = */ None,
-                                None)
+        let inputs =
+            CascadeInputs {
+                rules: Some(without_animation_rules),
+                visited_rules: None,
+            };
+
+        let style =
+            StyleResolverForElement::new(*self, context, RuleInclusion::All)
+                .cascade_style_and_visited_with_default_parents(inputs);
+        style
     }
-
 }
 
 impl<E: TElement> MatchMethods for E {}
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -2571,21 +2571,21 @@ pub fn cascade(device: &Device,
                parent_style: Option<<&ComputedValues>,
                layout_parent_style: Option<<&ComputedValues>,
                visited_style: Option<Arc<ComputedValues>>,
                cascade_info: Option<<&mut CascadeInfo>,
                font_metrics_provider: &FontMetricsProvider,
                flags: CascadeFlags,
                quirks_mode: QuirksMode)
                -> ComputedValues {
-    debug_assert_eq!(parent_style.is_some(), layout_parent_style.is_some());
+    debug_assert!(layout_parent_style.is_none() || parent_style.is_some());
     let (inherited_style, layout_parent_style) = match parent_style {
         Some(parent_style) => {
             (parent_style,
-             layout_parent_style.unwrap())
+             layout_parent_style.unwrap_or(parent_style))
         },
         None => {
             (device.default_computed_values(),
              device.default_computed_values())
         }
     };
 
     let iter_declarations = || {
--- a/servo/components/style/sharing/mod.rs
+++ b/servo/components/style/sharing/mod.rs
@@ -70,17 +70,17 @@ use bit_vec::BitVec;
 use bloom::StyleBloom;
 use cache::{LRUCache, LRUCacheMutIterator};
 use context::{SelectorFlagsMap, SharedStyleContext, StyleContext};
 use data::{ElementData, ElementStyles};
 use dom::{TElement, SendElement};
 use matching::{ChildCascadeRequirement, MatchMethods};
 use properties::ComputedValues;
 use selector_parser::RestyleDamage;
-use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode, StyleRelations};
+use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode};
 use smallvec::SmallVec;
 use std::mem;
 use std::ops::Deref;
 use stylist::Stylist;
 
 mod checks;
 
 /// The amount of nodes that the style sharing candidate cache should hold at
@@ -489,21 +489,18 @@ impl<E: TElement> StyleSharingCandidateC
     }
 
     /// Tries to insert an element in the style sharing cache.
     ///
     /// Fails if we know it should never be in the cache.
     pub fn insert_if_possible(&mut self,
                               element: &E,
                               style: &ComputedValues,
-                              relations: StyleRelations,
-                              mut validation_data: ValidationData,
+                              validation_data: ValidationData,
                               dom_depth: usize) {
-        use selectors::matching::AFFECTED_BY_PRESENTATIONAL_HINTS;
-
         let parent = match element.traversal_parent() {
             Some(element) => element,
             None => {
                 debug!("Failing to insert to the cache: no parent element");
                 return;
             }
         };
 
@@ -520,23 +517,16 @@ impl<E: TElement> StyleSharingCandidateC
             return;
         }
 
         if box_style.specifies_animations() {
             debug!("Failing to insert to the cache: animations");
             return;
         }
 
-        // Take advantage of the information we've learned during
-        // selector-matching.
-        if !relations.intersects(AFFECTED_BY_PRESENTATIONAL_HINTS) {
-            debug_assert!(validation_data.pres_hints.as_ref().map_or(true, |v| v.is_empty()));
-            validation_data.pres_hints = Some(SmallVec::new());
-        }
-
         debug!("Inserting into cache: {:?} with parent {:?}", element, parent);
 
         if self.dom_depth != dom_depth {
             debug!("Clearing cache because depth changed from {:?} to {:?}, element: {:?}",
                    self.dom_depth, dom_depth, element);
             self.clear();
             self.dom_depth = dom_depth;
         }
--- a/servo/components/style/style_resolver.rs
+++ b/servo/components/style/style_resolver.rs
@@ -1,17 +1,17 @@
 /* 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/. */
 
 //! Style resolution for a given element or pseudo-element.
 
 use applicable_declarations::ApplicableDeclarationList;
 use cascade_info::CascadeInfo;
-use context::StyleContext;
+use context::{CascadeInputs, ElementCascadeInputs, StyleContext};
 use data::{ElementStyles, EagerPseudoStyles};
 use dom::TElement;
 use log::LogLevel::Trace;
 use matching::{CascadeVisitedMode, MatchMethods};
 use properties::{AnimationRules, CascadeFlags, ComputedValues};
 use properties::{IS_ROOT_ELEMENT, PROHIBIT_DISPLAY_CONTENTS, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP};
 use properties::{VISITED_DEPENDENT_ONLY, cascade};
 use rule_tree::StrongRuleNode;
@@ -37,21 +37,47 @@ struct MatchingResults {
     rule_node: StrongRuleNode,
     relevant_link_found: bool,
 }
 
 /// The primary style of an element or an element-backed pseudo-element.
 pub struct PrimaryStyle {
     /// The style per se.
     pub style: Arc<ComputedValues>,
+}
 
-    /// Whether a relevant link was found while computing this style.
-    ///
-    /// FIXME(emilio): Slightly out of place?
-    pub relevant_link_found: bool,
+fn with_default_parent_styles<E, F, R>(element: E, f: F) -> R
+where
+    E: TElement,
+    F: FnOnce(Option<&ComputedValues>, Option<&ComputedValues>) -> R,
+{
+    let parent_el = element.inheritance_parent();
+    let parent_data = parent_el.as_ref().and_then(|e| e.borrow_data());
+    let parent_style = parent_data.as_ref().map(|d| {
+        // Sometimes Gecko eagerly styles things without processing
+        // pending restyles first. In general we'd like to avoid this,
+        // but there can be good reasons (for example, needing to
+        // construct a frame for some small piece of newly-added
+        // content in order to do something specific with that frame,
+        // but not wanting to flush all of layout).
+        debug_assert!(cfg!(feature = "gecko") ||
+                      parent_el.unwrap().has_current_styles(d));
+        d.styles.primary()
+    });
+
+    let mut layout_parent_el = parent_el.clone();
+    let layout_parent_data;
+    let mut layout_parent_style = parent_style;
+    if parent_style.map_or(false, |s| s.is_display_contents()) {
+        layout_parent_el = Some(layout_parent_el.unwrap().layout_parent());
+        layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap();
+        layout_parent_style = Some(layout_parent_data.styles.primary());
+    }
+
+    f(parent_style.map(|s| &**s), layout_parent_style.map(|s| &**s))
 }
 
 impl<'a, 'ctx, 'le, E> StyleResolverForElement<'a, 'ctx, 'le, E>
 where
     'ctx: 'a,
     'le: 'ctx,
     E: TElement + MatchMethods + 'le,
 {
@@ -90,35 +116,35 @@ where
 
         let mut visited_style = None;
         let should_compute_visited_style =
             relevant_link_found ||
             parent_style.and_then(|s| s.get_visited_style()).is_some();
 
         if should_compute_visited_style {
             visited_style = Some(self.cascade_style(
-                visited_rules.as_ref().unwrap_or(&primary_results.rule_node),
+                visited_rules.as_ref(),
                 /* style_if_visited = */ None,
                 parent_style,
                 layout_parent_style,
                 CascadeVisitedMode::Visited,
                 /* pseudo = */ None,
             ));
         }
 
         let style = self.cascade_style(
-            &primary_results.rule_node,
+            Some(&primary_results.rule_node),
             visited_style,
             parent_style,
             layout_parent_style,
             CascadeVisitedMode::Unvisited,
             /* pseudo = */ None,
         );
 
-        PrimaryStyle { style, relevant_link_found, }
+        PrimaryStyle { style, }
     }
 
 
     /// Resolve the style of a given element, and all its eager pseudo-elements.
     pub fn resolve_style(
         &mut self,
         parent_style: Option<&ComputedValues>,
         layout_parent_style: Option<&ComputedValues>,
@@ -132,17 +158,17 @@ where
         if primary_style.style.get_box().clone_display() == display::none {
             return ElementStyles {
                 // FIXME(emilio): Remove the Option<>.
                 primary: Some(primary_style.style),
                 pseudos: pseudo_styles,
             }
         }
 
-        {
+        if self.element.implemented_pseudo_element().is_none() {
             let layout_parent_style_for_pseudo =
                 if primary_style.style.is_display_contents() {
                     layout_parent_style
                 } else {
                     Some(&*primary_style.style)
                 };
             SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
                 let pseudo_style = self.resolve_pseudo_style(
@@ -158,59 +184,158 @@ where
 
         ElementStyles {
             // FIXME(emilio): Remove the Option<>.
             primary: Some(primary_style.style),
             pseudos: pseudo_styles,
         }
     }
 
+    /// Resolve an element's styles with the default inheritance parent/layout
+    /// parents.
+    pub fn resolve_style_with_default_parents(&mut self) -> ElementStyles {
+        with_default_parent_styles(self.element, |parent_style, layout_parent_style| {
+            self.resolve_style(parent_style, layout_parent_style)
+        })
+    }
+
+    /// Cascade a set of rules, using the default parent for inheritance.
+    pub fn cascade_style_and_visited_with_default_parents(
+        &mut self,
+        inputs: CascadeInputs,
+    ) -> Arc<ComputedValues> {
+        with_default_parent_styles(self.element, |parent_style, layout_parent_style| {
+            self.cascade_style_and_visited(
+                inputs,
+                parent_style,
+                layout_parent_style,
+                /* pseudo = */ None
+            )
+        })
+    }
+
+    fn cascade_style_and_visited(
+        &mut self,
+        inputs: CascadeInputs,
+        parent_style: Option<&ComputedValues>,
+        layout_parent_style: Option<&ComputedValues>,
+        pseudo: Option<&PseudoElement>,
+    ) -> Arc<ComputedValues> {
+        let mut style_if_visited = None;
+        if parent_style.map_or(false, |s| s.get_visited_style().is_some()) ||
+            inputs.visited_rules.is_some() {
+            style_if_visited = Some(self.cascade_style(
+                inputs.visited_rules.as_ref(),
+                /* style_if_visited = */ None,
+                parent_style,
+                layout_parent_style,
+                CascadeVisitedMode::Visited,
+                pseudo,
+            ));
+        }
+        self.cascade_style(
+            inputs.rules.as_ref(),
+            style_if_visited,
+            parent_style,
+            layout_parent_style,
+            CascadeVisitedMode::Unvisited,
+            pseudo,
+        )
+    }
+
+    /// Cascade the element and pseudo-element styles with the default parents.
+    pub fn cascade_styles_with_default_parents(
+        &mut self,
+        inputs: ElementCascadeInputs,
+    ) -> ElementStyles {
+        use properties::longhands::display::computed_value::T as display;
+        with_default_parent_styles(self.element, move |parent_style, layout_parent_style| {
+            let primary_style = PrimaryStyle {
+                style: self.cascade_style_and_visited(
+                   inputs.primary,
+                   parent_style,
+                   layout_parent_style,
+                   /* pseudo = */ None,
+                ),
+            };
+
+            let mut pseudo_styles = EagerPseudoStyles::default();
+            let pseudo_array = inputs.pseudos.into_array();
+            if pseudo_array.is_none() ||
+                primary_style.style.get_box().clone_display() == display::none {
+                return ElementStyles {
+                    primary: Some(primary_style.style),
+                    pseudos: pseudo_styles,
+                }
+            }
+
+            {
+                let layout_parent_style_for_pseudo =
+                    if primary_style.style.is_display_contents() {
+                        layout_parent_style
+                    } else {
+                        Some(&*primary_style.style)
+                    };
+
+                for (i, mut inputs) in pseudo_array.unwrap().iter_mut().enumerate() {
+                    if let Some(inputs) = inputs.take() {
+                        let pseudo = PseudoElement::from_eager_index(i);
+                        pseudo_styles.set(
+                            &pseudo,
+                            self.cascade_style_and_visited(
+                                inputs,
+                                Some(&*primary_style.style),
+                                layout_parent_style_for_pseudo,
+                                Some(&pseudo),
+                            )
+                        )
+                    }
+                }
+            }
+
+            ElementStyles {
+                primary: Some(primary_style.style),
+                pseudos: pseudo_styles,
+            }
+        })
+    }
+
     fn resolve_pseudo_style(
         &mut self,
         pseudo: &PseudoElement,
         originating_element_style: &PrimaryStyle,
         layout_parent_style: Option<&ComputedValues>,
     ) -> Option<Arc<ComputedValues>> {
         let rules = self.match_pseudo(
             &originating_element_style.style,
             pseudo,
             VisitedHandlingMode::AllLinksUnvisited
         );
         let rules = match rules {
             Some(rules) => rules,
             None => return None,
         };
 
-        let mut visited_style = None;
-        if originating_element_style.relevant_link_found {
-            let visited_rules = self.match_pseudo(
+        let mut visited_rules = None;
+        if originating_element_style.style.get_visited_style().is_some() {
+            visited_rules = self.match_pseudo(
                 &originating_element_style.style,
                 pseudo,
                 VisitedHandlingMode::RelevantLinkVisited,
             );
-
-            if let Some(ref rules) = visited_rules {
-                visited_style = Some(self.cascade_style(
-                    rules,
-                    /* style_if_visited = */ None,
-                    Some(&originating_element_style.style),
-                    layout_parent_style,
-                    CascadeVisitedMode::Visited,
-                    Some(pseudo),
-                ));
-            }
         }
 
-        Some(self.cascade_style(
-            &rules,
-            visited_style,
+        Some(self.cascade_style_and_visited(
+            CascadeInputs {
+                rules: Some(rules),
+                visited_rules
+            },
             Some(&originating_element_style.style),
             layout_parent_style,
-            CascadeVisitedMode::Unvisited,
-            Some(pseudo)
+            Some(pseudo),
         ))
     }
 
     fn match_primary(
         &mut self,
         visited_handling: VisitedHandlingMode,
     ) -> MatchingResults {
         debug!("Match primary for {:?}, visited: {:?}",
@@ -330,48 +455,50 @@ where
             &self.context.shared.guards
         );
 
         Some(rule_node)
     }
 
     fn cascade_style(
         &mut self,
-        rules: &StrongRuleNode,
+        rules: Option<&StrongRuleNode>,
         style_if_visited: Option<Arc<ComputedValues>>,
-        parent_style: Option<&ComputedValues>,
+        mut parent_style: Option<&ComputedValues>,
         layout_parent_style: Option<&ComputedValues>,
         cascade_visited: CascadeVisitedMode,
         pseudo: Option<&PseudoElement>,
     ) -> Arc<ComputedValues> {
         let mut cascade_info = CascadeInfo::new();
         let mut cascade_flags = CascadeFlags::empty();
 
         if self.element.skip_root_and_item_based_display_fixup() {
             cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP);
         }
         if cascade_visited.visited_dependent_only() {
+            parent_style = parent_style.map(|s| {
+                s.get_visited_style().map(|s| &**s).unwrap_or(s)
+            });
             cascade_flags.insert(VISITED_DEPENDENT_ONLY);
         }
         if self.element.is_native_anonymous() || pseudo.is_some() {
             cascade_flags.insert(PROHIBIT_DISPLAY_CONTENTS);
         } else if self.element.is_root() {
             cascade_flags.insert(IS_ROOT_ELEMENT);
         }
 
         let values =
             Arc::new(cascade(
                 self.context.shared.stylist.device(),
-                rules,
+                rules.unwrap_or(self.context.shared.stylist.rule_tree().root()),
                 &self.context.shared.guards,
                 parent_style,
                 layout_parent_style,
                 style_if_visited,
                 Some(&mut cascade_info),
-                &*self.context.shared.error_reporter,
                 &self.context.thread_local.font_metrics_provider,
                 cascade_flags,
                 self.context.shared.quirks_mode
             ));
 
         cascade_info.finish(&self.element.as_node());
         values
     }
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -10,17 +10,16 @@ use bit_vec::BitVec;
 use context::{CascadeInputs, QuirksMode};
 use dom::TElement;
 use element_state::ElementState;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")]
 use gecko_bindings::structs::{nsIAtom, StyleRuleInclusion};
 use invalidation::element::invalidation_map::InvalidationMap;
 use invalidation::media_queries::{EffectiveMediaQueryResults, ToMediaListKey};
-use matching::CascadeVisitedMode;
 use media_queries::Device;
 use properties::{self, CascadeFlags, ComputedValues};
 use properties::{AnimationRules, PropertyDeclarationBlock};
 #[cfg(feature = "servo")]
 use properties::INHERIT_ALL;
 use rule_tree::{CascadeLevel, RuleTree, StyleSource};
 use selector_map::{SelectorMap, SelectorMapEntry};
 use selector_parser::{SelectorImpl, PseudoElement};
@@ -714,34 +713,40 @@ impl Stylist {
                                                     inputs: &CascadeInputs,
                                                     guards: &StylesheetGuards,
                                                     parent_style: &Arc<ComputedValues>,
                                                     font_metrics: &FontMetricsProvider)
                                                     -> Option<Arc<ComputedValues>>
     {
         // We may have only visited rules in cases when we are actually
         // resolving, not probing, pseudo-element style.
-        if !inputs.has_rules() && !inputs.has_visited_rules() {
+        if inputs.rules.is_none() && inputs.visited_rules.is_none() {
             return None
         }
 
         // We need to compute visited values if we have visited rules or if our
         // parent has visited values.
-        let visited_values = if inputs.has_visited_rules() || parent_style.get_visited_style().is_some() {
+        let visited_values = if inputs.visited_rules.is_some() || parent_style.get_visited_style().is_some() {
             // Slightly annoying: we know that inputs has either rules or
             // visited rules, but we can't do inputs.rules() up front because
             // maybe it just has visited rules, so can't unwrap_or.
-            let rule_node = match inputs.get_visited_rules() {
+            let rule_node = match inputs.visited_rules.as_ref() {
                 Some(rules) => rules,
-                None => inputs.rules()
+                None => inputs.rules.as_ref().unwrap(),
             };
             // We want to use the visited bits (if any) from our parent style as
             // our parent.
-            let mode = CascadeVisitedMode::Visited;
-            let inherited_style = mode.values(parent_style);
+            let inherited_style =
+                parent_style.get_visited_style().unwrap_or(&*parent_style);
+
+            // FIXME(emilio): The lack of layout_parent_style here could be
+            // worrying, but we're probably dropping the display fixup for
+            // pseudos other than before and after, so it's probably ok.
+            //
+            // (Though the flags don't indicate so!)
             let computed =
                 properties::cascade(&self.device,
                                     rule_node,
                                     guards,
                                     Some(inherited_style),
                                     Some(inherited_style),
                                     None,
                                     None,
@@ -751,17 +756,17 @@ impl Stylist {
 
             Some(Arc::new(computed))
         } else {
             None
         };
 
         // We may not have non-visited rules, if we only had visited ones.  In
         // that case we want to use the root rulenode for our non-visited rules.
-        let rules = inputs.get_rules().unwrap_or(self.rule_tree.root());
+        let rules = inputs.rules.as_ref().unwrap_or(self.rule_tree.root());
 
         // Read the comment on `precomputed_values_for_pseudo` to see why it's
         // difficult to assert that display: contents nodes never arrive here
         // (tl;dr: It doesn't apply for replaced elements and such, but the
         // computed value is still "contents").
         let computed =
             properties::cascade(&self.device,
                                 rules,
@@ -844,20 +849,20 @@ impl Stylist {
             &mut matching_context,
             &mut set_selector_flags
         );
 
         if !declarations.is_empty() {
             let rule_node =
                 self.rule_tree.compute_rule_node(&mut declarations, guards);
             debug_assert!(rule_node != *self.rule_tree.root());
-            inputs.set_rules(VisitedHandlingMode::AllLinksUnvisited, rule_node);
+            inputs.rules = Some(rule_node);
         }
 
-        if is_probe && !inputs.has_rules() {
+        if is_probe && inputs.rules.is_none() {
             // When probing, don't compute visited styles if we have no
             // unvisited styles.
             return inputs;
         }
 
         if matching_context.relevant_link_found {
             let mut declarations = ApplicableDeclarationList::new();
             let mut matching_context =
@@ -875,18 +880,17 @@ impl Stylist {
                                               &mut matching_context,
                                               &mut set_selector_flags);
             if !declarations.is_empty() {
                 let rule_node =
                     self.rule_tree.insert_ordered_rules_with_important(
                         declarations.into_iter().map(|a| a.order_and_level()),
                         guards);
                 if rule_node != *self.rule_tree.root() {
-                    inputs.set_rules(VisitedHandlingMode::RelevantLinkVisited,
-                                     rule_node);
+                    inputs.visited_rules = Some(rule_node);
                 }
             }
         }
 
         inputs
     }
 
     /// Set a given device, which may change the styles that apply to the
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -5,18 +5,19 @@
 //! Traversing the DOM tree; the bloom filter.
 
 use atomic_refcell::AtomicRefCell;
 use context::{ElementCascadeInputs, StyleContext, SharedStyleContext};
 use data::{ElementData, ElementStyles};
 use dom::{NodeInfo, OpaqueNode, TElement, TNode};
 use invalidation::element::restyle_hints::{RECASCADE_SELF, RECASCADE_DESCENDANTS, RestyleHint};
 use matching::{ChildCascadeRequirement, MatchMethods};
-use sharing::{StyleSharingBehavior, StyleSharingTarget};
+use sharing::StyleSharingTarget;
 use smallvec::SmallVec;
+use style_resolver::StyleResolverForElement;
 use stylist::RuleInclusion;
 
 /// A per-traversal-level chunk of data. This is sent down by the traversal, and
 /// currently only holds the dom depth for the bloom filter.
 ///
 /// NB: Keep this as small as possible, please!
 #[derive(Clone, Debug)]
 pub struct PerLevelTraversalData {
@@ -760,17 +761,18 @@ where
     let kind = data.restyle_kind(context.shared);
 
     debug!("compute_style: {:?} (kind={:?})", element, kind);
 
     if data.has_styles() {
         data.restyle.set_restyled();
     }
 
-    match kind {
+    let mut important_rules_changed = false;
+    let new_styles = match kind {
         MatchAndCascade => {
             debug_assert!(!context.shared.traversal_flags.for_animation_only(),
                           "MatchAndCascade shouldn't be processed during \
                            animation-only traversal");
             // Ensure the bloom filter is up to date.
             context.thread_local.bloom_filter
                    .insert_parents_recovering(element,
                                               traversal_data.current_dom_depth);
@@ -785,45 +787,72 @@ where
             if let StyleWasShared(index, had_damage) = sharing_result {
                 context.thread_local.statistics.styles_shared += 1;
                 context.thread_local.style_sharing_candidate_cache.touch(index);
                 return had_damage;
             }
 
             context.thread_local.statistics.elements_matched += 1;
 
+            important_rules_changed = true;
+
             // Perform the matching and cascading.
-            element.match_and_cascade(
-                context,
-                data,
-                StyleSharingBehavior::Allow
-            )
+            let new_styles =
+                StyleResolverForElement::new(element, context, RuleInclusion::All)
+                    .resolve_style_with_default_parents();
+
+            // If we previously tried to match this element against the cache,
+            // the revalidation match results will already be cached. Otherwise
+            // we'll have None, and compute them later on-demand.
+            //
+            // If we do have the results, grab them here to satisfy the borrow
+            // checker.
+            let validation_data =
+                context.thread_local
+                    .current_element_info
+                    .as_mut().unwrap()
+                    .validation_data
+                    .take();
+
+            let dom_depth = context.thread_local.bloom_filter.matching_depth();
+            context.thread_local
+                   .style_sharing_candidate_cache
+                   .insert_if_possible(
+                       &element,
+                       new_styles.primary(),
+                       validation_data,
+                       dom_depth
+                    );
+
+            new_styles
         }
         CascadeWithReplacements(flags) => {
             // Skipping full matching, load cascade inputs from previous values.
-            *context.cascade_inputs_mut() =
+            let mut cascade_inputs =
                 ElementCascadeInputs::new_from_element_data(data);
-            let important_rules_changed = element.replace_rules(flags, context);
-            element.cascade_primary_and_pseudos(
-                context,
-                data,
-                important_rules_changed
-            )
+            important_rules_changed =
+                element.replace_rules(flags, context, &mut cascade_inputs);
+            StyleResolverForElement::new(element, context, RuleInclusion::All)
+                .cascade_styles_with_default_parents(cascade_inputs)
         }
         CascadeOnly => {
             // Skipping full matching, load cascade inputs from previous values.
-            *context.cascade_inputs_mut() =
+            let cascade_inputs =
                 ElementCascadeInputs::new_from_element_data(data);
-            element.cascade_primary_and_pseudos(
-                context,
-                data,
-                /* important_rules_changed = */ false
-            )
+            StyleResolverForElement::new(element, context, RuleInclusion::All)
+                .cascade_styles_with_default_parents(cascade_inputs)
         }
-    }
+    };
+
+    element.finish_restyle(
+        context,
+        data,
+        new_styles,
+        important_rules_changed
+    )
 }
 
 fn preprocess_children<E, D>(
     context: &mut StyleContext<E>,
     element: E,
     propagated_hint: RestyleHint,
     reconstructed_ancestor: bool,
 )
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -659,42 +659,45 @@ pub extern "C" fn Servo_StyleSet_GetBase
                                                                  -> ServoComputedValuesStrong
 {
     use style::matching::MatchMethods;
     debug_assert!(!snapshots.is_null());
 
     let doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow();
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let guard = global_style_data.shared_lock.read();
-    let shared_context = create_shared_context(&global_style_data,
-                                               &guard,
-                                               &doc_data,
-                                               TraversalFlags::empty(),
-                                               unsafe { &*snapshots });
+
     let element = GeckoElement(element);
     let element_data = element.borrow_data().unwrap();
     let styles = &element_data.styles;
 
-    let pseudo = PseudoElement::from_pseudo_type(pseudo_type);
-    let pseudos = &styles.pseudos;
-    let pseudo_style = match pseudo {
-        Some(ref p) => {
-            let style = pseudos.get(p);
-            debug_assert!(style.is_some());
-            style
-        }
-        None => None,
+    if let Some(pseudo) = PseudoElement::from_pseudo_type(pseudo_type) {
+        // This style already doesn't have animations.
+        return styles
+            .pseudos
+            .get(&pseudo)
+            .expect("GetBaseComputedValuesForElement for an unexisting pseudo?")
+            .clone().into_strong();
+    }
+
+    let shared = create_shared_context(&global_style_data,
+                                       &guard,
+                                       &doc_data,
+                                       TraversalFlags::empty(),
+                                       unsafe { &*snapshots });
+    let mut tlc = ThreadLocalStyleContext::new(&shared);
+    let mut context = StyleContext {
+        shared: &shared,
+        thread_local: &mut tlc,
     };
 
-    let provider = get_metrics_provider_for_product();
-    element.get_base_style(&shared_context,
-                           &provider,
-                           styles.primary(),
-                           pseudo_style)
-           .into_strong()
+    element.get_base_style(
+        &mut context,
+        styles.primary(),
+    ).into_strong()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ComputedValues_ExtractAnimationValue(computed_values: ServoComputedValuesBorrowed,
                                                              property_id: nsCSSPropertyID)
                                                              -> RawServoAnimationValueStrong
 {
     let property = match AnimatableLonghand::from_nscsspropertyid(property_id) {