Bug 1370719 - Move match and cascade temporaries to CurrentElementInfo. r=bholley draft
authorJ. Ryan Stinnett <jryans@gmail.com>
Tue, 13 Jun 2017 12:51:37 -0500
changeset 597765 6f136f65ea1bdcb126f960d4e335af4b9548bfc3
parent 597764 1aba36ac3a53359e7f44f6979711e28644d31b0e
child 597766 e4230a2f5385f0e3b304deb1eae0292a4a7e3546
push id65023
push userbmo:jryans@gmail.com
push dateTue, 20 Jun 2017 22:59:41 +0000
reviewersbholley
bugs1370719
milestone56.0a1
Bug 1370719 - Move match and cascade temporaries to CurrentElementInfo. r=bholley Before this change, the `ComputedStyle` struct that is part of permanent style data per element holds 2 `StrongRuleNode`s (unvisited and visited) and 2 `Arc<ComputedValues>` (unvisited and visited). Both rule nodes and the visited values don't actually need to be here. This patch moves these 3 to new temporary storage in `CascadeInputs` on `CurrentElementInfo` during the match and cascade process. Rule nodes are pushed down inside the `ComputedValues` for later access after the cascade. (Visited values were already available there.) The permanent style data per element now has just the `Arc<ComputedValues>` for itself and eager pseudo-elements (plus the `RestyleHint`). MozReview-Commit-ID: 3wq52ERMpdi
servo/components/layout_thread/lib.rs
servo/components/script/layout_wrapper.rs
servo/components/script_layout_interface/lib.rs
servo/components/script_layout_interface/wrapper_traits.rs
servo/components/style/animation.rs
servo/components/style/context.rs
servo/components/style/data.rs
servo/components/style/dom.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/invalidation/stylesheets.rs
servo/components/style/matching.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/rule_tree/mod.rs
servo/components/style/sharing/checks.rs
servo/components/style/sharing/mod.rs
servo/components/style/stylist.rs
servo/components/style/traversal.rs
servo/components/style/values/specified/color.rs
servo/ports/geckolib/glue.rs
servo/tests/unit/stylo/size_of.rs
--- a/servo/components/layout_thread/lib.rs
+++ b/servo/components/layout_thread/lib.rs
@@ -1110,17 +1110,17 @@ impl LayoutThread {
             if data.document_stylesheets.iter().any(|sheet| sheet.dirty_on_viewport_size_change()) {
                 let mut iter = element.as_node().traverse_preorder();
 
                 let mut next = iter.next();
                 while let Some(node) = next {
                     if node.needs_dirty_on_viewport_size_changed() {
                         let el = node.as_element().unwrap();
                         if let Some(mut d) = element.mutate_data() {
-                            if d.has_styles() {
+                            if d.has_values() {
                                 d.restyle.hint.insert(RestyleHint::restyle_subtree());
                             }
                         }
                         if let Some(p) = el.parent_element() {
                             unsafe { p.note_dirty_descendant() };
                         }
 
                         next = iter.next_skipping_children();
@@ -1146,17 +1146,17 @@ impl LayoutThread {
             &guards,
             Some(ua_stylesheets),
             data.stylesheets_changed,
             /* author_styles_disabled = */ false,
             &mut extra_data);
         let needs_reflow = viewport_size_changed && !needs_dirtying;
         if needs_dirtying {
             if let Some(mut d) = element.mutate_data() {
-                if d.has_styles() {
+                if d.has_values() {
                     d.restyle.hint.insert(RestyleHint::restyle_subtree());
                 }
             }
         }
         if needs_reflow {
             if let Some(mut flow) = self.try_get_layout_root(element.as_node()) {
                 LayoutThread::reflow_all_nodes(FlowRef::deref_mut(&mut flow));
             }
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -897,17 +897,17 @@ impl<'ln> ThreadSafeLayoutNode for Servo
     #[inline]
     fn type_id_without_excluding_pseudo_elements(&self) -> LayoutNodeType {
         self.node.type_id()
     }
 
     fn parent_style(&self) -> Arc<ComputedValues> {
         let parent = self.node.parent_node().unwrap().as_element().unwrap();
         let parent_data = parent.get_data().unwrap().borrow();
-        parent_data.styles().primary.values().clone()
+        parent_data.values.primary().clone()
     }
 
     fn debug_id(self) -> usize {
         self.node.debug_id()
     }
 
     fn children(&self) -> LayoutIterator<Self::ChildrenIterator> {
         LayoutIterator(ThreadSafeLayoutNodeChildrenIterator::new(*self))
--- a/servo/components/script_layout_interface/lib.rs
+++ b/servo/components/script_layout_interface/lib.rs
@@ -60,17 +60,17 @@ pub struct StyleData {
 
     /// Information needed during parallel traversals.
     pub parallel: DomParallelInfo,
 }
 
 impl StyleData {
     pub fn new() -> Self {
         Self {
-            element_data: AtomicRefCell::new(ElementData::new(None)),
+            element_data: AtomicRefCell::new(ElementData::default()),
             parallel: DomParallelInfo::new(),
         }
     }
 }
 
 #[derive(Copy, Clone, HeapSizeOf)]
 pub struct OpaqueStyleAndLayoutData {
     // NB: We really store a `StyleAndLayoutData` here, so be careful!
--- a/servo/components/script_layout_interface/wrapper_traits.rs
+++ b/servo/components/script_layout_interface/wrapper_traits.rs
@@ -341,26 +341,26 @@ pub trait ThreadSafeLayoutElement: Clone
 
     fn style_data(&self) -> AtomicRef<ElementData>;
 
     #[inline]
     fn get_pseudo_element_type(&self) -> PseudoElementType<Option<display::T>>;
 
     #[inline]
     fn get_before_pseudo(&self) -> Option<Self> {
-        if self.style_data().styles().pseudos.has(&PseudoElement::Before) {
+        if self.style_data().values.pseudos.has(&PseudoElement::Before) {
             Some(self.with_pseudo(PseudoElementType::Before(None)))
         } else {
             None
         }
     }
 
     #[inline]
     fn get_after_pseudo(&self) -> Option<Self> {
-        if self.style_data().styles().pseudos.has(&PseudoElement::After) {
+        if self.style_data().values.pseudos.has(&PseudoElement::After) {
             Some(self.with_pseudo(PseudoElementType::After(None)))
         } else {
             None
         }
     }
 
     #[inline]
     fn get_details_summary_pseudo(&self) -> Option<Self> {
@@ -391,75 +391,75 @@ pub trait ThreadSafeLayoutElement: Clone
     /// has not yet been performed, fails.
     ///
     /// Unlike the version on TNode, this handles pseudo-elements.
     #[inline]
     fn style(&self, context: &SharedStyleContext) -> Arc<ServoComputedValues> {
         let data = self.style_data();
         match self.get_pseudo_element_type() {
             PseudoElementType::Normal => {
-                data.styles().primary.values().clone()
+                data.values.primary().clone()
             },
             other => {
                 // Precompute non-eagerly-cascaded pseudo-element styles if not
                 // cached before.
                 let style_pseudo = other.style_pseudo_element();
                 match style_pseudo.cascade_type() {
                     // Already computed during the cascade.
                     PseudoElementCascadeType::Eager => {
                         self.style_data()
-                            .styles().pseudos.get(&style_pseudo)
-                            .unwrap().values().clone()
+                            .values.pseudos.get(&style_pseudo)
+                            .unwrap().clone()
                     },
                     PseudoElementCascadeType::Precomputed => {
                         context.stylist.precomputed_values_for_pseudo(
                             &context.guards,
                             &style_pseudo,
-                            Some(data.styles().primary.values()),
+                            Some(data.values.primary()),
                             CascadeFlags::empty(),
                             &ServoMetricsProvider)
-                            .values().clone()
+                            .clone()
                     }
                     PseudoElementCascadeType::Lazy => {
                         context.stylist
                                .lazily_compute_pseudo_element_style(
                                    &context.guards,
                                    unsafe { &self.unsafe_get() },
                                    &style_pseudo,
                                    RuleInclusion::All,
-                                   data.styles().primary.values(),
+                                   data.values.primary(),
                                    &ServoMetricsProvider)
                                .unwrap()
-                               .values().clone()
+                               .clone()
                     }
                 }
             }
         }
     }
 
     #[inline]
     fn selected_style(&self) -> Arc<ServoComputedValues> {
         let data = self.style_data();
-        data.styles().pseudos
+        data.values.pseudos
             .get(&PseudoElement::Selection).map(|s| s)
-            .unwrap_or(&data.styles().primary)
-            .values().clone()
+            .unwrap_or(data.values.primary())
+            .clone()
     }
 
     /// Returns the already resolved style of the node.
     ///
     /// This differs from `style(ctx)` in that if the pseudo-element has not yet
     /// been computed it would panic.
     ///
     /// This should be used just for querying layout, or when we know the
     /// element style is precomputed, not from general layout itself.
     #[inline]
     fn resolved_style(&self) -> Arc<ServoComputedValues> {
         let data = self.style_data();
         match self.get_pseudo_element_type() {
             PseudoElementType::Normal
-                => data.styles().primary.values().clone(),
+                => data.values.primary().clone(),
             other
-                => data.styles().pseudos
-                       .get(&other.style_pseudo_element()).unwrap().values().clone(),
+                => data.values.pseudos
+                       .get(&other.style_pseudo_element()).unwrap().clone(),
         }
     }
 }
--- a/servo/components/style/animation.rs
+++ b/servo/components/style/animation.rs
@@ -475,17 +475,17 @@ pub fn start_transitions_if_applicable(n
         }
     }
 
     had_animations
 }
 
 fn compute_style_for_animation_step(context: &SharedStyleContext,
                                     step: &KeyframesStep,
-                                    previous_style: &ComputedValues,
+                                    previous_values: &ComputedValues,
                                     style_from_cascade: &ComputedValues,
                                     font_metrics_provider: &FontMetricsProvider)
                                     -> ComputedValues {
     match step.value {
         KeyframesStepValue::ComputedValues => style_from_cascade.clone(),
         KeyframesStepValue::Declarations { block: ref declarations } => {
             let guard = declarations.read_with(context.guards.author);
 
@@ -497,20 +497,21 @@ fn compute_style_for_animation_step(cont
                 guard.declarations().iter().rev()
                      .map(|&(ref decl, _importance)| (decl, CascadeLevel::Animations))
             };
 
             // This currently ignores visited styles, which seems acceptable,
             // as existing browsers don't appear to animate visited styles.
             let computed =
                 properties::apply_declarations(context.stylist.device(),
+                                               previous_values.rules(),
                                                /* is_root = */ false,
                                                iter,
-                                               previous_style,
-                                               previous_style,
+                                               previous_values,
+                                               previous_values,
                                                /* cascade_info = */ None,
                                                /* visited_style = */ None,
                                                &*context.error_reporter,
                                                font_metrics_provider,
                                                CascadeFlags::empty(),
                                                context.quirks_mode);
             computed
         }
--- a/servo/components/style/context.rs
+++ b/servo/components/style/context.rs
@@ -5,26 +5,25 @@
 //! 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::ElementData;
+use data::{EagerPseudoValues, ElementData};
 use dom::{OpaqueNode, TNode, TElement, SendElement};
 use error_reporting::ParseErrorReporter;
 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 properties::longhands::display::computed_value as display;
 use rule_tree::StrongRuleNode;
 use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, SnapshotMap};
 use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode};
 use shared_lock::StylesheetGuards;
 use sharing::{ValidationData, StyleSharingCandidateCache};
 use std::fmt;
 use std::ops::Add;
 #[cfg(feature = "servo")] use std::sync::Mutex;
@@ -139,29 +138,28 @@ pub struct SharedStyleContext<'a> {
 
 impl<'a> SharedStyleContext<'a> {
     /// Return a suitable viewport size in order to be used for viewport units.
     pub fn viewport_size(&self) -> Size2D<Au> {
         self.stylist.device().au_viewport_size()
     }
 }
 
-/// The structure that represents the result of style computation. This is
-/// effectively a tuple of rules and computed values, that is, the rule node,
-/// and the result of computing that rule node's rules, the `ComputedValues`.
+/// 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)]
-pub struct ComputedStyle {
+pub struct CascadeInputs {
     /// The rule node representing the ordered list of rules matched for this
     /// node.
-    pub rules: StrongRuleNode,
-
-    /// The computed values for each property obtained by cascading the
-    /// matched rules. This can only be none during a transient interval of
-    /// the styling algorithm, and callers can safely unwrap it.
-    pub values: Option<Arc<ComputedValues>>,
+    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
@@ -169,42 +167,78 @@ pub struct ComputedStyle {
     /// 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 ComputedStyle {
-    /// Trivially construct a new `ComputedStyle`.
-    pub fn new(rules: StrongRuleNode, values: Arc<ComputedValues>) -> Self {
-        ComputedStyle {
-            rules: rules,
-            values: Some(values),
+impl Default for CascadeInputs {
+    fn default() -> Self {
+        CascadeInputs {
+            rules: None,
             visited_rules: None,
             visited_values: None,
         }
     }
+}
+
+impl CascadeInputs {
+    /// Construct inputs from previous cascade results, if any.
+    fn new_from_values(values: &Arc<ComputedValues>) -> Self {
+        CascadeInputs {
+            rules: values.rules.clone(),
+            visited_rules: values.get_visited_style().and_then(|v| v.rules.clone()),
+            // Values will be re-cascaded if necessary, so this can be None.
+            visited_values: None,
+        }
+    }
 
-    /// Constructs a partial ComputedStyle, whose ComputedVaues will be filled
-    /// in later.
-    pub fn new_partial(rules: StrongRuleNode) -> Self {
-        ComputedStyle {
-            rules: rules,
-            values: None,
-            visited_rules: None,
-            visited_values: None,
+    /// Whether there are any rules.
+    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. 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),
         }
     }
 
-    /// Returns a reference to the ComputedValues. The values can only be null during
-    /// the styling algorithm, so this is safe to call elsewhere.
-    pub fn values(&self) -> &Arc<ComputedValues> {
-        self.values.as_ref().unwrap()
+    /// 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.
     pub fn has_visited_rules(&self) -> bool {
         self.visited_rules.is_some()
     }
 
     /// Gets a reference to the visited rule node, if any.
@@ -215,21 +249,21 @@ impl ComputedStyle {
     /// 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.get_visited_rules().unwrap()
+        self.visited_rules.as_ref().unwrap()
     }
 
     /// Sets the visited rule node, and returns whether it changed.
-    pub fn set_visited_rules(&mut self, rules: StrongRuleNode) -> bool {
+    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
     }
@@ -257,62 +291,91 @@ impl ComputedStyle {
 
     /// 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 ComputedStyle so that we can avoid the
+// We manually implement Debug for CascadeInputs so that we can avoid the
 // verbose stringification of ComputedValues for normal logging.
-impl fmt::Debug for ComputedStyle {
+impl fmt::Debug for CascadeInputs {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "ComputedStyle {{ rules: {:?}, values: {{..}} }}", self.rules)
+        write!(f, "CascadeInputs {{ rules: {:?}, visited_rules: {:?}, .. }}",
+               self.rules, self.visited_rules)
     }
 }
 
-/// A list of styles for eagerly-cascaded pseudo-elements. Lazily-allocated.
-#[derive(Clone, Debug)]
-pub struct EagerPseudoStyles(Option<Box<[Option<ComputedStyle>]>>);
+/// A list of cascade inputs for eagerly-cascaded pseudo-elements.
+/// The list is stored inline.
+#[derive(Debug)]
+pub struct EagerPseudoCascadeInputs(Option<[Option<CascadeInputs>; EAGER_PSEUDO_COUNT]>);
 
-impl EagerPseudoStyles {
-    /// Returns whether there are any pseudo styles.
+// Manually implement `Clone` here because the derived impl of `Clone` for
+// array types assumes the value inside is `Copy`.
+impl Clone for EagerPseudoCascadeInputs {
+    fn clone(&self) -> Self {
+        if self.0.is_none() {
+            return EagerPseudoCascadeInputs(None)
+        }
+        let self_inputs = self.0.as_ref().unwrap();
+        let mut inputs: [Option<CascadeInputs>; EAGER_PSEUDO_COUNT] = Default::default();
+        for i in 0..EAGER_PSEUDO_COUNT {
+            inputs[i] = self_inputs[i].clone();
+        }
+        EagerPseudoCascadeInputs(Some(inputs))
+    }
+}
+
+impl EagerPseudoCascadeInputs {
+    /// Construct inputs from previous cascade results, if any.
+    fn new_from_values(values: &EagerPseudoValues) -> Self {
+        EagerPseudoCascadeInputs(values.0.as_ref().map(|values| {
+            let mut inputs: [Option<CascadeInputs>; EAGER_PSEUDO_COUNT] = Default::default();
+            for i in 0..EAGER_PSEUDO_COUNT {
+                inputs[i] = values[i].as_ref().map(|v| CascadeInputs::new_from_values(v));
+            }
+            inputs
+        }))
+    }
+
+    /// Returns whether there are any pseudo inputs.
     pub fn is_empty(&self) -> bool {
         self.0.is_none()
     }
 
-    /// Returns a reference to the style for a given eager pseudo, if it exists.
-    pub fn get(&self, pseudo: &PseudoElement) -> Option<&ComputedStyle> {
+    /// 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 style for a given eager pseudo, if it exists.
-    pub fn get_mut(&mut self, pseudo: &PseudoElement) -> Option<&mut ComputedStyle> {
+    /// 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 EagerPseudoStyles has a ComputedStyle for |pseudo|.
+    /// 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, style: ComputedStyle) {
+    pub fn insert(&mut self, pseudo: &PseudoElement, value: CascadeInputs) {
         debug_assert!(!self.has(pseudo));
         if self.0.is_none() {
-            self.0 = Some(vec![None; EAGER_PSEUDO_COUNT].into_boxed_slice());
+            self.0 = Some(Default::default());
         }
-        self.0.as_mut().unwrap()[pseudo.eager_index()] = Some(style);
+        self.0.as_mut().unwrap()[pseudo.eager_index()] = Some(value);
     }
 
-    /// Removes a pseudo-element style if it exists, and returns it.
-    fn take(&mut self, pseudo: &PseudoElement) -> Option<ComputedStyle> {
+    /// 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;
         }
@@ -335,56 +398,58 @@ impl EagerPseudoStyles {
     /// 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 style) = self.get_mut(pseudo) {
-            style.rules = rules;
+        if let Some(mut inputs) = self.get_mut(pseudo) {
+            inputs.set_unvisited_rules(rules);
             return false
         }
-        self.insert(pseudo, ComputedStyle::new_partial(rules));
+        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 styles should have been added first.
+    /// 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 style = self.get_mut(pseudo).unwrap();
-        style.set_visited_rules(rules);
+        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 style) = self.get_mut(pseudo) {
-            style.take_visited_rules();
+        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.
@@ -423,60 +488,77 @@ impl EagerPseudoStyles {
             VisitedHandlingMode::AllLinksUnvisited => {
                 self.remove_unvisited_rules(&pseudo)
             },
             VisitedHandlingMode::RelevantLinkVisited => {
                 self.remove_visited_rules(&pseudo)
             },
         }
     }
+}
 
-    /// Returns whether this EagerPseudoStyles has the same set of
-    /// pseudos as the given one.
-    pub fn has_same_pseudos_as(&self, other: &EagerPseudoStyles) -> bool {
-        // We could probably just compare self.keys() to other.keys(), but that
-        // seems like it'll involve a bunch more moving stuff around and
-        // whatnot.
-        match (&self.0, &other.0) {
-            (&Some(ref our_arr), &Some(ref other_arr)) => {
-                for i in 0..EAGER_PSEUDO_COUNT {
-                    if our_arr[i].is_some() != other_arr[i].is_some() {
-                        return false
-                    }
-                }
-                true
-            },
-            (&None, &None) => true,
-            _ => false,
+/// 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>,
+    /// 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),
         }
     }
 }
 
-/// The styles associated with a node, including the styles for any
-/// pseudo-elements.
-#[derive(Clone, Debug)]
-pub struct ElementStyles {
-    /// The element's style.
-    pub primary: ComputedStyle,
-    /// A list of the styles for the element's eagerly-cascaded pseudo-elements.
-    pub pseudos: EagerPseudoStyles,
-}
-
-impl ElementStyles {
-    /// Trivially construct a new `ElementStyles`.
-    pub fn new(primary: ComputedStyle) -> Self {
-        ElementStyles {
-            primary: primary,
-            pseudos: EagerPseudoStyles(None),
+impl ElementCascadeInputs {
+    /// Construct inputs from previous cascade results, if any.
+    pub fn new_from_element_data(data: &ElementData) -> Self {
+        if !data.has_values() {
+            return ElementCascadeInputs::default()
+        }
+        ElementCascadeInputs {
+            primary: Some(CascadeInputs::new_from_values(data.values.primary())),
+            pseudos: EagerPseudoCascadeInputs::new_from_values(&data.values.pseudos),
         }
     }
 
-    /// Whether this element `display` value is `none`.
-    pub fn is_display_none(&self) -> bool {
-        self.primary.values().get_box().clone_display() == display::T::none
+    /// 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 {
@@ -486,16 +568,21 @@ 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.
@@ -780,19 +867,20 @@ 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(),
+            is_initial_style: !data.has_values(),
             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());
--- a/servo/components/style/data.rs
+++ b/servo/components/style/data.rs
@@ -7,352 +7,19 @@
 use arrayvec::ArrayVec;
 use context::SharedStyleContext;
 use dom::TElement;
 use invalidation::element::restyle_hints::RestyleHint;
 use properties::{AnimationRules, ComputedValues, PropertyDeclarationBlock};
 use properties::longhands::display::computed_value as display;
 use rule_tree::StrongRuleNode;
 use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, RestyleDamage};
-use selectors::matching::VisitedHandlingMode;
 use shared_lock::{Locked, StylesheetGuards};
-use std::fmt;
 use stylearc::Arc;
 
-/// The structure that represents the result of style computation. This is
-/// effectively a tuple of rules and computed values, that is, the rule node,
-/// and the result of computing that rule node's rules, the `ComputedValues`.
-#[derive(Clone)]
-pub struct ComputedStyle {
-    /// The rule node representing the ordered list of rules matched for this
-    /// node.
-    pub rules: StrongRuleNode,
-
-    /// The computed values for each property obtained by cascading the
-    /// matched rules. This can only be none during a transient interval of
-    /// the styling algorithm, and callers can safely unwrap it.
-    pub values: Option<Arc<ComputedValues>>,
-
-    /// 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 ComputedStyle {
-    /// Trivially construct a new `ComputedStyle`.
-    pub fn new(rules: StrongRuleNode, values: Arc<ComputedValues>) -> Self {
-        ComputedStyle {
-            rules: rules,
-            values: Some(values),
-            visited_rules: None,
-            visited_values: None,
-        }
-    }
-
-    /// Constructs a partial ComputedStyle, whose ComputedVaues will be filled
-    /// in later.
-    pub fn new_partial(rules: StrongRuleNode) -> Self {
-        ComputedStyle {
-            rules: rules,
-            values: None,
-            visited_rules: None,
-            visited_values: None,
-        }
-    }
-
-    /// Returns a reference to the ComputedValues. The values can only be null during
-    /// the styling algorithm, so this is safe to call elsewhere.
-    pub fn values(&self) -> &Arc<ComputedValues> {
-        self.values.as_ref().unwrap()
-    }
-
-    /// Whether there are any visited rules.
-    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.get_visited_rules().unwrap()
-    }
-
-    /// Sets the visited rule node, and returns whether it changed.
-    pub 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()
-    }
-
-    /// 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 ComputedStyle so that we can avoid the
-// verbose stringification of ComputedValues for normal logging.
-impl fmt::Debug for ComputedStyle {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "ComputedStyle {{ rules: {:?}, values: {{..}} }}", self.rules)
-    }
-}
-
-/// A list of styles for eagerly-cascaded pseudo-elements. Lazily-allocated.
-#[derive(Clone, Debug)]
-pub struct EagerPseudoStyles(Option<Box<[Option<ComputedStyle>]>>);
-
-impl EagerPseudoStyles {
-    /// Returns whether there are any pseudo styles.
-    pub fn is_empty(&self) -> bool {
-        self.0.is_none()
-    }
-
-    /// Returns a reference to the style for a given eager pseudo, if it exists.
-    pub fn get(&self, pseudo: &PseudoElement) -> Option<&ComputedStyle> {
-        debug_assert!(pseudo.is_eager());
-        self.0.as_ref().and_then(|p| p[pseudo.eager_index()].as_ref())
-    }
-
-    /// Returns a mutable reference to the style for a given eager pseudo, if it exists.
-    pub fn get_mut(&mut self, pseudo: &PseudoElement) -> Option<&mut ComputedStyle> {
-        debug_assert!(pseudo.is_eager());
-        self.0.as_mut().and_then(|p| p[pseudo.eager_index()].as_mut())
-    }
-
-    /// Returns true if the EagerPseudoStyles has a ComputedStyle for |pseudo|.
-    pub fn has(&self, pseudo: &PseudoElement) -> bool {
-        self.get(pseudo).is_some()
-    }
-
-    /// Inserts a pseudo-element. The pseudo-element must not already exist.
-    pub fn insert(&mut self, pseudo: &PseudoElement, style: ComputedStyle) {
-        debug_assert!(!self.has(pseudo));
-        if self.0.is_none() {
-            self.0 = Some(vec![None; EAGER_PSEUDO_COUNT].into_boxed_slice());
-        }
-        self.0.as_mut().unwrap()[pseudo.eager_index()] = Some(style);
-    }
-
-    /// Removes a pseudo-element style if it exists, and returns it.
-    fn take(&mut self, pseudo: &PseudoElement) -> Option<ComputedStyle> {
-        let result = match self.0.as_mut() {
-            None => return None,
-            Some(arr) => arr[pseudo.eager_index()].take(),
-        };
-        let empty = self.0.as_ref().unwrap().iter().all(|x| x.is_none());
-        if empty {
-            self.0 = None;
-        }
-        result
-    }
-
-    /// 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 style) = self.get_mut(pseudo) {
-            style.rules = rules;
-            return false
-        }
-        self.insert(pseudo, ComputedStyle::new_partial(rules));
-        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 styles 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 style = self.get_mut(pseudo).unwrap();
-        style.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 style) = self.get_mut(pseudo) {
-            style.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 whether this EagerPseudoStyles has the same set of
-    /// pseudos as the given one.
-    pub fn has_same_pseudos_as(&self, other: &EagerPseudoStyles) -> bool {
-        // We could probably just compare self.keys() to other.keys(), but that
-        // seems like it'll involve a bunch more moving stuff around and
-        // whatnot.
-        match (&self.0, &other.0) {
-            (&Some(ref our_arr), &Some(ref other_arr)) => {
-                for i in 0..EAGER_PSEUDO_COUNT {
-                    if our_arr[i].is_some() != other_arr[i].is_some() {
-                        return false
-                    }
-                }
-                true
-            },
-            (&None, &None) => true,
-            _ => false,
-        }
-    }
-}
-
-/// The styles associated with a node, including the styles for any
-/// pseudo-elements.
-#[derive(Clone, Debug)]
-pub struct ElementStyles {
-    /// The element's style.
-    pub primary: ComputedStyle,
-    /// A list of the styles for the element's eagerly-cascaded pseudo-elements.
-    pub pseudos: EagerPseudoStyles,
-}
-
-impl ElementStyles {
-    /// Trivially construct a new `ElementStyles`.
-    pub fn new(primary: ComputedStyle) -> Self {
-        ElementStyles {
-            primary: primary,
-            pseudos: EagerPseudoStyles(None),
-        }
-    }
-
-    /// Whether this element `display` value is `none`.
-    pub fn is_display_none(&self) -> bool {
-        self.primary.values().get_box().clone_display() == display::T::none
-    }
-}
-
 bitflags! {
     flags RestyleFlags: u8 {
         /// Whether the styles changed for this restyle.
         const WAS_RESTYLED = 1 << 0,
         /// Whether we reframed/reconstructed any ancestor or self.
         const ANCESTOR_WAS_RECONSTRUCTED = 1 << 1,
     }
 }
@@ -369,16 +36,22 @@ pub struct RestyleData {
     /// A few flags to have in mind.
     flags: RestyleFlags,
 
     /// The restyle damage, indicating what kind of layout changes are required
     /// afte restyling.
     pub damage: RestyleDamage,
 }
 
+impl Default for RestyleData {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
 impl RestyleData {
     fn new() -> Self {
         Self {
             hint: RestyleHint::empty(),
             flags: RestyleFlags::empty(),
             damage: RestyleDamage::empty(),
         }
     }
@@ -420,25 +93,155 @@ impl RestyleData {
     }
 
     /// Returns whether this element has been part of a restyle.
     pub fn contains_restyle_data(&self) -> bool {
         self.is_restyle() || !self.hint.is_empty() || !self.damage.is_empty()
     }
 }
 
+/// A list of computed values for eagerly-cascaded pseudo-elements.
+/// Lazily-allocated.
+#[derive(Clone, Debug)]
+pub struct EagerPseudoValues(pub Option<Box<[Option<Arc<ComputedValues>>]>>);
+
+impl EagerPseudoValues {
+    /// Returns whether there are any pseudo values.
+    pub fn is_empty(&self) -> bool {
+        self.0.is_none()
+    }
+
+    /// Returns a reference to the values for a given eager pseudo, if it exists.
+    pub fn get(&self, pseudo: &PseudoElement) -> Option<&Arc<ComputedValues>> {
+        debug_assert!(pseudo.is_eager());
+        self.0.as_ref().and_then(|p| p[pseudo.eager_index()].as_ref())
+    }
+
+    /// Returns a mutable reference to the values for a given eager pseudo, if it exists.
+    pub fn get_mut(&mut self, pseudo: &PseudoElement) -> Option<&mut Arc<ComputedValues>> {
+        debug_assert!(pseudo.is_eager());
+        self.0.as_mut().and_then(|p| p[pseudo.eager_index()].as_mut())
+    }
+
+    /// Returns true if the EagerPseudoStyles has the values for |pseudo|.
+    pub fn has(&self, pseudo: &PseudoElement) -> bool {
+        self.get(pseudo).is_some()
+    }
+
+    /// Sets the values for the eager pseudo.
+    pub fn set(&mut self, pseudo: &PseudoElement, value: Arc<ComputedValues>) {
+        if self.0.is_none() {
+            self.0 = Some(vec![None; EAGER_PSEUDO_COUNT].into_boxed_slice());
+        }
+        self.0.as_mut().unwrap()[pseudo.eager_index()] = Some(value);
+    }
+
+    /// Inserts a pseudo-element. The pseudo-element must not already exist.
+    pub fn insert(&mut self, pseudo: &PseudoElement, value: Arc<ComputedValues>) {
+        debug_assert!(!self.has(pseudo));
+        self.set(pseudo, value);
+    }
+
+    /// Removes a pseudo-element values if it exists, and returns it.
+    pub fn take(&mut self, pseudo: &PseudoElement) -> Option<Arc<ComputedValues>> {
+        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
+    }
+
+    /// Returns whether this map has the same set of pseudos as the given one.
+    pub fn has_same_pseudos_as(&self, other: &Self) -> bool {
+        // We could probably just compare self.keys() to other.keys(), but that
+        // seems like it'll involve a bunch more moving stuff around and
+        // whatnot.
+        match (&self.0, &other.0) {
+            (&Some(ref our_arr), &Some(ref other_arr)) => {
+                for i in 0..EAGER_PSEUDO_COUNT {
+                    if our_arr[i].is_some() != other_arr[i].is_some() {
+                        return false
+                    }
+                }
+                true
+            },
+            (&None, &None) => true,
+            _ => false,
+        }
+    }
+}
+
+/// The values associated with a node, including the values for any
+/// pseudo-elements.
+#[derive(Clone, Debug)]
+pub struct ElementValues {
+    /// The element's computed values.
+    pub primary: Option<Arc<ComputedValues>>,
+    /// The computed values for the element's eagerly-cascaded pseudo-elements.
+    pub pseudos: EagerPseudoValues,
+}
+
+impl Default for ElementValues {
+    /// Construct an empty `ElementValues`.
+    fn default() -> Self {
+        ElementValues {
+            primary: None,
+            pseudos: EagerPseudoValues(None),
+        }
+    }
+}
+
+impl ElementValues {
+    /// Returns the primary computed values.
+    pub fn get_primary(&self) -> Option<&Arc<ComputedValues>> {
+        self.primary.as_ref()
+    }
+
+    /// Returns the mutable primary computed values.
+    pub fn get_primary_mut(&mut self) -> Option<&mut Arc<ComputedValues>> {
+        self.primary.as_mut()
+    }
+
+    /// Returns the primary computed values.  Panic if no values available.
+    pub fn primary(&self) -> &Arc<ComputedValues> {
+        self.primary.as_ref().unwrap()
+    }
+
+    /// Whether this element `display` value is `none`.
+    pub fn is_display_none(&self) -> bool {
+        self.primary().get_box().clone_display() == display::T::none
+    }
+}
+
 /// Style system data associated with an Element.
 ///
 /// In Gecko, this hangs directly off the Element. Servo, this is embedded
 /// inside of layout data, which itself hangs directly off the Element. In
 /// both cases, it is wrapped inside an AtomicRefCell to ensure thread safety.
-#[derive(Debug)]
+#[derive(Debug, Default)]
 pub struct ElementData {
-    /// The computed styles for the element and its pseudo-elements.
-    styles: Option<ElementStyles>,
+    /// The computed values for the element and its pseudo-elements.
+    pub values: ElementValues,
 
     /// Restyle state.
     pub restyle: RestyleData,
 }
 
 /// The kind of restyle that a single element should do.
 #[derive(Debug)]
 pub enum RestyleKind {
@@ -449,21 +252,21 @@ pub enum RestyleKind {
     /// attribute, or animation rules.
     CascadeWithReplacements(RestyleHint),
     /// We only need to recascade, for example, because only inherited
     /// properties in the parent changed.
     CascadeOnly,
 }
 
 impl ElementData {
-    /// Borrows both styles and restyle mutably at the same time.
-    pub fn styles_and_restyle_mut(
+    /// Borrows both values and restyle mutably at the same time.
+    pub fn values_and_restyle_mut(
         &mut self
-    ) -> (&mut ElementStyles, &mut RestyleData) {
-        (self.styles.as_mut().unwrap(),
+    ) -> (&mut ElementValues, &mut RestyleData) {
+        (&mut self.values,
          &mut self.restyle)
     }
 
     /// Invalidates style for this element, its descendants, and later siblings,
     /// based on the snapshot of the element that we took when attributes or
     /// state changed.
     pub fn invalidate_style_if_needed<'a, E: TElement>(
         &mut self,
@@ -487,43 +290,34 @@ impl ElementData {
                 shared_context,
             );
             invalidator.invalidate();
             unsafe { element.set_handled_snapshot() }
             debug_assert!(element.handled_snapshot());
         }
     }
 
-
-    /// Trivially construct an ElementData.
-    pub fn new(existing: Option<ElementStyles>) -> Self {
-        ElementData {
-            styles: existing,
-            restyle: RestyleData::new(),
-        }
-    }
-
-    /// Returns true if this element has a computed style.
-    pub fn has_styles(&self) -> bool {
-        self.styles.is_some()
+    /// Returns true if this element has computed values.
+    pub fn has_values(&self) -> bool {
+        self.values.primary.is_some()
     }
 
     /// Returns whether we have any outstanding style invalidation.
     pub fn has_invalidations(&self) -> bool {
         self.restyle.hint.has_self_invalidations()
     }
 
     /// Returns the kind of restyling that we're going to need to do on this
     /// element, based of the stored restyle hint.
     pub fn restyle_kind(&self,
                         shared_context: &SharedStyleContext)
                         -> RestyleKind {
-        debug_assert!(!self.has_styles() || self.has_invalidations(),
+        debug_assert!(!self.has_values() || self.has_invalidations(),
                       "Should've stopped earlier");
-        if !self.has_styles() {
+        if !self.has_values() {
             debug_assert!(!shared_context.traversal_flags.for_animation_only(),
                           "Unstyled element shouldn't be traversed during \
                            animation-only traversal");
             return RestyleKind::MatchAndCascade;
         }
 
         let hint = self.restyle.hint;
         if shared_context.traversal_flags.for_animation_only() {
@@ -545,97 +339,56 @@ impl ElementData {
             return RestyleKind::CascadeWithReplacements(hint & RestyleHint::replacements());
         }
 
         debug_assert!(hint.has_recascade_self(),
                       "We definitely need to do something!");
         return RestyleKind::CascadeOnly;
     }
 
-    /// Gets the element styles, if any.
-    pub fn get_styles(&self) -> Option<&ElementStyles> {
-        self.styles.as_ref()
-    }
-
-    /// Gets the element styles. Panic if the element has never been styled.
-    pub fn styles(&self) -> &ElementStyles {
-        self.styles.as_ref().expect("Calling styles() on unstyled ElementData")
-    }
-
-    /// Gets a mutable reference to the element styles, if any.
-    pub fn get_styles_mut(&mut self) -> Option<&mut ElementStyles> {
-        self.styles.as_mut()
-    }
-
-    /// Gets a mutable reference to the element styles. Panic if the element has
-    /// never been styled.
-    pub fn styles_mut(&mut self) -> &mut ElementStyles {
-        self.styles.as_mut().expect("Calling styles_mut() on unstyled ElementData")
-    }
-
-    /// Sets the computed element styles.
-    pub fn set_styles(&mut self, styles: ElementStyles) {
-        self.styles = Some(styles);
-    }
-
-    /// Sets the computed element rules, and returns whether the rules changed.
-    pub fn set_primary_rules(&mut self, rules: StrongRuleNode) -> bool {
-        if !self.has_styles() {
-            self.set_styles(ElementStyles::new(ComputedStyle::new_partial(rules)));
-            return true;
-        }
-
-        if self.styles().primary.rules == rules {
-            return false;
-        }
-
-        self.styles_mut().primary.rules = rules;
-        true
-    }
-
     /// Return true if important rules are different.
     /// We use this to make sure the cascade of off-main thread animations is correct.
     /// Note: Ignore custom properties for now because we only support opacity and transform
     ///       properties for animations running on compositor. Actually, we only care about opacity
     ///       and transform for now, but it's fine to compare all properties and let the user
     ///       the check which properties do they want.
     ///       If it costs too much, get_properties_overriding_animations() should return a set
     ///       containing only opacity and transform properties.
     pub fn important_rules_are_different(&self,
                                          rules: &StrongRuleNode,
                                          guards: &StylesheetGuards) -> bool {
-        debug_assert!(self.has_styles());
+        debug_assert!(self.has_values());
         let (important_rules, _custom) =
-            self.styles().primary.rules.get_properties_overriding_animations(&guards);
+            self.values.primary().rules().get_properties_overriding_animations(&guards);
         let (other_important_rules, _custom) = rules.get_properties_overriding_animations(&guards);
         important_rules != other_important_rules
     }
 
     /// Drops any restyle state from the element.
     pub fn clear_restyle_state(&mut self) {
         self.restyle.clear();
     }
 
     /// Returns SMIL overriden value if exists.
     pub fn get_smil_override(&self) -> Option<&Arc<Locked<PropertyDeclarationBlock>>> {
         if cfg!(feature = "servo") {
             // Servo has no knowledge of a SMIL rule, so just avoid looking for it.
             return None;
         }
 
-        match self.get_styles() {
-            Some(s) => s.primary.rules.get_smil_animation_rule(),
+        match self.values.get_primary() {
+            Some(v) => v.rules().get_smil_animation_rule(),
             None => None,
         }
     }
 
     /// Returns AnimationRules that has processed during animation-only restyles.
     pub fn get_animation_rules(&self) -> AnimationRules {
         if cfg!(feature = "servo") {
             return AnimationRules(None, None)
         }
 
-        match self.get_styles() {
-            Some(s) => s.primary.rules.get_animation_rules(),
+        match self.values.get_primary() {
+            Some(v) => v.rules().get_animation_rules(),
             None => AnimationRules(None, None),
         }
     }
 }
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -212,18 +212,17 @@ fn fmt_with_data<N: TNode>(f: &mut fmt::
         write!(f, "{:?}", n)
     }
 }
 
 fn fmt_with_data_and_primary_values<N: TNode>(f: &mut fmt::Formatter, n: N) -> fmt::Result {
     if let Some(el) = n.as_element() {
         let dd = el.has_dirty_descendants();
         let data = el.borrow_data();
-        let styles = data.as_ref().and_then(|d| d.get_styles());
-        let values = styles.map(|s| s.primary.values());
+        let values = data.as_ref().and_then(|d| d.values.get_primary());
         write!(f, "{:?} dd={} data={:?} values={:?}", el, dd, &data, values)
     } else {
         write!(f, "{:?}", n)
     }
 }
 
 fn fmt_subtree<F, N: TNode>(f: &mut fmt::Formatter, stringify: &F, n: N, indent: u32)
                             -> fmt::Result
@@ -444,17 +443,17 @@ pub trait TElement : Eq + PartialEq + De
     unsafe fn set_handled_snapshot(&self);
 
     /// Returns whether the element's styles are up-to-date.
     fn has_current_styles(&self, data: &ElementData) -> bool {
         if self.has_snapshot() && !self.handled_snapshot() {
             return false;
         }
 
-        data.has_styles() && !data.has_invalidations()
+        data.has_values() && !data.has_invalidations()
     }
 
     /// Flags an element and its ancestors with a given `DescendantsBit`.
     ///
     /// TODO(emilio): We call this conservatively from restyle_element_internal
     /// because we never flag unstyled stuff. A different setup for this may be
     /// a bit cleaner, but it's probably not worth to invest on it right now
     /// unless necessary.
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -510,34 +510,34 @@ impl<'le> GeckoElement<'le> {
     ///
     /// Only safe to call with exclusive access to the element, given otherwise
     /// it could race to allocate and leak.
     pub unsafe fn ensure_data(&self) -> &AtomicRefCell<ElementData> {
         match self.get_data() {
             Some(x) => x,
             None => {
                 debug!("Creating ElementData for {:?}", self);
-                let ptr = Box::into_raw(Box::new(AtomicRefCell::new(ElementData::new(None))));
+                let ptr = Box::into_raw(Box::new(AtomicRefCell::new(ElementData::default())));
                 self.0.mServoData.set(ptr);
                 unsafe { &* ptr }
             },
         }
     }
 
     /// Sets the specified element data, return any existing data.
     ///
     /// Like `ensure_data`, only safe to call with exclusive access to the
     /// element.
     pub unsafe fn set_data(&self, replace_data: Option<ElementData>) -> Option<ElementData> {
         match (self.get_data(), replace_data) {
             (Some(old), Some(replace_data)) => {
                 Some(mem::replace(old.borrow_mut().deref_mut(), replace_data))
             }
             (Some(old), None) => {
-                let old_data = mem::replace(old.borrow_mut().deref_mut(), ElementData::new(None));
+                let old_data = mem::replace(old.borrow_mut().deref_mut(), ElementData::default());
                 self.0.mServoData.set(ptr::null_mut());
                 Some(old_data)
             }
             (None, Some(replace_data)) => {
                 let ptr = Box::into_raw(Box::new(AtomicRefCell::new(replace_data)));
                 self.0.mServoData.set(ptr);
                 None
             }
@@ -975,17 +975,17 @@ impl<'le> TElement for GeckoElement<'le>
     fn update_animations(&self,
                          before_change_style: Option<Arc<ComputedValues>>,
                          tasks: UpdateAnimationsTasks) {
         // We have to update animations even if the element has no computed
         // style since it means the element is in a display:none subtree, we
         // should destroy all CSS animations in display:none subtree.
         let computed_data = self.borrow_data();
         let computed_values =
-            computed_data.as_ref().map(|d| d.styles().primary.values());
+            computed_data.as_ref().map(|d| d.values.primary());
         let computed_values_opt =
             computed_values.map(|v| *HasArcFFI::arc_as_borrowed(v));
         let before_change_values =
             before_change_style.as_ref().map(|v| *HasArcFFI::arc_as_borrowed(v));
         unsafe {
             Gecko_UpdateAnimations(self.0,
                                    before_change_values,
                                    computed_values_opt,
--- a/servo/components/style/invalidation/stylesheets.rs
+++ b/servo/components/style/invalidation/stylesheets.rs
@@ -156,17 +156,17 @@ impl StylesheetInvalidationSet {
     fn process_invalidations_in_subtree<E>(&self, element: E) -> bool
         where E: TElement,
     {
         let mut data = match element.mutate_data() {
             Some(data) => data,
             None => return false,
         };
 
-        if !data.has_styles() {
+        if !data.has_values() {
             return false;
         }
 
         if data.restyle.hint.contains_subtree() {
             debug!("process_invalidations_in_subtree: {:?} was already invalid",
                    element);
             return false;
         }
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -4,18 +4,18 @@
 
 //! High-level interface to CSS selector matching.
 
 #![allow(unsafe_code)]
 #![deny(missing_docs)]
 
 use applicable_declarations::ApplicableDeclarationList;
 use cascade_info::CascadeInfo;
-use context::{SelectorFlagsMap, SharedStyleContext, StyleContext};
-use data::{ComputedStyle, ElementData, RestyleData};
+use context::{CascadeInputs, SelectorFlagsMap, SharedStyleContext, StyleContext};
+use data::{ElementData, ElementValues, RestyleData};
 use dom::{TElement, TNode};
 use font_metrics::FontMetricsProvider;
 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::{ALLOW_SET_ROOT_FONT_SIZE, PROHIBIT_DISPLAY_CONTENTS, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP};
 use properties::{AnimationRules, CascadeFlags, ComputedValues};
@@ -137,69 +137,109 @@ 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
+/// Various helper methods to ease navigating the input storage locations
 /// depending on the current cascade mode.
 impl CascadeVisitedMode {
     /// Returns whether there is a rule node based on the cascade mode.
-    fn has_rules(&self, style: &ComputedStyle) -> bool {
+    fn has_rules(&self, inputs: &CascadeInputs) -> bool {
         match *self {
-            CascadeVisitedMode::Unvisited => true,
-            CascadeVisitedMode::Visited => style.has_visited_rules(),
+            CascadeVisitedMode::Unvisited => inputs.has_rules(),
+            CascadeVisitedMode::Visited => inputs.has_visited_rules(),
         }
     }
 
     /// Returns the rule node based on the cascade mode.
-    fn rules<'a>(&self, style: &'a ComputedStyle) -> &'a StrongRuleNode {
+    fn rules<'a>(&self, inputs: &'a CascadeInputs) -> &'a StrongRuleNode {
         match *self {
-            CascadeVisitedMode::Unvisited => &style.rules,
-            CascadeVisitedMode::Visited => style.visited_rules(),
+            CascadeVisitedMode::Unvisited => inputs.rules(),
+            CascadeVisitedMode::Visited => inputs.visited_rules(),
         }
     }
 
     /// Returns a mutable rules node based on the cascade mode, if any.
-    fn get_rules_mut<'a>(&self, style: &'a mut ComputedStyle) -> Option<&'a mut StrongRuleNode> {
+    fn get_rules_mut<'a>(&self, inputs: &'a mut CascadeInputs) -> Option<&'a mut StrongRuleNode> {
         match *self {
-            CascadeVisitedMode::Unvisited => Some(&mut style.rules),
-            CascadeVisitedMode::Visited => style.get_visited_rules_mut(),
+            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.
-    fn values<'a>(&self, style: &'a ComputedStyle) -> &'a Arc<ComputedValues> {
-        let mut values = style.values();
-
+    fn values<'a>(&self, values: &'a Arc<ComputedValues>) -> &'a Arc<ComputedValues> {
         if *self == CascadeVisitedMode::Visited && values.get_visited_style().is_some() {
-            values = values.visited_style();
+            return values.visited_style();
         }
 
         values
     }
 
-    /// Set the computed values based on the cascade mode.
-    fn set_values(&self, style: &mut ComputedStyle, values: Arc<ComputedValues>) {
+    /// Set the primary computed values based on the cascade mode.
+    fn set_primary_values(&self,
+                          el_values: &mut ElementValues,
+                          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 => style.values = Some(values),
-            CascadeVisitedMode::Visited => style.set_visited_values(values),
+            CascadeVisitedMode::Unvisited => el_values.primary = Some(values),
+            CascadeVisitedMode::Visited => inputs.set_visited_values(values),
         }
     }
 
-    /// Take the computed values based on the cascade mode.
-    fn take_values(&self, style: &mut ComputedStyle) -> Option<Arc<ComputedValues>> {
+    /// Set the primary computed values based on the cascade mode.
+    fn set_pseudo_values(&self,
+                         el_values: &mut ElementValues,
+                         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 => style.values.take(),
-            CascadeVisitedMode::Visited => style.take_visited_values(),
+            CascadeVisitedMode::Unvisited => el_values.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,
+                           el_values: &mut ElementValues,
+                           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 => el_values.primary.take(),
+            CascadeVisitedMode::Visited => inputs.take_visited_values(),
+        }
+    }
+
+    /// Take the pseudo computed values based on the cascade mode.
+    fn take_pseudo_values(&self,
+                          el_values: &mut ElementValues,
+                          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 => el_values.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.
     fn visited_values_for_insertion(&self) -> bool {
         *self == CascadeVisitedMode::Unvisited
     }
@@ -241,29 +281,29 @@ trait PrivateMatchMethods: TElement {
         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.values().is_display_contents();
+                current.borrow_data().unwrap().values.primary().is_display_contents();
 
             if !is_display_contents {
                 return current;
             }
         }
     }
 
     fn cascade_with_rules(&self,
                           shared_context: &SharedStyleContext,
                           font_metrics_provider: &FontMetricsProvider,
                           rule_node: &StrongRuleNode,
-                          primary_style: &ComputedStyle,
+                          primary_values: Option<&Arc<ComputedValues>>,
                           cascade_target: CascadeTarget,
                           cascade_visited: CascadeVisitedMode,
                           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)
@@ -288,33 +328,33 @@ trait PrivateMatchMethods: TElement {
                     // 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
+                    d.values.primary()
                 });
                 parent_style.map(|s| cascade_visited.values(s))
             }
             CascadeTarget::EagerPseudo => {
                 parent_el = Some(self.clone());
-                Some(cascade_visited.values(primary_style))
+                Some(cascade_visited.values(primary_values.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));
+            layout_parent_style = Some(cascade_visited.values(layout_parent_data.values.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.
         //
@@ -345,22 +385,23 @@ trait PrivateMatchMethods: TElement {
                              shared_context.quirks_mode));
 
         cascade_info.finish(&self.as_node());
         values
     }
 
     fn cascade_internal(&self,
                         context: &StyleContext<Self>,
-                        primary_style: &ComputedStyle,
-                        eager_pseudo_style: Option<&ComputedStyle>,
+                        primary_values: Option<&Arc<ComputedValues>>,
+                        primary_inputs: &CascadeInputs,
+                        eager_pseudo_inputs: Option<&CascadeInputs>,
                         cascade_visited: CascadeVisitedMode)
                         -> Arc<ComputedValues> {
         if let Some(pseudo) = self.implemented_pseudo_element() {
-            debug_assert!(eager_pseudo_style.is_none());
+            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
@@ -371,100 +412,109 @@ trait PrivateMatchMethods: TElement {
             // 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() ||
-                   primary_style.rules.get_animation_rules().is_empty() {
+                   primary_inputs.rules().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);
+                    let pseudo_values =
+                        parent_data.values.pseudos.get(&pseudo).unwrap();
+                    let values = cascade_visited.values(pseudo_values);
                     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_style {
+            match eager_pseudo_inputs {
                 Some(ref s) => s.clone_visited_values(),
-                None => primary_style.clone_visited_values(),
+                None => primary_inputs.clone_visited_values(),
             }
         } else {
             None
         };
 
         // Grab the rule node.
-        let style = eager_pseudo_style.unwrap_or(primary_style);
-        let rule_node = cascade_visited.rules(style);
-        let cascade_target = if eager_pseudo_style.is_some() {
+        let inputs = eager_pseudo_inputs.unwrap_or(primary_inputs);
+        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,
+                                primary_values,
                                 cascade_target,
                                 cascade_visited,
                                 visited_values_to_insert)
     }
 
     /// Computes values and damage for the primary style of an element, setting
     /// them on the ElementData.
     fn cascade_primary(&self,
                        context: &mut StyleContext<Self>,
                        data: &mut ElementData,
                        important_rules_changed: bool,
                        cascade_visited: CascadeVisitedMode)
                        -> ChildCascadeRequirement {
         debug!("Cascade primary for {:?}, visited: {:?}", self, cascade_visited);
 
-        // Collect some values.
-        let (mut styles, restyle) = data.styles_and_restyle_mut();
-        let mut primary_style = &mut styles.primary;
-        // If there was no relevant link, 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(primary_style) {
-            return ChildCascadeRequirement::CanSkipCascade
-        }
-        let mut old_values = cascade_visited.take_values(primary_style);
+        let mut old_values = cascade_visited.take_primary_values(
+            &mut data.values,
+            context.thread_local.current_element_info
+                                .as_mut().unwrap()
+                                .cascade_inputs.primary_mut()
+        );
+
+        let mut new_values = {
+            let primary_inputs = context.thread_local.current_element_info
+                                        .as_ref().unwrap()
+                                        .cascade_inputs.primary();
 
-        // Compute the new values.
-        let mut new_values = self.cascade_internal(context,
-                                                   primary_style,
-                                                   None,
-                                                   cascade_visited);
+            // If there was no relevant link, 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(primary_inputs) {
+                return ChildCascadeRequirement::CanSkipCascade
+            }
+
+            // Compute the new values.
+            self.cascade_internal(context,
+                                  None,
+                                  primary_inputs,
+                                  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,
-                                    primary_style,
                                     important_rules_changed);
         }
 
         let mut child_cascade_requirement =
             ChildCascadeRequirement::CanSkipCascade;
         if cascade_visited.should_accumulate_damage() {
             child_cascade_requirement =
                 self.accumulate_damage(&context.shared,
-                                       restyle,
+                                       &mut data.restyle,
                                        old_values.as_ref().map(|v| v.as_ref()),
                                        &new_values,
                                        None);
 
             // Handle root font-size changes.
             if self.is_root() && !self.is_native_anonymous() {
                 // The new root font-size has already been updated on the Device
                 // in properties::apply_declarations.
@@ -477,80 +527,109 @@ trait PrivateMatchMethods: TElement {
                 if old_values.map_or(false, |v| v.get_font().clone_font_size() != new_font_size) &&
                    device.used_root_font_size() {
                     child_cascade_requirement = ChildCascadeRequirement::MustCascadeDescendants;
                 }
             }
         }
 
         // Set the new computed values.
-        cascade_visited.set_values(primary_style, new_values);
+        let primary_inputs = context.thread_local.current_element_info
+                                    .as_mut().unwrap()
+                                    .cascade_inputs.primary_mut();
+        cascade_visited.set_primary_values(&mut data.values,
+                                           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 (mut styles, restyle) = data.styles_and_restyle_mut();
-        let mut pseudo_style = styles.pseudos.get_mut(pseudo).unwrap();
-        // If there was no relevant link, 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_style) {
-            return
-        }
-        let old_values = cascade_visited.take_values(pseudo_style);
+
+        let old_values = cascade_visited.take_pseudo_values(
+            &mut data.values,
+            context.thread_local.current_element_info
+                   .as_mut().unwrap()
+                   .cascade_inputs.pseudos.get_mut(pseudo).unwrap(),
+            pseudo
+        );
+
+        let new_values = {
+            let info = context.thread_local.current_element_info
+                              .as_ref().unwrap();
+            let pseudo_inputs = info.cascade_inputs.pseudos.get(pseudo).unwrap();
 
-        let new_values = self.cascade_internal(context,
-                                               &styles.primary,
-                                               Some(pseudo_style),
-                                               cascade_visited);
+            // If there was no relevant link, 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.
+            debug_assert!(cascade_visited.has_rules(info.cascade_inputs.primary()));
+            let primary_inputs = info.cascade_inputs.primary();
+
+            self.cascade_internal(context,
+                                  data.values.get_primary(),
+                                  primary_inputs,
+                                  Some(pseudo_inputs),
+                                  cascade_visited)
+        };
 
         if cascade_visited.should_accumulate_damage() {
             self.accumulate_damage(&context.shared,
-                                   restyle,
-                                   old_values.as_ref().map(|v| &**v),
+                                   &mut data.restyle,
+                                   old_values.as_ref().map(|v| v.as_ref()),
                                    &new_values,
                                    Some(pseudo));
         }
 
-        cascade_visited.set_values(pseudo_style, new_values);
+        let pseudo_inputs = context.thread_local.current_element_info
+                                  .as_mut().unwrap()
+                                  .cascade_inputs.pseudos.get_mut(pseudo).unwrap();
+        cascade_visited.set_pseudo_values(&mut data.values,
+                                          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: &ComputedStyle)
+                              primary_values: &Arc<ComputedValues>)
                               -> Option<Arc<ComputedValues>> {
-        let rule_node = &primary_style.rules;
+        let rule_node = primary_values.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.
             return None;
         }
 
         // This currently ignores visited styles, which seems acceptable,
         // as existing browsers don't appear to transition visited styles.
         Some(self.cascade_with_rules(context.shared,
                                      &context.thread_local.font_metrics_provider,
                                      &without_transition_rules,
-                                     primary_style,
+                                     Some(primary_values),
                                      CascadeTarget::Normal,
                                      CascadeVisitedMode::Unvisited,
                                      None))
     }
 
     #[cfg(feature = "gecko")]
     fn needs_animations_update(&self,
                                context: &mut StyleContext<Self>,
@@ -580,30 +659,29 @@ trait PrivateMatchMethods: TElement {
         })
     }
 
     #[cfg(feature = "gecko")]
     fn process_animations(&self,
                           context: &mut StyleContext<Self>,
                           old_values: &mut Option<Arc<ComputedValues>>,
                           new_values: &mut Arc<ComputedValues>,
-                          primary_style: &ComputedStyle,
                           important_rules_changed: bool) {
         use context::{CASCADE_RESULTS, CSS_ANIMATIONS, CSS_TRANSITIONS, EFFECT_PROPERTIES};
         use context::UpdateAnimationsTasks;
 
         let mut tasks = UpdateAnimationsTasks::empty();
         if self.needs_animations_update(context, old_values.as_ref(), new_values) {
             tasks.insert(CSS_ANIMATIONS);
         }
 
         let before_change_style = if self.might_need_transitions_update(old_values.as_ref().map(|s| &**s),
                                                                         new_values) {
             let after_change_style = if self.has_css_transitions() {
-                self.get_after_change_style(context, primary_style)
+                self.get_after_change_style(context, new_values)
             } else {
                 None
             };
 
             // In order to avoid creating a SequentialTask for transitions which
             // may not be updated, we check it per property to make sure Gecko
             // side will really update transition.
             let needs_transitions_update = {
@@ -646,17 +724,16 @@ trait PrivateMatchMethods: TElement {
         }
     }
 
     #[cfg(feature = "servo")]
     fn process_animations(&self,
                           context: &mut StyleContext<Self>,
                           old_values: &mut Option<Arc<ComputedValues>>,
                           new_values: &mut Arc<ComputedValues>,
-                          _primary_style: &ComputedStyle,
                           _important_rules_changed: bool) {
         use animation;
 
         let possibly_expired_animations =
             &mut context.thread_local.current_element_info.as_mut().unwrap()
                         .possibly_expired_animations;
         let shared_context = context.shared;
         if let Some(ref mut old) = *old_values {
@@ -730,24 +807,24 @@ trait PrivateMatchMethods: TElement {
         let difference = self.compute_style_difference(&old_values, &new_values, pseudo);
         restyle.damage |= difference.damage;
         difference.change.into()
     }
 
     #[cfg(feature = "servo")]
     fn update_animations_for_cascade(&self,
                                      context: &SharedStyleContext,
-                                     style: &mut Arc<ComputedValues>,
+                                     values: &mut Arc<ComputedValues>,
                                      possibly_expired_animations: &mut Vec<::animation::PropertyAnimation>,
                                      font_metrics: &FontMetricsProvider) {
         use animation::{self, Animation};
 
         // Finish any expired transitions.
         let this_opaque = self.as_node().opaque();
-        animation::complete_expired_transitions(this_opaque, style, context);
+        animation::complete_expired_transitions(this_opaque, values, context);
 
         // Merge any running transitions into the current style, and cancel them.
         let had_running_animations = context.running_animations
                                             .read()
                                             .get(&this_opaque)
                                             .is_some();
         if had_running_animations {
             let mut all_running_animations = context.running_animations.write();
@@ -761,17 +838,17 @@ trait PrivateMatchMethods: TElement {
                 // updated by layout, because other restyle due to script might
                 // be triggered by layout before the animation tick.
                 //
                 // See #12171 and the associated PR for an example where this
                 // happened while debugging other release panic.
                 if !running_animation.is_expired() {
                     animation::update_style_for_animation(context,
                                                           running_animation,
-                                                          style,
+                                                          values,
                                                           font_metrics);
                     if let Animation::Transition(_, _, ref frame, _) = *running_animation {
                         possibly_expired_animations.push(frame.property_animation.clone())
                     }
                 }
             }
         }
     }
@@ -851,17 +928,17 @@ pub trait MatchMethods : TElement {
         }
 
         // Cascade properties and compute primary values.
         let child_cascade_requirement =
             self.cascade_primary(context, data, important_rules_changed,
                                  CascadeVisitedMode::Unvisited);
 
         // Match and cascade eager pseudo-elements.
-        if !data.styles().is_display_none() {
+        if !data.values.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
@@ -870,17 +947,17 @@ pub trait MatchMethods : TElement {
                 self.match_pseudos(context, data, VisitedHandlingMode::RelevantLinkVisited);
                 self.cascade_pseudos(context, data, CascadeVisitedMode::Visited);
             }
 
             self.cascade_pseudos(context, data, CascadeVisitedMode::Unvisited);
         }
 
         // If we have any pseudo elements, indicate so in the primary StyleRelations.
-        if !data.styles().pseudos.is_empty() {
+        if !data.values.pseudos.is_empty() {
             primary_results.relations |= AFFECTED_BY_PSEUDO_ELEMENTS;
         }
 
         // 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.
@@ -893,17 +970,17 @@ pub trait MatchMethods : TElement {
                     .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.values(),
+                                       data.values.primary(),
                                        primary_results.relations,
                                        validation_data,
                                        dom_depth);
         }
 
         child_cascade_requirement
     }
 
@@ -937,16 +1014,19 @@ pub trait MatchMethods : TElement {
     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 info = context.thread_local.current_element_info.as_mut().unwrap();
+        let mut primary_inputs = info.cascade_inputs.ensure_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
@@ -957,19 +1037,19 @@ pub trait MatchMethods : TElement {
                 // 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();
+                let pseudo_values =
+                    parent_data.values.pseudos.get(&pseudo).unwrap();
+                let mut rules = pseudo_values.rules().clone();
                 if parent.may_have_animations() {
                     let animation_rules = data.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,
@@ -990,32 +1070,21 @@ pub trait MatchMethods : TElement {
                                                       &context.shared.guards);
                         if let Some(node) = animation_rule_node {
                             rules = node;
                         }
                     }
                 }
                 let important_rules_changed =
                     self.has_animations() &&
-                    data.has_styles() &&
+                    data.has_values() &&
                     data.important_rules_are_different(&rules,
-                                                   &context.shared.guards);
+                                                       &context.shared.guards);
 
-                let rules_changed = match visited_handling {
-                    VisitedHandlingMode::AllLinksVisitedAndUnvisited => {
-                        unreachable!("We should never try to selector match with \
-                                     AllLinksVisitedAndUnvisited");
-                    },
-                    VisitedHandlingMode::AllLinksUnvisited => {
-                        data.set_primary_rules(rules)
-                    },
-                    VisitedHandlingMode::RelevantLinkVisited => {
-                        data.styles_mut().primary.set_visited_rules(rules)
-                    },
-                };
+                let rules_changed = primary_inputs.set_rules(visited_handling, rules);
 
                 return MatchingResults::new(rules_changed, important_rules_changed)
             }
         }
 
         let mut applicable_declarations = ApplicableDeclarationList::new();
 
         let stylist = &context.shared.stylist;
@@ -1072,34 +1141,23 @@ pub trait MatchMethods : TElement {
                 if source.is_some() {
                     trace!(" > {:?}", source);
                 }
             }
         }
 
         let important_rules_changed =
             self.has_animations() &&
-            data.has_styles() &&
+            data.has_values() &&
             data.important_rules_are_different(
                 &primary_rule_node,
                 &context.shared.guards
             );
 
-        let rules_changed = match visited_handling {
-            VisitedHandlingMode::AllLinksVisitedAndUnvisited => {
-                unreachable!("We should never try to selector match with \
-                             AllLinksVisitedAndUnvisited");
-            },
-            VisitedHandlingMode::AllLinksUnvisited => {
-                data.set_primary_rules(primary_rule_node)
-            },
-            VisitedHandlingMode::RelevantLinkVisited => {
-                data.styles_mut().primary.set_visited_rules(primary_rule_node)
-            },
-        };
+        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.
@@ -1112,68 +1170,73 @@ pub trait MatchMethods : TElement {
 
         if self.implemented_pseudo_element().is_some() {
             // Element pseudos can't have any other pseudo.
             return;
         }
 
         let mut applicable_declarations = ApplicableDeclarationList::new();
 
-        let map = &mut context.thread_local.selector_flags;
-        let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| {
-            self.apply_selector_flags(map, element, flags);
-        };
-
         // 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
         };
 
-        let bloom_filter = context.thread_local.bloom_filter.filter();
-
-        let mut matching_context =
-            MatchingContext::new_for_visited(MatchingMode::ForStatelessPseudoElement,
-                                             Some(bloom_filter),
-                                             visited_handling,
-                                             context.shared.quirks_mode);
-
         // Compute rule nodes for eagerly-cascaded pseudo-elements.
         let mut matches_different_pseudos = false;
         SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
+            let bloom_filter = context.thread_local.bloom_filter.filter();
+
+            let mut matching_context =
+                MatchingContext::new_for_visited(MatchingMode::ForStatelessPseudoElement,
+                                                 Some(bloom_filter),
+                                                 visited_handling,
+                                                 context.shared.quirks_mode);
+
             // For pseudo-elements, we only try to match visited rules if there
             // are also unvisited rules.  (This matches Gecko's behavior.)
             if visited_handling == VisitedHandlingMode::RelevantLinkVisited &&
-               !data.styles().pseudos.has(&pseudo) {
+               !context.thread_local.current_element_info.as_ref().unwrap()
+                       .cascade_inputs.pseudos.has(&pseudo) {
                 return
             }
 
-            if !self.may_generate_pseudo(&pseudo, data.styles().primary.values()) {
+            if !self.may_generate_pseudo(&pseudo, data.values.primary()) {
                 return;
             }
 
-            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 map = &mut context.thread_local.selector_flags;
+                let mut set_selector_flags = |element: &Self, flags: ElementSelectorFlags| {
+                    self.apply_selector_flags(map, element, flags);
+                };
 
-            let pseudos = &mut data.styles_mut().pseudos;
+                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 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 |= pseudos.add_rules(
                     &pseudo,
                     visited_handling,
@@ -1286,57 +1349,60 @@ 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: &StyleContext<Self>,
-        data: &mut ElementData
+        context: &mut StyleContext<Self>,
     ) -> bool {
         let mut result = false;
-        result |= self.replace_rules_internal(replacements, context, data,
+        result |= self.replace_rules_internal(replacements, context,
                                               CascadeVisitedMode::Unvisited);
         if !context.shared.traversal_flags.for_animation_only() {
-            result |= self.replace_rules_internal(replacements, context, data,
+            result |= self.replace_rules_internal(replacements, context,
                                                   CascadeVisitedMode::Visited);
         }
         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: &StyleContext<Self>,
-        data: &mut ElementData,
+        context: &mut StyleContext<Self>,
         cascade_visited: CascadeVisitedMode
     ) -> bool {
         use properties::PropertyDeclarationBlock;
         use shared_lock::Locked;
 
         debug_assert!(replacements.intersects(RestyleHint::replacements()) &&
                       (replacements & !RestyleHint::replacements()).is_empty());
 
-        let element_styles = &mut data.styles_mut();
-        let primary_rules = match cascade_visited.get_rules_mut(&mut element_styles.primary) {
+        let stylist = &context.shared.stylist;
+        let guards = &context.shared.guards;
+
+        let mut primary_inputs = context.thread_local.current_element_info
+                                        .as_mut().unwrap()
+                                        .cascade_inputs.primary_mut();
+        let primary_rules = match cascade_visited.get_rules_mut(primary_inputs) {
             Some(r) => r,
             None => return false,
         };
 
         let replace_rule_node = |level: CascadeLevel,
                                  pdb: Option<&Arc<Locked<PropertyDeclarationBlock>>>,
                                  path: &mut StrongRuleNode| -> bool {
-            let new_node = context.shared.stylist.rule_tree()
-                .update_rule_at_level(level, pdb, path, &context.shared.guards);
+            let new_node = stylist.rule_tree()
+                                  .update_rule_at_level(level, pdb, path, guards);
             match new_node {
                 Some(n) => {
                     *path = n;
                     level.is_important()
                 },
                 None => false,
             }
         };
@@ -1449,45 +1515,48 @@ pub trait MatchMethods : TElement {
     {
         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 = data.styles().pseudos.keys();
+        let matched_pseudos = context.thread_local.current_element_info
+                                     .as_ref().unwrap()
+                                     .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,
                       shared_context: &SharedStyleContext,
                       font_metrics_provider: &FontMetricsProvider,
-                      primary_style: &ComputedStyle,
-                      pseudo_style: Option<&ComputedStyle>)
+                      primary_values: &Arc<ComputedValues>,
+                      pseudo_values: Option<&Arc<ComputedValues>>)
                       -> Arc<ComputedValues> {
-        let relevant_style = pseudo_style.unwrap_or(primary_style);
-        let rule_node = &relevant_style.rules;
+        let relevant_values = pseudo_values.unwrap_or(primary_values);
+        let rule_node = relevant_values.rules();
         let without_animation_rules =
             shared_context.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.values.as_ref().unwrap().clone();
+            return relevant_values.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,
-                                primary_style,
+                                Some(primary_values),
                                 CascadeTarget::Normal,
                                 CascadeVisitedMode::Unvisited,
                                 None)
     }
 
 }
 
 impl<E: TElement> MatchMethods for E {}
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -53,16 +53,17 @@ use gecko::values::GeckoStyleCoordConver
 use gecko::values::round_border_to_device_pixels;
 use logical_geometry::WritingMode;
 use media_queries::Device;
 use properties::animated_properties::TransitionProperty;
 use properties::longhands;
 use properties:: FontComputationData;
 use properties::{Importance, LonghandId};
 use properties::{PropertyDeclaration, PropertyDeclarationBlock, PropertyDeclarationId};
+use rule_tree::StrongRuleNode;
 use std::fmt::{self, Debug};
 use std::mem::{forget, transmute, zeroed};
 use std::ptr;
 use stylearc::Arc;
 use std::cmp;
 use values::{Auto, CustomIdent, Either, KeyframesName};
 use values::computed::{Shadow, ToComputedValue};
 use values::specified::length::Percentage;
@@ -70,65 +71,71 @@ use computed_values::border_style;
 
 pub mod style_structs {
     % for style_struct in data.style_structs:
     pub use super::${style_struct.gecko_struct_name} as ${style_struct.name};
     % endfor
 }
 
 
-#[derive(Clone, Debug)]
+#[derive(Clone)]
 pub struct ComputedValues {
     % for style_struct in data.style_structs:
     ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
     % endfor
 
     custom_properties: Option<Arc<ComputedValuesMap>>,
     pub writing_mode: WritingMode,
     pub font_computation_data: FontComputationData,
 
+    /// The rule node representing the ordered list of rules matched for this
+    /// node.  A value should always be present, except when asking for the
+    /// default values.
+    pub 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.
     visited_style: Option<Arc<ComputedValues>>,
 }
 
 impl ComputedValues {
     pub fn new(custom_properties: Option<Arc<ComputedValuesMap>>,
                writing_mode: WritingMode,
                font_size_keyword: Option<(longhands::font_size::KeywordSize, f32)>,
+               rules: Option<StrongRuleNode>,
                visited_style: Option<Arc<ComputedValues>>,
                % for style_struct in data.style_structs:
                ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
                % endfor
     ) -> Self {
         ComputedValues {
             custom_properties: custom_properties,
             writing_mode: writing_mode,
             font_computation_data: FontComputationData::new(font_size_keyword),
+            rules: rules,
             visited_style: visited_style,
             % for style_struct in data.style_structs:
             ${style_struct.ident}: ${style_struct.ident},
             % endfor
         }
     }
 
     pub fn default_values(pres_context: RawGeckoPresContextBorrowed) -> Arc<Self> {
         Arc::new(ComputedValues {
             custom_properties: None,
             writing_mode: WritingMode::empty(), // FIXME(bz): This seems dubious
             font_computation_data: FontComputationData::default_values(),
+            rules: None,
             visited_style: None,
             % for style_struct in data.style_structs:
                 ${style_struct.ident}: style_structs::${style_struct.name}::default(pres_context),
             % endfor
         })
     }
 
-
     #[inline]
     pub fn is_display_contents(&self) -> bool {
         self.get_box().clone_display() == longhands::display::computed_value::T::contents
     }
 
     /// Returns true if the value of the `content` property would make a
     /// pseudo-element not rendered.
     #[inline]
@@ -151,16 +158,22 @@ impl ComputedValues {
     }
 
     #[inline]
     pub fn mutate_${style_struct.name_lower}(&mut self) -> &mut style_structs::${style_struct.name} {
         Arc::make_mut(&mut self.${style_struct.ident})
     }
     % endfor
 
+    /// Gets a reference to the rule node. Panic if the element does not have a
+    /// rule node.
+    pub fn rules(&self) -> &StrongRuleNode {
+        self.rules.as_ref().unwrap()
+    }
+
     /// Gets a reference to the visited computed values, if any.
     pub fn get_visited_style(&self) -> Option<<&Arc<ComputedValues>> {
         self.visited_style.as_ref()
     }
 
     /// Gets a reference to the visited computed values. Panic if the element
     /// does not have visited computed values.
     pub fn visited_style(&self) -> &Arc<ComputedValues> {
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -1805,48 +1805,54 @@ pub type ServoComputedValues = ComputedV
 
 /// The struct that Servo uses to represent computed values.
 ///
 /// This struct contains an immutable atomically-reference-counted pointer to
 /// every kind of style struct.
 ///
 /// When needed, the structs may be copied in order to get mutated.
 #[cfg(feature = "servo")]
-#[cfg_attr(feature = "servo", derive(Clone, Debug))]
+#[cfg_attr(feature = "servo", derive(Clone))]
 pub struct ComputedValues {
     % for style_struct in data.active_style_structs():
         ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
     % endfor
     custom_properties: Option<Arc<::custom_properties::ComputedValuesMap>>,
     /// The writing mode of this computed values struct.
     pub writing_mode: WritingMode,
     /// The keyword behind the current font-size property, if any
     pub font_computation_data: FontComputationData,
 
+    /// The rule node representing the ordered list of rules matched for this
+    /// node.  A value should always be present, except when asking for the
+    /// default values.
+    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.
     visited_style: Option<Arc<ComputedValues>>,
 }
 
 #[cfg(feature = "servo")]
 impl ComputedValues {
     /// Construct a `ComputedValues` instance.
     pub fn new(custom_properties: Option<Arc<::custom_properties::ComputedValuesMap>>,
                writing_mode: WritingMode,
                font_size_keyword: Option<(longhands::font_size::KeywordSize, f32)>,
+               rules: Option<StrongRuleNode>,
                visited_style: Option<Arc<ComputedValues>>,
             % for style_struct in data.active_style_structs():
                ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
             % endfor
     ) -> Self {
         ComputedValues {
             custom_properties: custom_properties,
             writing_mode: writing_mode,
             font_computation_data: FontComputationData::new(font_size_keyword),
+            rules: rules,
             visited_style: visited_style,
         % for style_struct in data.active_style_structs():
             ${style_struct.ident}: ${style_struct.ident},
         % endfor
         }
     }
 
     /// Get the initial computed values.
@@ -1873,16 +1879,22 @@ impl ComputedValues {
 
         /// Get a mutable reference to the ${style_struct.name} struct.
         #[inline]
         pub fn mutate_${style_struct.name_lower}(&mut self) -> &mut style_structs::${style_struct.name} {
             Arc::make_mut(&mut self.${style_struct.ident})
         }
     % endfor
 
+    /// Gets a reference to the rule node. Panic if the element does not have a
+    /// rule node.
+    pub fn rules(&self) -> &StrongRuleNode {
+        self.rules.as_ref().unwrap()
+    }
+
     /// Gets a reference to the visited computed values, if any.
     pub fn get_visited_style(&self) -> Option<<&Arc<ComputedValues>> {
         self.visited_style.as_ref()
     }
 
     /// Gets a reference to the visited computed values. Panic if the element
     /// does not have visited computed values.
     pub fn visited_style(&self) -> &Arc<ComputedValues> {
@@ -2133,16 +2145,24 @@ impl ComputedValues {
                     .and_then(|map| map.get(name))
                     .map(|value| value.to_css_string())
                     .unwrap_or(String::new())
             }
         }
     }
 }
 
+// We manually implement Debug for ComputedValues so that we can avoid the
+// verbose stringification of every property and instead focus on a few values.
+impl fmt::Debug for ComputedValues {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "ComputedValues {{ rules: {:?}, .. }}", self.rules)
+    }
+}
+
 /// Return a WritingMode bitflags from the relevant CSS properties.
 pub fn get_writing_mode(inheritedbox_style: &style_structs::InheritedBox) -> WritingMode {
     use logical_geometry;
     let mut flags = WritingMode::empty();
     match inheritedbox_style.clone_direction() {
         computed_values::direction::T::ltr => {},
         computed_values::direction::T::rtl => {
             flags.insert(logical_geometry::FLAG_RTL);
@@ -2272,16 +2292,19 @@ impl<'a, T: 'a> Deref for StyleStructRef
 }
 
 /// A type used to compute a struct with minimal overhead.
 ///
 /// This allows holding references to the parent/default computed values without
 /// actually cloning them, until we either build the style, or mutate the
 /// inherited value.
 pub struct StyleBuilder<'a> {
+    /// The rule node representing the ordered list of rules matched for this
+    /// node.
+    rules: Option<StrongRuleNode>,
     custom_properties: Option<Arc<::custom_properties::ComputedValuesMap>>,
     /// The writing mode flags.
     ///
     /// TODO(emilio): Make private.
     pub writing_mode: WritingMode,
     /// The keyword behind the current font-size property, if any.
     pub font_size_keyword: Option<(longhands::font_size::KeywordSize, f32)>,
     /// The element's style if visited, only computed if there's a relevant link
@@ -2291,25 +2314,27 @@ pub struct StyleBuilder<'a> {
     % for style_struct in data.active_style_structs():
         ${style_struct.ident}: StyleStructRef<'a, style_structs::${style_struct.name}>,
     % endfor
 }
 
 impl<'a> StyleBuilder<'a> {
     /// Trivially construct a `StyleBuilder`.
     pub fn new(
+        rules: Option<StrongRuleNode>,
         custom_properties: Option<Arc<::custom_properties::ComputedValuesMap>>,
         writing_mode: WritingMode,
         font_size_keyword: Option<(longhands::font_size::KeywordSize, f32)>,
         visited_style: Option<Arc<ComputedValues>>,
         % for style_struct in data.active_style_structs():
             ${style_struct.ident}: &'a Arc<style_structs::${style_struct.name}>,
         % endfor
     ) -> Self {
         StyleBuilder {
+            rules: rules,
             custom_properties: custom_properties,
             writing_mode: writing_mode,
             font_size_keyword: font_size_keyword,
             visited_style: visited_style,
         % for style_struct in data.active_style_structs():
             ${style_struct.ident}: StyleStructRef::Borrowed(${style_struct.ident}),
         % endfor
         }
@@ -2319,17 +2344,18 @@ impl<'a> StyleBuilder<'a> {
     /// order to create a derived style.
     pub fn for_derived_style(s: &'a ComputedValues) -> Self {
         Self::for_inheritance(s, s)
     }
 
     /// Inherits style from the parent element, accounting for the default
     /// computed values that need to be provided as well.
     pub fn for_inheritance(parent: &'a ComputedValues, default: &'a ComputedValues) -> Self {
-        Self::new(parent.custom_properties(),
+        Self::new(parent.rules.clone(),
+                  parent.custom_properties(),
                   parent.writing_mode,
                   parent.font_computation_data.font_size_keyword,
                   parent.clone_visited_style(),
                   % for style_struct in data.active_style_structs():
                   % if style_struct.inherited:
                   parent.${style_struct.name_lower}_arc(),
                   % else:
                   default.${style_struct.name_lower}_arc(),
@@ -2395,16 +2421,17 @@ impl<'a> StyleBuilder<'a> {
     }
 
 
     /// Turns this `StyleBuilder` into a proper `ComputedValues` instance.
     pub fn build(self) -> ComputedValues {
         ComputedValues::new(self.custom_properties,
                             self.writing_mode,
                             self.font_size_keyword,
+                            self.rules,
                             self.visited_style,
                             % for style_struct in data.active_style_structs():
                             self.${style_struct.ident}.build(),
                             % endfor
         )
     }
 
     /// Get the custom properties map if necessary.
@@ -2438,16 +2465,17 @@ mod lazy_static_module {
                     % if style_struct.name == "Font":
                         hash: 0,
                     % endif
                 }),
             % endfor
             custom_properties: None,
             writing_mode: WritingMode::empty(),
             font_computation_data: FontComputationData::default_values(),
+            rules: None,
             visited_style: None,
         };
     }
 }
 
 /// A per-longhand function that performs the CSS cascade for that longhand.
 pub type CascadePropertyFn =
     extern "Rust" fn(declaration: &PropertyDeclaration,
@@ -2553,32 +2581,34 @@ pub fn cascade(device: &Device,
                         Some((declaration, cascade_level))
                     } else {
                         None
                     }
                 })
         })
     };
     apply_declarations(device,
+                       rule_node,
                        is_root_element,
                        iter_declarations,
                        inherited_style,
                        layout_parent_style,
                        visited_style,
                        cascade_info,
                        error_reporter,
                        font_metrics_provider,
                        flags,
                        quirks_mode)
 }
 
 /// NOTE: This function expects the declaration with more priority to appear
 /// first.
 #[allow(unused_mut)] // conditionally compiled code for "position"
 pub fn apply_declarations<'a, F, I>(device: &Device,
+                                    rules: &StrongRuleNode,
                                     is_root_element: bool,
                                     iter_declarations: F,
                                     inherited_style: &ComputedValues,
                                     layout_parent_style: &ComputedValues,
                                     visited_style: Option<Arc<ComputedValues>>,
                                     mut cascade_info: Option<<&mut CascadeInfo>,
                                     error_reporter: &ParseErrorReporter,
                                     font_metrics_provider: &FontMetricsProvider,
@@ -2600,30 +2630,32 @@ pub fn apply_declarations<'a, F, I>(devi
         }
     }
 
     let custom_properties =
         ::custom_properties::finish_cascade(
             custom_properties, &inherited_custom_properties);
 
     let builder = if !flags.contains(INHERIT_ALL) {
-        StyleBuilder::new(custom_properties,
+        StyleBuilder::new(Some(rules.clone()),
+                          custom_properties,
                           WritingMode::empty(),
                           inherited_style.font_computation_data.font_size_keyword,
                           visited_style,
                           % for style_struct in data.active_style_structs():
                               % if style_struct.inherited:
                                   inherited_style.${style_struct.name_lower}_arc(),
                               % else:
                                   default_style.${style_struct.name_lower}_arc(),
                               % endif
                           % endfor
                           )
     } else {
-        StyleBuilder::new(custom_properties,
+        StyleBuilder::new(Some(rules.clone()),
+                          custom_properties,
                           WritingMode::empty(),
                           inherited_style.font_computation_data.font_size_keyword,
                           visited_style,
                           % for style_struct in data.active_style_structs():
                               inherited_style.${style_struct.name_lower}_arc(),
                           % endfor
                           )
     };
--- a/servo/components/style/rule_tree/mod.rs
+++ b/servo/components/style/rule_tree/mod.rs
@@ -1161,17 +1161,17 @@ impl StrongRuleNode {
 
             // Continue to the parent element and search for the inherited properties.
             element = match element.inheritance_parent() {
                 Some(parent) => parent,
                 None => break
             };
 
             let parent_data = element.mutate_data().unwrap();
-            let parent_rule_node = parent_data.styles().primary.rules.clone();
+            let parent_rule_node = parent_data.values.primary().rules().clone();
             element_rule_node = Cow::Owned(parent_rule_node);
 
             properties = inherited_properties;
         }
 
         false
     }
 
--- a/servo/components/style/sharing/checks.rs
+++ b/servo/components/style/sharing/checks.rs
@@ -23,18 +23,18 @@ use stylearc::Arc;
 pub fn same_computed_values<E>(first: Option<E>, second: Option<E>) -> bool
     where E: TElement,
 {
     let (a, b) = match (first, second) {
         (Some(f), Some(s)) => (f, s),
         _ => return false,
     };
 
-    let eq = Arc::ptr_eq(a.borrow_data().unwrap().styles().primary.values(),
-                         b.borrow_data().unwrap().styles().primary.values());
+    let eq = Arc::ptr_eq(a.borrow_data().unwrap().values.primary(),
+                         b.borrow_data().unwrap().values.primary());
     eq
 }
 
 /// Whether two elements have the same same style attribute (by pointer identity).
 pub fn have_same_style_attribute<E>(
     target: &mut StyleSharingTarget<E>,
     candidate: &mut StyleSharingCandidate<E>
 ) -> bool
--- a/servo/components/style/sharing/mod.rs
+++ b/servo/components/style/sharing/mod.rs
@@ -65,17 +65,17 @@
 //! elements makes sense.
 
 use Atom;
 use applicable_declarations::ApplicableDeclarationBlock;
 use bit_vec::BitVec;
 use bloom::StyleBloom;
 use cache::{LRUCache, LRUCacheMutIterator};
 use context::{SelectorFlagsMap, SharedStyleContext, StyleContext};
-use data::{ElementData, ElementStyles};
+use data::{ElementData, ElementValues};
 use dom::{TElement, SendElement};
 use matching::{ChildCascadeRequirement, MatchMethods};
 use properties::ComputedValues;
 use selector_parser::RestyleDamage;
 use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode, StyleRelations};
 use smallvec::SmallVec;
 use std::mem;
 use std::ops::Deref;
@@ -357,65 +357,63 @@ impl<E: TElement> StyleSharingTarget<E> 
 
         context.thread_local.current_element_info.as_mut().unwrap().validation_data =
             self.validation_data.take();
         result
     }
 
     fn accumulate_damage_when_sharing(&self,
                                       shared_context: &SharedStyleContext,
-                                      shared_style: &ElementStyles,
+                                      shared_values: &ElementValues,
                                       data: &mut ElementData) -> ChildCascadeRequirement {
         // Accumulate restyle damage for the case when our sharing
         // target managed to share style.  This can come from several
         // sources:
         //
         // 1) We matched a different set of eager pseudos (which
         //    should cause a reconstruct).
         // 2) We have restyle damage from the eager pseudo computed
         //    styles.
         // 3) We have restyle damage from our own computed styles.
-        if data.has_styles() {
+        if data.has_values() {
             // We used to have pseudos (because we had styles).
             // Check for damage from the set of pseudos changing or
             // pseudos being restyled.
-            let (styles, mut restyle_data) = data.styles_and_restyle_mut();
-            let old_pseudos = &styles.pseudos;
-            let new_pseudos = &shared_style.pseudos;
+            let (values, mut restyle_data) = data.values_and_restyle_mut();
+            let old_pseudos = &values.pseudos;
+            let new_pseudos = &shared_values.pseudos;
 
             if !old_pseudos.has_same_pseudos_as(new_pseudos) {
                 restyle_data.damage |= RestyleDamage::reconstruct();
             } else {
                 // It's a bit unfortunate that we have to keep
                 // mapping PseudoElements back to indices
                 // here....
                 for pseudo in old_pseudos.keys() {
                     let old_values =
-                        old_pseudos.get(&pseudo).unwrap().values.as_ref().map(|v| &**v);
+                        old_pseudos.get(&pseudo).map(|v| &**v);
                     let new_values =
-                        new_pseudos.get(&pseudo).unwrap().values();
+                        new_pseudos.get(&pseudo).unwrap();
                     self.element.accumulate_damage(
                         &shared_context,
                         restyle_data,
                         old_values,
                         new_values,
                         Some(&pseudo)
                     );
                 }
             }
         }
 
-        let old_values =
-            data.get_styles_mut().and_then(|s| s.primary.values.take());
-
+        let old_values = data.values.primary.take();
         self.element.accumulate_damage(
             &shared_context,
             &mut data.restyle,
             old_values.as_ref().map(|v| &**v),
-            shared_style.primary.values(),
+            shared_values.primary(),
             None
         )
     }
 }
 
 /// A cache miss result.
 #[derive(Clone, Debug)]
 pub enum CacheMiss {
@@ -590,23 +588,23 @@ impl<E: TElement> StyleSharingCandidateC
                     target,
                     candidate,
                     &shared_context,
                     bloom_filter,
                     selector_flags_map
                 );
 
             match sharing_result {
-                Ok(shared_style) => {
+                Ok(shared_values) => {
                     // Yay, cache hit. Share the style.
                     let child_cascade_requirement =
                         target.accumulate_damage_when_sharing(shared_context,
-                                                              &shared_style,
+                                                              &shared_values,
                                                               data);
-                    data.set_styles(shared_style);
+                    data.values = shared_values;
 
                     return StyleSharingResult::StyleWasShared(i, child_cascade_requirement)
                 }
                 Err(miss) => {
                     debug!("Cache miss: {:?}", miss);
 
                     // Cache miss, let's see what kind of failure to decide
                     // whether we keep trying or not.
@@ -627,17 +625,17 @@ impl<E: TElement> StyleSharingCandidateC
         StyleSharingResult::CannotShare
     }
 
     fn test_candidate(target: &mut StyleSharingTarget<E>,
                       candidate: &mut StyleSharingCandidate<E>,
                       shared: &SharedStyleContext,
                       bloom: &StyleBloom<E>,
                       selector_flags_map: &mut SelectorFlagsMap<E>)
-                      -> Result<ElementStyles, CacheMiss> {
+                      -> Result<ElementValues, CacheMiss> {
         macro_rules! miss {
             ($miss: ident) => {
                 return Err(CacheMiss::$miss);
             }
         }
 
         // Check that we have the same parent, or at least the same pointer
         // identity for parent computed style. The latter check allows us to
@@ -703,11 +701,11 @@ impl<E: TElement> StyleSharingCandidateC
             miss!(Revalidation)
         }
 
         let data = candidate.element.borrow_data().unwrap();
         debug_assert!(target.has_current_styles(&data));
 
         debug!("Sharing style between {:?} and {:?}",
                target.element, candidate.element);
-        Ok(data.styles().clone())
+        Ok(data.values.clone())
     }
 }
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -3,17 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Selector matching.
 
 use {Atom, LocalName, Namespace};
 use applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList};
 use bit_vec::BitVec;
 use context::QuirksMode;
-use data::ComputedStyle;
 use dom::TElement;
 use element_state::ElementState;
 use error_reporting::create_error_reporter;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")]
 use gecko_bindings::structs::{nsIAtom, StyleRuleInclusion};
 use invalidation::element::invalidation_map::InvalidationMap;
 use invalidation::media_queries::EffectiveMediaQueryResults;
@@ -583,17 +582,17 @@ impl Stylist {
     /// values. The flow constructor uses this flag when constructing anonymous
     /// flows.
     pub fn precomputed_values_for_pseudo(&self,
                                          guards: &StylesheetGuards,
                                          pseudo: &PseudoElement,
                                          parent: Option<&Arc<ComputedValues>>,
                                          cascade_flags: CascadeFlags,
                                          font_metrics: &FontMetricsProvider)
-                                         -> ComputedStyle {
+                                         -> Arc<ComputedValues> {
         debug_assert!(pseudo.is_precomputed());
 
         let rule_node = match self.precomputed_pseudo_element_decls.get(pseudo) {
             Some(declarations) => {
                 // FIXME(emilio): When we've taken rid of the cascade we can just
                 // use into_iter.
                 self.rule_tree.insert_ordered_rules_with_important(
                     declarations.into_iter().map(|a| (a.source.clone(), a.level())),
@@ -623,17 +622,17 @@ impl Stylist {
                                 parent.map(|p| &**p),
                                 parent.map(|p| &**p),
                                 None,
                                 None,
                                 &create_error_reporter(),
                                 font_metrics,
                                 cascade_flags,
                                 self.quirks_mode);
-        ComputedStyle::new(rule_node, Arc::new(computed))
+        Arc::new(computed)
     }
 
     /// Returns the style for an anonymous box of the given type.
     #[cfg(feature = "servo")]
     pub fn style_for_anonymous(&self,
                                guards: &StylesheetGuards,
                                pseudo: &PseudoElement,
                                parent_style: &Arc<ComputedValues>)
@@ -661,34 +660,33 @@ impl Stylist {
             }
         };
         let mut cascade_flags = CascadeFlags::empty();
         if inherit_all {
             cascade_flags.insert(INHERIT_ALL);
         }
         self.precomputed_values_for_pseudo(guards, &pseudo, Some(parent_style), cascade_flags,
                                            &ServoMetricsProvider)
-            .values.unwrap()
     }
 
     /// Computes a pseudo-element style lazily during layout.
     ///
     /// This can only be done for a certain set of pseudo-elements, like
     /// :selection.
     ///
     /// Check the documentation on lazy pseudo-elements in
     /// docs/components/style.md
     pub fn lazily_compute_pseudo_element_style<E>(&self,
                                                   guards: &StylesheetGuards,
                                                   element: &E,
                                                   pseudo: &PseudoElement,
                                                   rule_inclusion: RuleInclusion,
                                                   parent_style: &ComputedValues,
                                                   font_metrics: &FontMetricsProvider)
-                                                  -> Option<ComputedStyle>
+                                                  -> Option<Arc<ComputedValues>>
         where E: TElement,
     {
         let rule_node =
             match self.lazy_pseudo_rules(guards, element, pseudo, rule_inclusion) {
                 Some(rule_node) => rule_node,
                 None => return None
             };
 
@@ -705,17 +703,17 @@ impl Stylist {
                                 Some(parent_style),
                                 None,
                                 None,
                                 &create_error_reporter(),
                                 font_metrics,
                                 CascadeFlags::empty(),
                                 self.quirks_mode);
 
-        Some(ComputedStyle::new(rule_node, Arc::new(computed)))
+        Some(Arc::new(computed))
     }
 
     /// Computes the rule node for a lazily-cascaded pseudo-element.
     ///
     /// See the documentation on lazy pseudo-elements in
     /// docs/components/style.md
     pub fn lazy_pseudo_rules<E>(&self,
                                 guards: &StylesheetGuards,
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -1,17 +1,18 @@
 /* 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/. */
 
 //! Traversing the DOM tree; the bloom filter.
 
 use atomic_refcell::AtomicRefCell;
+use context::ElementCascadeInputs;
 use context::{SharedStyleContext, StyleContext, ThreadLocalStyleContext};
-use data::{ElementData, ElementStyles};
+use data::{ElementData, ElementValues};
 use dom::{DirtyDescendants, NodeInfo, OpaqueNode, TElement, TNode};
 use invalidation::element::restyle_hints::{RECASCADE_SELF, RECASCADE_DESCENDANTS, RestyleHint};
 use matching::{ChildCascadeRequirement, MatchMethods};
 use sharing::{StyleSharingBehavior, StyleSharingTarget};
 #[cfg(feature = "servo")] use servo_config::opts;
 use smallvec::SmallVec;
 use std::borrow::BorrowMut;
 
@@ -223,17 +224,17 @@ pub trait DomTraversal<E: TElement> : Sy
                     traversal_flags: TraversalFlags)
                     -> PreTraverseToken
     {
         debug_assert!(!(traversal_flags.for_reconstruct() &&
                         traversal_flags.for_unstyled_children_only()),
                       "must not specify FOR_RECONSTRUCT in combination with UNSTYLED_CHILDREN_ONLY");
 
         if traversal_flags.for_unstyled_children_only() {
-            if root.borrow_data().map_or(true, |d| d.has_styles() && d.styles().is_display_none()) {
+            if root.borrow_data().map_or(true, |d| d.has_values() && d.values.is_display_none()) {
                 return PreTraverseToken {
                     traverse: false,
                     unstyled_children_only: false,
                 };
             }
             return PreTraverseToken {
                 traverse: true,
                 unstyled_children_only: true,
@@ -298,17 +299,17 @@ pub trait DomTraversal<E: TElement> : Sy
                 let going_to_reframe =
                     parent_data.restyle.reconstructed_self_or_ancestor();
 
                 let mut is_before_or_after_pseudo = false;
                 if let Some(pseudo) = el.implemented_pseudo_element() {
                     if pseudo.is_before_or_after() {
                         is_before_or_after_pseudo = true;
                         let still_match =
-                            parent_data.styles().pseudos.get(&pseudo).is_some();
+                            parent_data.values.pseudos.get(&pseudo).is_some();
 
                         if !still_match {
                             debug_assert!(going_to_reframe,
                                           "We're removing a pseudo, so we \
                                            should reframe!");
                             return false;
                         }
                     }
@@ -328,17 +329,17 @@ pub trait DomTraversal<E: TElement> : Sy
         if traversal_flags.for_animation_only() {
             // Skip elements that have no style data since animation-only
             // restyle is not necessary for the elements.
             let data = match el.borrow_data() {
                 Some(d) => d,
                 None => return false,
             };
 
-            if !data.has_styles() {
+            if !data.has_values() {
                 return false;
             }
 
             if el.has_animation_only_dirty_descendants() {
                 return true;
             }
 
             return data.restyle.hint.has_animation_hint() ||
@@ -354,17 +355,17 @@ pub trait DomTraversal<E: TElement> : Sy
         // Check the element data. If it doesn't exist, we need to visit
         // the element.
         let data = match el.borrow_data() {
             Some(d) => d,
             None => return true,
         };
 
         // If we don't have any style data, we need to visit the element.
-        if !data.has_styles() {
+        if !data.has_values() {
             return true;
         }
 
         // If we have a restyle hint or need to recascade, we need to
         // visit the element.
         //
         // Note that this is different than checking has_current_styles(),
         // since that can return true even if we have a restyle hint
@@ -399,17 +400,17 @@ pub trait DomTraversal<E: TElement> : Sy
                                 parent: E,
                                 parent_data: &ElementData,
                                 log: LogBehavior) -> bool
     {
         // See the comment on `cascade_node` for why we allow this on Gecko.
         debug_assert!(cfg!(feature = "gecko") || parent.has_current_styles(parent_data));
 
         // If the parent computed display:none, we don't style the subtree.
-        if parent_data.styles().is_display_none() {
+        if parent_data.values.is_display_none() {
             if log.allow() { debug!("Parent {:?} is display:none, culling traversal", parent); }
             return false;
         }
 
         // Gecko-only XBL handling.
         //
         // If we're computing initial styles and the parent has a Gecko XBL
         // binding, that binding may inject anonymous children and remap the
@@ -426,17 +427,17 @@ pub trait DomTraversal<E: TElement> : Sy
         // in the same way.
         //
         // We explicitly avoid handling restyles here (explicitly removing or
         // changing bindings), since that adds complexity and is rarer. If it
         // happens, we may just end up doing wasted work, since Gecko
         // recursively drops Servo ElementData when the XBL insertion parent of
         // an Element is changed.
         if cfg!(feature = "gecko") && thread_local.is_initial_style() &&
-           parent_data.styles().primary.values().has_moz_binding() {
+           parent_data.values.primary().has_moz_binding() {
             if log.allow() { debug!("Parent {:?} has XBL binding, deferring traversal", parent); }
             return false;
         }
 
         return true;
     }
 
     /// Helper for the traversal implementations to select the children that
@@ -456,17 +457,17 @@ pub trait DomTraversal<E: TElement> : Sy
         for kid in parent.as_node().traversal_children() {
             if Self::node_needs_traversal(kid, self.shared_context().traversal_flags) {
                 // If we are in a restyle for reconstruction, there is no need to
                 // perform a post-traversal, so we don't need to set the dirty
                 // descendants bit on the parent.
                 if !self.shared_context().traversal_flags.for_reconstruct() {
                     let el = kid.as_element();
                     if el.as_ref().and_then(|el| el.borrow_data())
-                                  .map_or(false, |d| d.has_styles()) {
+                                  .map_or(false, |d| d.has_values()) {
                         if self.shared_context().traversal_flags.for_animation_only() {
                             unsafe { parent.set_animation_only_dirty_descendants(); }
                         } else {
                             unsafe { parent.set_dirty_descendants(); }
                         }
                     }
                 }
                 f(thread_local, kid);
@@ -510,17 +511,17 @@ fn resolve_style_internal<E, F>(context:
     where E: TElement,
           F: Fn(E),
 {
     ensure_data(element);
     let mut data = element.mutate_data().unwrap();
     let mut display_none_root = None;
 
     // If the Element isn't styled, we need to compute its style.
-    if data.get_styles().is_none() {
+    if !data.has_values() {
         // Compute the parent style if necessary.
         let parent = element.traversal_parent();
         if let Some(p) = parent {
             display_none_root = resolve_style_internal(context, p, ensure_data);
         }
 
         // Maintain the bloom filter. If it doesn't exist, we need to build it
         // from scratch. Otherwise we just need to push the parent.
@@ -544,44 +545,44 @@ fn resolve_style_internal<E, F>(context:
             // chain.  No need to do this if we're computing default styles, since
             // resolve_default_style will want the tree to be left as it is.
             unsafe { element.note_descendants::<DirtyDescendants>() };
         }
     }
 
     // If we're display:none and none of our ancestors are, we're the root
     // of a display:none subtree.
-    if display_none_root.is_none() && data.styles().is_display_none() {
+    if display_none_root.is_none() && data.values.is_display_none() {
         display_none_root = Some(element);
     }
 
     return display_none_root
 }
 
 /// Manually resolve style by sequentially walking up the parent chain to the
 /// first styled Element, ignoring pending restyles. The resolved style is
 /// made available via a callback, and can be dropped by the time this function
 /// returns in the display:none subtree case.
 pub fn resolve_style<E, F, G, H>(context: &mut StyleContext<E>, element: E,
                                  ensure_data: &F, clear_data: &G, callback: H)
     where E: TElement,
           F: Fn(E),
           G: Fn(E),
-          H: FnOnce(&ElementStyles)
+          H: FnOnce(&ElementValues)
 {
     // Clear the bloom filter, just in case the caller is reusing TLS.
     context.thread_local.bloom_filter.clear();
 
     // Resolve styles up the tree.
     let display_none_root = resolve_style_internal(context, element, ensure_data);
 
     // Make them available for the scope of the callback. The callee may use the
     // argument, or perform any other processing that requires the styles to exist
     // on the Element.
-    callback(element.borrow_data().unwrap().styles());
+    callback(&element.borrow_data().unwrap().values);
 
     // Clear any styles in display:none subtrees or subtrees not in the document,
     // to leave the tree in a valid state.  For display:none subtrees, we leave
     // the styles on the display:none root, but for subtrees not in the document,
     // we clear styles all the way up to the root of the disconnected subtree.
     let in_doc = element.as_node().is_in_doc();
     if !in_doc || display_none_root.is_some() {
         let mut curr = element;
@@ -609,17 +610,17 @@ pub fn resolve_style<E, F, G, H>(context
 pub fn resolve_default_style<E, F, G, H>(context: &mut StyleContext<E>,
                                          element: E,
                                          ensure_data: &F,
                                          set_data: &G,
                                          callback: H)
     where E: TElement,
           F: Fn(E),
           G: Fn(E, Option<ElementData>) -> Option<ElementData>,
-          H: FnOnce(&ElementStyles)
+          H: FnOnce(&ElementValues)
 {
     // Save and clear out element data from the element and its ancestors.
     let mut old_data: SmallVec<[(E, Option<ElementData>); 8]> = SmallVec::new();
     {
         let mut e = element;
         loop {
             old_data.push((e, set_data(e, None)));
             match e.traversal_parent() {
@@ -630,17 +631,17 @@ pub fn resolve_default_style<E, F, G, H>
     }
 
     // Resolve styles up the tree.
     resolve_style_internal(context, element, ensure_data);
 
     // Make them available for the scope of the callback. The callee may use the
     // argument, or perform any other processing that requires the styles to exist
     // on the Element.
-    callback(element.borrow_data().unwrap().styles());
+    callback(&element.borrow_data().unwrap().values);
 
     // Swap the old element data back into the element and its ancestors.
     for entry in old_data {
         set_data(entry.0, entry.1);
     }
 }
 
 /// Calculates the style for a single node.
@@ -680,17 +681,17 @@ pub fn recalc_style_at<E, D>(traversal: 
         // We must always cascade native anonymous subtrees, since they inherit styles
         // from their first non-NAC ancestor.
         if element.is_native_anonymous() {
             hint |= RECASCADE_SELF;
         }
 
         // If we're restyling this element to display:none, throw away all style
         // data in the subtree, notify the caller to early-return.
-        if data.styles().is_display_none() {
+        if data.values.is_display_none() {
             debug!("{:?} style is display:none - clearing data from descendants.",
                    element);
             clear_descendant_data(element, &|e| unsafe { D::clear_element_data(&e) });
         }
     }
 
     // Now that matching and cascading is done, clear the bits corresponding to
     // those operations and compute the propagated restyle hint.
@@ -704,17 +705,17 @@ pub fn recalc_style_at<E, D>(traversal: 
 
     // FIXME(bholley): Need to handle explicitly-inherited reset properties
     // somewhere.
     propagated_hint.insert(hint);
 
     trace!("propagated_hint={:?} \
             is_display_none={:?}, implementing_pseudo={:?}",
            propagated_hint,
-           data.styles().is_display_none(),
+           data.values.is_display_none(),
            element.implemented_pseudo_element());
     debug_assert!(element.has_current_styles(data) ||
                   context.shared.traversal_flags.for_animation_only(),
                   "Should have computed style or haven't yet valid computed \
                    style in case of animation-only restyle");
 
     let has_dirty_descendants_for_this_restyle =
         if context.shared.traversal_flags.for_animation_only() {
@@ -764,17 +765,17 @@ pub fn recalc_style_at<E, D>(traversal: 
     // check display:none on the parent when inserting new children (which
     // can be moderately expensive). Instead, DOM implementations can
     // unconditionally set the dirty descendants bit on any styled parent,
     // and let the traversal sort it out.
     //
     // The second case is when we are in a restyle for reconstruction,
     // where we won't need to perform a post-traversal to pick up any
     // change hints.
-    if data.styles().is_display_none() ||
+    if data.values.is_display_none() ||
        context.shared.traversal_flags.for_reconstruct() {
         unsafe { element.unset_dirty_descendants(); }
     }
 }
 
 fn compute_style<E, D>(_traversal: &D,
                        traversal_data: &PerLevelTraversalData,
                        context: &mut StyleContext<E>,
@@ -787,17 +788,17 @@ fn compute_style<E, D>(_traversal: &D,
     use data::RestyleKind::*;
     use sharing::StyleSharingResult::*;
 
     context.thread_local.statistics.elements_styled += 1;
     let kind = data.restyle_kind(context.shared);
 
     debug!("compute_style: {:?} (kind={:?})", element, kind);
 
-    if data.has_styles() {
+    if data.has_values() {
         data.restyle.set_restyled();
     }
 
     match kind {
         MatchAndCascade => {
             debug_assert!(!context.shared.traversal_flags.for_animation_only(),
                           "MatchAndCascade shouldn't be processed during \
                            animation-only traversal");
@@ -824,24 +825,32 @@ fn compute_style<E, D>(_traversal: &D,
             // Perform the matching and cascading.
             element.match_and_cascade(
                 context,
                 data,
                 StyleSharingBehavior::Allow
             )
         }
         CascadeWithReplacements(flags) => {
-            let important_rules_changed = element.replace_rules(flags, context, data);
+            // Skipping full matching, load cascade inputs from previous values.
+            context.thread_local.current_element_info
+                   .as_mut().unwrap()
+                   .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
             )
         }
         CascadeOnly => {
+            // Skipping full matching, load cascade inputs from previous values.
+            context.thread_local.current_element_info
+                   .as_mut().unwrap()
+                   .cascade_inputs = ElementCascadeInputs::new_from_element_data(data);
             element.cascade_primary_and_pseudos(
                 context,
                 data,
                 /* important_rules_changed = */ false
             )
         }
     }
 }
@@ -862,17 +871,17 @@ where
     for child in element.as_node().traversal_children() {
         // FIXME(bholley): Add TElement::element_children instead of this.
         let child = match child.as_element() {
             Some(el) => el,
             None => continue,
         };
 
         // If the child is unstyled, we don't need to set up any restyling.
-        if child.borrow_data().map_or(true, |d| !d.has_styles()) {
+        if child.borrow_data().map_or(true, |d| !d.has_values()) {
             continue;
         }
 
         let mut child_data =
             unsafe { D::ensure_element_data(&child).borrow_mut() };
 
         trace!(" > {:?} -> {:?} + {:?}, pseudo: {:?}",
                child,
--- a/servo/components/style/values/specified/color.rs
+++ b/servo/components/style/values/specified/color.rs
@@ -271,17 +271,17 @@ impl ToComputedValue for Color {
                 let pres_context = unsafe { &*_context.device.pres_context };
                 let body = unsafe {
                     Gecko_GetBody(pres_context)
                 };
                 if let Some(body) = body {
                     let wrap = GeckoElement(body);
                     let borrow = wrap.borrow_data();
                     ComputedColor::rgba(borrow.as_ref().unwrap()
-                                              .styles().primary.values()
+                                              .values.primary()
                                               .get_color()
                                               .clone_color())
                 } else {
                     convert_nscolor_to_computedcolor(pres_context.mDefaultColor)
                 }
             },
         }
     }
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -7,17 +7,17 @@ use cssparser::{Parser, ParserInput};
 use cssparser::ToCss as ParserToCss;
 use env_logger::LogBuilder;
 use selectors::Element;
 use std::env;
 use std::fmt::Write;
 use std::ptr;
 use style::context::{QuirksMode, SharedStyleContext, StyleContext};
 use style::context::ThreadLocalStyleContext;
-use style::data::{ElementData, ElementStyles, RestyleData};
+use style::data::{ElementData, ElementValues, RestyleData};
 use style::dom::{AnimationOnlyDirtyDescendants, DirtyDescendants};
 use style::dom::{ShowSubtreeData, TElement, TNode};
 use style::element_state::ElementState;
 use style::error_reporting::RustLogReporter;
 use style::font_metrics::{FontMetricsProvider, get_metrics_provider_for_product};
 use style::gecko::data::{PerDocumentStyleData, PerDocumentStyleDataImpl};
 use style::gecko::global_style_data::{GLOBAL_STYLE_DATA, GlobalStyleData};
 use style::gecko::restyle_damage::GeckoRestyleDamage;
@@ -194,17 +194,17 @@ fn create_shared_context<'a>(global_styl
 
 fn traverse_subtree(element: GeckoElement,
                     raw_data: RawServoStyleSetBorrowed,
                     traversal_flags: TraversalFlags,
                     snapshots: &ServoElementSnapshotTable) {
     // When new content is inserted in a display:none subtree, we will call into
     // servo to try to style it. Detect that here and bail out.
     if let Some(parent) = element.traversal_parent() {
-        if parent.borrow_data().map_or(true, |d| d.styles().is_display_none()) {
+        if parent.borrow_data().map_or(true, |d| d.values.is_display_none()) {
             debug!("{:?} has unstyled parent {:?} - ignoring call to traverse_subtree", element, parent);
             return;
         }
     }
 
     let per_doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow();
     debug_assert!(!per_doc_data.stylesheets.has_changed());
 
@@ -667,34 +667,34 @@ pub extern "C" fn Servo_StyleSet_GetBase
     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 values = &element_data.values;
 
     let pseudo = PseudoElement::from_pseudo_type(pseudo_type);
-    let pseudos = &styles.pseudos;
-    let pseudo_style = match pseudo {
+    let pseudos = &values.pseudos;
+    let pseudo_values = match pseudo {
         Some(ref p) => {
-            let style = pseudos.get(p);
-            debug_assert!(style.is_some());
-            style
+            let values = pseudos.get(p);
+            debug_assert!(values.is_some());
+            values
         }
         None => None,
     };
 
     let provider = get_metrics_provider_for_product();
     element.get_base_style(&shared_context,
                            &provider,
-                           &styles.primary,
-                           pseudo_style)
+                           values.primary(),
+                           pseudo_values)
            .into_strong()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ComputedValues_ExtractAnimationValue(computed_values: ServoComputedValuesBorrowed,
                                                              property_id: nsCSSPropertyID)
                                                              -> RawServoAnimationValueStrong
 {
@@ -1423,104 +1423,106 @@ pub extern "C" fn Servo_ComputedValues_G
     let maybe_parent = ComputedValues::arc_from_borrowed(&parent_style_or_null);
     let mut cascade_flags = CascadeFlags::empty();
     if skip_display_fixup {
         cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP);
     }
     let metrics = get_metrics_provider_for_product();
     data.stylist.precomputed_values_for_pseudo(&guards, &pseudo, maybe_parent,
                                                cascade_flags, &metrics)
-        .values.unwrap()
         .into_strong()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ResolvePseudoStyle(element: RawGeckoElementBorrowed,
                                            pseudo_type: CSSPseudoElementType,
                                            is_probe: bool,
                                            raw_data: RawServoStyleSetBorrowed)
      -> ServoComputedValuesStrong
 {
     let element = GeckoElement(element);
     let data = unsafe { element.ensure_data() }.borrow_mut();
     let doc_data = PerDocumentStyleData::from_ffi(raw_data);
 
+    debug!("Servo_ResolvePseudoStyle: {:?} {:?}", element,
+           PseudoElement::from_pseudo_type(pseudo_type));
+
     // FIXME(bholley): Assert against this.
-    if data.get_styles().is_none() {
+    if !data.has_values() {
         warn!("Calling Servo_ResolvePseudoStyle on unstyled element");
         return if is_probe {
             Strong::null()
         } else {
             doc_data.borrow().default_computed_values().clone().into_strong()
         };
     }
 
     let pseudo = PseudoElement::from_pseudo_type(pseudo_type)
                     .expect("ResolvePseudoStyle with a non-pseudo?");
 
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let guard = global_style_data.shared_lock.read();
     match get_pseudo_style(&guard, element, &pseudo, RuleInclusion::All,
-                           data.styles(), &*doc_data.borrow()) {
+                           &data.values, &*doc_data.borrow()) {
         Some(values) => values.into_strong(),
         // FIXME(emilio): This looks pretty wrong! Shouldn't it be at least an
         // empty style inheriting from the element?
-        None if !is_probe => data.styles().primary.values().clone().into_strong(),
+        None if !is_probe => data.values.primary().clone().into_strong(),
         None => Strong::null(),
     }
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_HasAuthorSpecifiedRules(element: RawGeckoElementBorrowed,
                                                 rule_type_mask: u32,
                                                 author_colors_allowed: bool)
     -> bool
 {
     let element = GeckoElement(element);
 
     let data = element.borrow_data().unwrap();
-    let primary_style = &data.styles().primary;
+    let primary_values = data.values.primary();
 
     let guard = (*GLOBAL_STYLE_DATA).shared_lock.read();
     let guards = StylesheetGuards::same(&guard);
 
-    primary_style.rules.has_author_specified_rules(element,
-                                                   &guards,
-                                                   rule_type_mask,
-                                                   author_colors_allowed)
+    primary_values.rules().has_author_specified_rules(element,
+                                                      &guards,
+                                                      rule_type_mask,
+                                                      author_colors_allowed)
 }
 
 fn get_pseudo_style(guard: &SharedRwLockReadGuard,
                     element: GeckoElement,
                     pseudo: &PseudoElement,
                     rule_inclusion: RuleInclusion,
-                    styles: &ElementStyles,
+                    values: &ElementValues,
                     doc_data: &PerDocumentStyleDataImpl)
                     -> Option<Arc<ComputedValues>>
 {
     match pseudo.cascade_type() {
-        PseudoElementCascadeType::Eager => styles.pseudos.get(&pseudo).map(|s| s.values().clone()),
+        PseudoElementCascadeType::Eager => values.pseudos.get(&pseudo).map(|s| s.clone()),
         PseudoElementCascadeType::Precomputed => unreachable!("No anonymous boxes"),
         PseudoElementCascadeType::Lazy => {
             let base = if pseudo.inherits_from_default_values() {
                 doc_data.default_computed_values()
             } else {
-                styles.primary.values()
+                values.primary()
             };
             let guards = StylesheetGuards::same(guard);
             let metrics = get_metrics_provider_for_product();
             doc_data.stylist
                 .lazily_compute_pseudo_element_style(
                     &guards,
                     &element,
                     &pseudo,
                     rule_inclusion,
                     base,
                     &metrics)
-                .map(|s| s.values().clone())
+                .map(|s| s.clone())
         },
     }
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ComputedValues_Inherit(
   raw_data: RawServoStyleSetBorrowed,
   parent_style: ServoComputedValuesBorrowedOrNull,
@@ -1682,17 +1684,17 @@ pub extern "C" fn Servo_GetProperties_Ov
     let element_data = match element.borrow_data() {
         Some(data) => data,
         None => return
     };
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let guard = global_style_data.shared_lock.read();
     let guards = StylesheetGuards::same(&guard);
     let (overridden, custom) =
-        element_data.styles().primary.rules.get_properties_overriding_animations(&guards);
+        element_data.values.primary().rules().get_properties_overriding_animations(&guards);
     for p in list.iter() {
         match PropertyId::from_nscsspropertyid(*p) {
             Ok(property) => {
                 if let PropertyId::Longhand(id) = property {
                     if overridden.contains(id) {
                         unsafe { Gecko_AddPropertyToSet(set, *p) };
                     }
                 }
@@ -2454,17 +2456,17 @@ pub extern "C" fn Servo_CSSSupports(cond
 /// Only safe to call on the main thread, with exclusive access to the element and
 /// its ancestors.
 unsafe fn maybe_restyle<'a>(data: &'a mut AtomicRefMut<ElementData>,
                             element: GeckoElement,
                             animation_only: bool)
     -> Option<&'a mut RestyleData>
 {
     // Don't generate a useless RestyleData if the element hasn't been styled.
-    if !data.has_styles() {
+    if !data.has_values() {
         return None;
     }
 
     // Propagate the bit up the chain.
     if let Some(p) = element.traversal_parent() {
         if animation_only {
             p.note_descendants::<AnimationOnlyDirtyDescendants>();
         } else {
@@ -2481,22 +2483,22 @@ unsafe fn maybe_restyle<'a>(data: &'a mu
 #[no_mangle]
 pub extern "C" fn Servo_Element_GetStyleRuleList(element: RawGeckoElementBorrowed,
                                                  rules: RawGeckoServoStyleRuleListBorrowedMut) {
     let element = GeckoElement(element);
     let data = match element.borrow_data() {
         Some(element_data) => element_data,
         None => return,
     };
-    let computed = match data.get_styles() {
-        Some(styles) => &styles.primary,
+    let computed = match data.values.get_primary() {
+        Some(values) => values,
         None => return,
     };
     let mut result = vec![];
-    for rule_node in computed.rules.self_and_ancestors() {
+    for rule_node in computed.rules().self_and_ancestors() {
         if let &StyleSource::Style(ref rule) = rule_node.style_source() {
             result.push(Locked::<StyleRule>::arc_as_borrowed(&rule));
         }
     }
     unsafe { rules.set_len(result.len() as u32) };
     for (&src, dest) in result.into_iter().zip(rules.iter_mut()) {
         *dest = src;
     }
@@ -2554,29 +2556,29 @@ pub extern "C" fn Servo_ResolveStyle(ele
                                      allow_stale: bool)
                                      -> ServoComputedValuesStrong
 {
     let element = GeckoElement(element);
     debug!("Servo_ResolveStyle: {:?}", element);
     let data = unsafe { element.ensure_data() }.borrow();
 
     let valid_styles = if allow_stale {
-        data.has_styles()
+        data.has_values()
     } else {
         element.has_current_styles(&*data)
     };
 
     if !valid_styles {
         debug_assert!(false, "Resolving style on element without current styles with lazy \
                               computation forbidden.");
         let per_doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow();
         return per_doc_data.default_computed_values().clone().into_strong();
     }
 
-    data.styles().primary.values().clone().into_strong()
+    data.values.primary().clone().into_strong()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed,
                                            pseudo_type: CSSPseudoElementType,
                                            rule_inclusion: StyleRuleInclusion,
                                            snapshots: *const ServoElementSnapshotTable,
                                            raw_data: RawServoStyleSetBorrowed)
@@ -2584,28 +2586,33 @@ pub extern "C" fn Servo_ResolveStyleLazi
 {
     debug_assert!(!snapshots.is_null());
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let guard = global_style_data.shared_lock.read();
     let element = GeckoElement(element);
     let doc_data = PerDocumentStyleData::from_ffi(raw_data);
     let data = doc_data.borrow();
     let rule_inclusion = RuleInclusion::from(rule_inclusion);
-    let finish = |styles: &ElementStyles| -> Arc<ComputedValues> {
+    let finish = |values: &ElementValues| -> Arc<ComputedValues> {
         PseudoElement::from_pseudo_type(pseudo_type).and_then(|ref pseudo| {
-            get_pseudo_style(&guard, element, pseudo, rule_inclusion, styles, &*data)
-        }).unwrap_or_else(|| styles.primary.values().clone())
+            get_pseudo_style(&guard, element, pseudo, rule_inclusion, values, &*data)
+        }).unwrap_or_else(|| values.primary().clone())
     };
 
     // In the common case we already have the style. Check that before setting
     // up all the computation machinery. (Don't use it when we're getting
     // default styles, though.)
     if rule_inclusion == RuleInclusion::All {
-        if let Some(result) = element.mutate_data()
-                                     .and_then(|d| d.get_styles().map(&finish)) {
+        let styles = element.mutate_data().and_then(|d| {
+            match d.has_values() {
+                true => Some(finish(&d.values)),
+                false => None,
+            }
+        });
+        if let Some(result) = styles {
             return result.into_strong();
         }
     }
 
     let traversal_flags = match rule_inclusion {
         RuleInclusion::All => TraversalFlags::empty(),
         RuleInclusion::DefaultOnly => FOR_DEFAULT_STYLES,
     };
@@ -2623,22 +2630,22 @@ pub extern "C" fn Servo_ResolveStyleLazi
         thread_local: &mut tlc,
     };
     let ensure = |el: GeckoElement| { unsafe { el.ensure_data(); } };
 
     match rule_inclusion {
         RuleInclusion::All => {
             let clear = |el: GeckoElement| el.clear_data();
             resolve_style(&mut context, element, &ensure, &clear,
-                          |styles| result = Some(finish(styles)));
+                          |values| result = Some(finish(values)));
         }
         RuleInclusion::DefaultOnly => {
             let set_data = |el: GeckoElement, data| { unsafe { el.set_data(data) } };
             resolve_default_style(&mut context, element, &ensure, &set_data,
-                                  |styles| result = Some(finish(styles)));
+                                  |values| result = Some(finish(values)));
         }
     }
 
     result.unwrap().into_strong()
 }
 
 #[cfg(feature = "gecko_debug")]
 fn simulate_compute_values_failure(property: &PropertyValuePair) -> bool {
@@ -2684,19 +2691,19 @@ pub extern "C" fn Servo_GetComputedKeyfr
 
     let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
     let metrics = get_metrics_provider_for_product();
     let style = ComputedValues::as_arc(&style);
 
     let element = GeckoElement(element);
     let parent_element = element.inheritance_parent();
     let parent_data = parent_element.as_ref().and_then(|e| e.borrow_data());
-    let parent_style = parent_data.as_ref().map(|d| d.styles().primary.values());
-
-    let mut context = create_context(&data, &metrics, style, &parent_style);
+    let parent_values = parent_data.as_ref().map(|d| d.values.primary());
+
+    let mut context = create_context(&data, &metrics, style, &parent_values);
 
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let guard = global_style_data.shared_lock.read();
     let default_values = data.default_computed_values();
 
     for (index, keyframe) in keyframes.iter().enumerate() {
         let ref mut animation_values = computed_keyframes[index];
 
@@ -2741,19 +2748,19 @@ pub extern "C" fn Servo_GetAnimationValu
                                            animation_values: RawGeckoServoAnimationValueListBorrowedMut) {
     let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
     let style = ComputedValues::as_arc(&style);
     let metrics = get_metrics_provider_for_product();
 
     let element = GeckoElement(element);
     let parent_element = element.inheritance_parent();
     let parent_data = parent_element.as_ref().and_then(|e| e.borrow_data());
-    let parent_style = parent_data.as_ref().map(|d| d.styles().primary.values());
-
-    let mut context = create_context(&data, &metrics, style, &parent_style);
+    let parent_values = parent_data.as_ref().map(|d| d.values.primary());
+
+    let mut context = create_context(&data, &metrics, style, &parent_values);
 
     let default_values = data.default_computed_values();
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let guard = global_style_data.shared_lock.read();
 
     let declarations = Locked::<PropertyDeclarationBlock>::as_arc(&declarations);
     let guard = declarations.read_with(&guard);
     for (index, anim) in guard.to_animation_value_iter(&mut context, &default_values).enumerate() {
@@ -2770,19 +2777,19 @@ pub extern "C" fn Servo_AnimationValue_C
                                                -> RawServoAnimationValueStrong {
     let data = PerDocumentStyleData::from_ffi(raw_data).borrow();
     let style = ComputedValues::as_arc(&style);
     let metrics = get_metrics_provider_for_product();
 
     let element = GeckoElement(element);
     let parent_element = element.inheritance_parent();
     let parent_data = parent_element.as_ref().and_then(|e| e.borrow_data());
-    let parent_style = parent_data.as_ref().map(|d| d.styles().primary.values());
-
-    let mut context = create_context(&data, &metrics, style, &parent_style);
+    let parent_values = parent_data.as_ref().map(|d| d.values.primary());
+
+    let mut context = create_context(&data, &metrics, style, &parent_values);
 
     let default_values = data.default_computed_values();
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let guard = global_style_data.shared_lock.read();
     let declarations = Locked::<PropertyDeclarationBlock>::as_arc(&declarations);
     // We only compute the first element in declarations.
     match declarations.read_with(&guard).declarations().first() {
         Some(&(ref decl, imp)) if imp == Importance::Normal => {
--- a/servo/tests/unit/stylo/size_of.rs
+++ b/servo/tests/unit/stylo/size_of.rs
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use selectors::gecko_like_types as dummies;
 use servo_arc::Arc;
 use std::mem::{size_of, align_of};
 use style;
 use style::applicable_declarations::ApplicableDeclarationBlock;
-use style::data::{ComputedStyle, ElementData, ElementStyles, RestyleData};
+use style::data::{ElementData, ElementValues, RestyleData};
 use style::gecko::selector_parser as real;
 use style::properties::ComputedValues;
 use style::rule_tree::{RuleNode, StrongRuleNode};
 
 #[test]
 fn size_of_selectors_dummy_types() {
     assert_eq!(size_of::<dummies::PseudoClass>(), size_of::<real::NonTSPseudoClass>());
     assert_eq!(align_of::<dummies::PseudoClass>(), align_of::<real::NonTSPseudoClass>());
@@ -26,20 +26,20 @@ fn size_of_selectors_dummy_types() {
 
 // The size of this is critical to performance on the bloom-basic microbenchmark.
 // When iterating over a large Rule array, we want to be able to fast-reject
 // selectors (with the inline hashes) with as few cache misses as possible.
 size_of_test!(test_size_of_rule, style::stylist::Rule, 32);
 
 size_of_test!(test_size_of_option_arc_cv, Option<Arc<ComputedValues>>, 8);
 size_of_test!(test_size_of_option_rule_node, Option<StrongRuleNode>, 8);
-size_of_test!(test_size_of_computed_style, ComputedStyle, 32);
-size_of_test!(test_size_of_element_styles, ElementStyles, 48);
-size_of_test!(test_size_of_element_data, ElementData, 56);
+
+size_of_test!(test_size_of_element_values, ElementValues, 24);
 size_of_test!(test_size_of_restyle_data, RestyleData, 8);
+size_of_test!(test_size_of_element_data, ElementData, 32);
 
 size_of_test!(test_size_of_property_declaration, style::properties::PropertyDeclaration, 32);
 
 size_of_test!(test_size_of_application_declaration_block, ApplicableDeclarationBlock, 24);
 
 // FIXME(bholley): This can shrink with a little bit of work.
 // See https://github.com/servo/servo/issues/17280
 size_of_test!(test_size_of_rule_node, RuleNode, 80);