servo: Merge #18499 - style: add a TLS-based cache of reset style structs (from heycam:rule-cache); r=emilio
authorCameron McCormack <cam@mcc.id.au>
Thu, 14 Sep 2017 04:28:50 -0500
changeset 380966 9f1636ed17b6a33ae581a696c0ed1d34e7fe5622
parent 380965 163de8db41e8e1057f11816f5586bed29e837d76
child 380967 7807000c75138d299410a996db5f95eef9775e1d
push id95028
push userarchaeopteryx@coole-files.de
push dateThu, 14 Sep 2017 22:20:18 +0000
treeherdermozilla-inbound@c006eaf460bb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #18499 - style: add a TLS-based cache of reset style structs (from heycam:rule-cache); r=emilio Rule cache <!-- Please describe your changes on the following line: --> This adds a TLS-based cache reset styles structs keyed off rule nodes. Reviewed in https://bugzilla.mozilla.org/show_bug.cgi?id=1367635 by me and Emilio. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [ ] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: 874cb0d9df44e62a78d427f22f234a13227d07f8
servo/components/servo_arc/lib.rs
servo/components/style/animation.rs
servo/components/style/context.rs
servo/components/style/gecko/media_queries.rs
servo/components/style/lib.rs
servo/components/style/properties/computed_value_flags.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/helpers.mako.rs
servo/components/style/properties/longhand/box.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/rule_cache.rs
servo/components/style/rule_tree/mod.rs
servo/components/style/servo/media_queries.rs
servo/components/style/style_resolver.rs
servo/components/style/stylesheets/viewport_rule.rs
servo/components/style/stylist.rs
servo/components/style/values/computed/length.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/specified/color.rs
servo/components/style/values/specified/length.rs
servo/ports/geckolib/glue.rs
--- a/servo/components/servo_arc/lib.rs
+++ b/servo/components/servo_arc/lib.rs
@@ -117,16 +117,22 @@ impl<T: ?Sized + 'static> fmt::Debug for
 impl<T: ?Sized + 'static> PartialEq for NonZeroPtrMut<T> {
     fn eq(&self, other: &Self) -> bool {
         self.ptr() == other.ptr()
     }
 }
 
 impl<T: ?Sized + 'static> Eq for NonZeroPtrMut<T> {}
 
+impl<T: Sized + 'static> Hash for NonZeroPtrMut<T> {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.ptr().hash(state)
+    }
+}
+
 pub struct Arc<T: ?Sized + 'static> {
     p: NonZeroPtrMut<ArcInner<T>>,
 }
 
 /// An Arc that is known to be uniquely owned
 ///
 /// This lets us build arcs that we can mutate before
 /// freezing, without needing to change the allocation
--- a/servo/components/style/animation.rs
+++ b/servo/components/style/animation.rs
@@ -497,17 +497,19 @@ fn compute_style_for_animation_step(cont
                                                previous_style.rules(),
                                                iter,
                                                Some(previous_style),
                                                Some(previous_style),
                                                Some(previous_style),
                                                /* visited_style = */ None,
                                                font_metrics_provider,
                                                CascadeFlags::empty(),
-                                               context.quirks_mode());
+                                               context.quirks_mode(),
+                                               /* rule_cache = */ None,
+                                               &mut Default::default());
             computed
         }
     }
 }
 
 /// Triggers animations for a given node looking at the animation property
 /// values.
 pub fn maybe_start_animations(context: &SharedStyleContext,
--- a/servo/components/style/context.rs
+++ b/servo/components/style/context.rs
@@ -15,16 +15,17 @@ use euclid::ScaleFactor;
 use euclid::Size2D;
 use fnv::FnvHashMap;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")] use gecko_bindings::structs;
 use parallel::{STACK_SAFETY_MARGIN_KB, STYLE_THREAD_STACK_SIZE_KB};
 #[cfg(feature = "servo")] use parking_lot::RwLock;
 use properties::ComputedValues;
 #[cfg(feature = "servo")] use properties::PropertyId;
+use rule_cache::RuleCache;
 use rule_tree::StrongRuleNode;
 use selector_parser::{EAGER_PSEUDO_COUNT, SnapshotMap};
 use selectors::matching::ElementSelectorFlags;
 use servo_arc::Arc;
 #[cfg(feature = "servo")] use servo_atoms::Atom;
 use shared_lock::StylesheetGuards;
 use sharing::StyleSharingCache;
 use std::fmt;
@@ -680,16 +681,18 @@ impl StackLimitChecker {
 /// A thread-local style context.
 ///
 /// This context contains data that needs to be used during restyling, but is
 /// not required to be unique among worker threads, so we create one per worker
 /// thread in order to be able to mutate it without locking.
 pub struct ThreadLocalStyleContext<E: TElement> {
     /// A cache to share style among siblings.
     pub sharing_cache: StyleSharingCache<E>,
+    /// A cache from matched properties to elements that match those.
+    pub rule_cache: RuleCache,
     /// The bloom filter used to fast-reject selector-matching.
     pub bloom_filter: StyleBloom<E>,
     /// A channel on which new animations that have been triggered by style
     /// recalculation can be sent.
     #[cfg(feature = "servo")]
     pub new_animations_sender: Sender<Animation>,
     /// A set of tasks to be run (on the parent thread) in sequential mode after
     /// the rest of the styling is complete. This is useful for
@@ -717,16 +720,17 @@ pub struct ThreadLocalStyleContext<E: TE
 }
 
 impl<E: TElement> ThreadLocalStyleContext<E> {
     /// Creates a new `ThreadLocalStyleContext` from a shared one.
     #[cfg(feature = "servo")]
     pub fn new(shared: &SharedStyleContext) -> Self {
         ThreadLocalStyleContext {
             sharing_cache: StyleSharingCache::new(),
+            rule_cache: RuleCache::new(),
             bloom_filter: StyleBloom::new(),
             new_animations_sender: shared.local_context_creation_data.lock().unwrap().new_animations_sender.clone(),
             tasks: SequentialTaskList(Vec::new()),
             selector_flags: SelectorFlagsMap::new(),
             statistics: TraversalStatistics::default(),
             current_element_info: None,
             font_metrics_provider: E::FontMetricsProvider::create_from(shared),
             stack_limit_checker: StackLimitChecker::new(
@@ -734,16 +738,17 @@ impl<E: TElement> ThreadLocalStyleContex
         }
     }
 
     #[cfg(feature = "gecko")]
     /// Creates a new `ThreadLocalStyleContext` from a shared one.
     pub fn new(shared: &SharedStyleContext) -> Self {
         ThreadLocalStyleContext {
             sharing_cache: StyleSharingCache::new(),
+            rule_cache: RuleCache::new(),
             bloom_filter: StyleBloom::new(),
             tasks: SequentialTaskList(Vec::new()),
             selector_flags: SelectorFlagsMap::new(),
             statistics: TraversalStatistics::default(),
             current_element_info: None,
             font_metrics_provider: E::FontMetricsProvider::create_from(shared),
             stack_limit_checker: StackLimitChecker::new(
                 (STYLE_THREAD_STACK_SIZE_KB - STACK_SAFETY_MARGIN_KB) * 1024),
--- a/servo/components/style/gecko/media_queries.rs
+++ b/servo/components/style/gecko/media_queries.rs
@@ -18,17 +18,19 @@ use gecko_bindings::structs::{nsCSSKeywo
 use gecko_bindings::structs::{nsMediaExpression_Range, nsMediaFeature};
 use gecko_bindings::structs::{nsMediaFeature_ValueType, nsMediaFeature_RangeType, nsMediaFeature_RequirementFlags};
 use gecko_bindings::structs::{nsPresContext, RawGeckoPresContextOwned};
 use gecko_bindings::structs::nsIAtom;
 use media_queries::MediaType;
 use parser::ParserContext;
 use properties::{ComputedValues, StyleBuilder};
 use properties::longhands::font_size;
+use rule_cache::RuleCacheConditions;
 use servo_arc::Arc;
+use std::cell::RefCell;
 use std::fmt::{self, Write};
 use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
 use str::starts_with_ignore_ascii_case;
 use string_cache::Atom;
 use style_traits::{CSSPixel, DevicePixel};
 use style_traits::{ToCss, ParseError, StyleParseError};
 use style_traits::viewport::ViewportConstraints;
 use values::{CSSFloat, CustomIdent, serialize_dimension};
@@ -689,25 +691,28 @@ impl Expression {
 
         let default_values = device.default_computed_values();
 
 
         let provider = get_metrics_provider_for_product();
 
         // http://dev.w3.org/csswg/mediaqueries3/#units
         // em units are relative to the initial font-size.
+        let mut conditions = RuleCacheConditions::default();
         let context = computed::Context {
             is_root_element: false,
             builder: StyleBuilder::for_derived_style(device, default_values, None, None),
             font_metrics_provider: &provider,
             cached_system_font: None,
             in_media_query: true,
             // TODO: pass the correct value here.
             quirks_mode: quirks_mode,
             for_smil_animation: false,
+            for_non_inherited_property: None,
+            rule_cache_conditions: RefCell::new(&mut conditions),
         };
 
         let required_value = match self.value {
             Some(ref v) => v,
             None => {
                 // If there's no value, always match unless it's a zero length
                 // or a zero integer or boolean.
                 return match *actual_value {
--- a/servo/components/style/lib.rs
+++ b/servo/components/style/lib.rs
@@ -119,16 +119,17 @@ pub mod font_metrics;
 pub mod hash;
 pub mod invalidation;
 #[allow(missing_docs)] // TODO.
 pub mod logical_geometry;
 pub mod matching;
 pub mod media_queries;
 pub mod parallel;
 pub mod parser;
+pub mod rule_cache;
 pub mod rule_tree;
 pub mod scoped_tls;
 pub mod selector_map;
 pub mod selector_parser;
 pub mod shared_lock;
 pub mod sharing;
 pub mod style_resolver;
 pub mod stylist;
--- a/servo/components/style/properties/computed_value_flags.rs
+++ b/servo/components/style/properties/computed_value_flags.rs
@@ -54,10 +54,13 @@ bitflags! {
 
         /// Whether this style inherits the `content` property.
         ///
         /// Important because of the same reason.
         const INHERITS_CONTENT = 1 << 7,
 
         /// Whether the child explicitly inherits any reset property.
         const INHERITS_RESET_STYLE = 1 << 8,
+
+        /// A flag to mark a style which is a visited style.
+        const IS_STYLE_IF_VISITED = 1 << 9,
     }
 }
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -47,17 +47,17 @@ use gecko_bindings::structs::root::NS_ST
 use gecko_bindings::sugar::ns_style_coord::{CoordDataValue, CoordData, CoordDataMut};
 use gecko::values::convert_nscolor_to_rgba;
 use gecko::values::convert_rgba_to_nscolor;
 use gecko::values::GeckoStyleCoordConvertible;
 use gecko::values::round_border_to_device_pixels;
 use logical_geometry::WritingMode;
 use media_queries::Device;
 use properties::animated_properties::TransitionProperty;
-use properties::computed_value_flags::ComputedValueFlags;
+use properties::computed_value_flags::*;
 use properties::{default_font_size_keyword, longhands, FontComputationData, Importance, LonghandId};
 use properties::{PropertyDeclaration, PropertyDeclarationBlock, PropertyDeclarationId};
 use rule_tree::StrongRuleNode;
 use selector_parser::PseudoElement;
 use servo_arc::{Arc, RawOffsetArc};
 use std::mem::{forget, uninitialized, transmute, zeroed};
 use std::{cmp, ops, ptr};
 use values::{self, Auto, CustomIdent, Either, KeyframesName, None_};
@@ -254,16 +254,21 @@ impl ops::Deref for ComputedValues {
 
 impl ops::DerefMut for ComputedValues {
     fn deref_mut(&mut self) -> &mut ComputedValuesInner {
         &mut self.0.mSource
     }
 }
 
 impl ComputedValuesInner {
+    /// Whether we're a visited style.
+    pub fn is_style_if_visited(&self) -> bool {
+        self.flags.contains(IS_STYLE_IF_VISITED)
+    }
+
     #[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]
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -311,24 +311,35 @@
                     DeclaredValue::CSSWideKeyword(value)
                 },
                 PropertyDeclaration::WithVariables(..) => {
                     panic!("variables should already have been substituted")
                 }
                 _ => panic!("entered the wrong cascade_property() implementation"),
             };
 
+            context.for_non_inherited_property =
+                % if property.style_struct.inherited:
+                    None;
+                % else:
+                    Some(LonghandId::${property.camel_case});
+                % endif
+
             % if not property.derived_from:
                 match value {
                     DeclaredValue::Value(specified_value) => {
                         % if property.ident in SYSTEM_FONT_LONGHANDS and product == "gecko":
                             if let Some(sf) = specified_value.get_system() {
                                 longhands::system_font::resolve_system_font(sf, context);
                             }
                         % endif
+                        % if not property.style_struct.inherited and property.logical:
+                            context.rule_cache_conditions.borrow_mut()
+                                .set_writing_mode_dependency(context.builder.writing_mode);
+                        % endif
                         % if property.is_vector:
                             // In the case of a vector property we want to pass
                             // down an iterator so that this can be computed
                             // without allocation
                             //
                             // However, computing requires a context, but the
                             // style struct being mutated is on the context. We
                             // temporarily remove it, mutate it, and then put it
@@ -370,16 +381,19 @@
                             % else:
                                 context.builder.reset_${property.ident}();
                             % endif
                         },
                         % if data.current_style_struct.inherited:
                         CSSWideKeyword::Unset |
                         % endif
                         CSSWideKeyword::Inherit => {
+                            % if not property.style_struct.inherited:
+                                context.rule_cache_conditions.borrow_mut().set_uncacheable();
+                            % endif
                             % if property.ident == "font_size":
                                 longhands::font_size::cascade_inherit_font_size(context);
                             % else:
                                 context.builder.inherit_${property.ident}();
                             % endif
                         }
                     }
                 }
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -221,20 +221,34 @@
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
         fn to_computed_value(&self, context: &Context) -> computed_value::T {
             let ltr = context.style().writing_mode.is_bidi_ltr();
             // https://drafts.csswg.org/css-logical-props/#float-clear
             match *self {
-                SpecifiedValue::inline_start if ltr => computed_value::T::left,
-                SpecifiedValue::inline_start => computed_value::T::right,
-                SpecifiedValue::inline_end if ltr => computed_value::T::right,
-                SpecifiedValue::inline_end => computed_value::T::left,
+                SpecifiedValue::inline_start => {
+                    context.rule_cache_conditions.borrow_mut()
+                        .set_writing_mode_dependency(context.builder.writing_mode);
+                    if ltr {
+                        computed_value::T::left
+                    } else {
+                        computed_value::T::right
+                    }
+                }
+                SpecifiedValue::inline_end => {
+                    context.rule_cache_conditions.borrow_mut()
+                        .set_writing_mode_dependency(context.builder.writing_mode);
+                    if ltr {
+                        computed_value::T::right
+                    } else {
+                        computed_value::T::left
+                    }
+                }
                 % for value in "none left right".split():
                     SpecifiedValue::${value} => computed_value::T::${value},
                 % endfor
             }
         }
         #[inline]
         fn from_computed_value(computed: &computed_value::T) -> SpecifiedValue {
             match *computed {
@@ -259,20 +273,34 @@
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
         fn to_computed_value(&self, context: &Context) -> computed_value::T {
             let ltr = context.style().writing_mode.is_bidi_ltr();
             // https://drafts.csswg.org/css-logical-props/#float-clear
             match *self {
-                SpecifiedValue::inline_start if ltr => computed_value::T::left,
-                SpecifiedValue::inline_start => computed_value::T::right,
-                SpecifiedValue::inline_end if ltr => computed_value::T::right,
-                SpecifiedValue::inline_end => computed_value::T::left,
+                SpecifiedValue::inline_start => {
+                    context.rule_cache_conditions.borrow_mut()
+                        .set_writing_mode_dependency(context.builder.writing_mode);
+                    if ltr {
+                        computed_value::T::left
+                    } else {
+                        computed_value::T::right
+                    }
+                }
+                SpecifiedValue::inline_end => {
+                    context.rule_cache_conditions.borrow_mut()
+                        .set_writing_mode_dependency(context.builder.writing_mode);
+                    if ltr {
+                        computed_value::T::right
+                    } else {
+                        computed_value::T::left
+                    }
+                }
                 % for value in "none left right both".split():
                     SpecifiedValue::${value} => computed_value::T::${value},
                 % endfor
             }
         }
         #[inline]
         fn from_computed_value(computed: &computed_value::T) -> SpecifiedValue {
             match *computed {
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -11,16 +11,17 @@
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 use app_units::Au;
 use servo_arc::{Arc, UniqueArc};
 use smallbitvec::SmallBitVec;
 use std::borrow::Cow;
 use hash::HashSet;
 use std::{fmt, mem, ops};
+use std::cell::RefCell;
 #[cfg(feature = "gecko")] use std::ptr;
 
 #[cfg(feature = "servo")] use cssparser::RGBA;
 use cssparser::{CowRcStr, Parser, TokenSerializationType, serialize_identifier};
 use cssparser::ParserInput;
 #[cfg(feature = "servo")] use euclid::SideOffsets2D;
 use computed_values;
 use context::QuirksMode;
@@ -28,29 +29,30 @@ use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")] use gecko_bindings::bindings;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::{self, nsCSSPropertyID};
 #[cfg(feature = "servo")] use logical_geometry::{LogicalMargin, PhysicalSide};
 use logical_geometry::WritingMode;
 use media_queries::Device;
 use parser::ParserContext;
 use properties::animated_properties::AnimatableLonghand;
 #[cfg(feature = "gecko")] use properties::longhands::system_font::SystemFont;
+use rule_cache::{RuleCache, RuleCacheConditions};
 use selector_parser::PseudoElement;
 use selectors::parser::SelectorParseError;
 #[cfg(feature = "servo")] use servo_config::prefs::PREFS;
 use shared_lock::StylesheetGuards;
 use style_traits::{PARSING_MODE_DEFAULT, ToCss, ParseError};
 use style_traits::{PropertyDeclarationParseError, StyleParseError, ValueParseError};
 use stylesheets::{CssRuleType, Origin, UrlExtraData};
 #[cfg(feature = "servo")] use values::Either;
 use values::generics::text::LineHeight;
 use values::computed;
 use values::computed::NonNegativeLength;
 use rule_tree::{CascadeLevel, StrongRuleNode};
-use self::computed_value_flags::ComputedValueFlags;
+use self::computed_value_flags::*;
 use style_adjuster::StyleAdjuster;
 #[cfg(feature = "servo")] use values::specified::BorderStyle;
 
 pub use self::declaration_block::*;
 
 #[cfg(feature = "gecko")]
 #[macro_export]
 macro_rules! property_name {
@@ -620,16 +622,46 @@ impl LonghandId {
             LonghandId::FontSize |
             LonghandId::FontFamily |
             LonghandId::Color |
             LonghandId::TextDecorationLine |
             LonghandId::WritingMode |
             LonghandId::Direction
         )
     }
+
+    /// Whether computed values of this property lossily convert any complex
+    /// colors into RGBA colors.
+    ///
+    /// In Gecko, there are some properties still that compute currentcolor
+    /// down to an RGBA color at computed value time, instead of as
+    /// `StyleComplexColor`s. For these properties, we must return `false`,
+    /// so that we correctly avoid caching style data in the rule tree.
+    pub fn stores_complex_colors_lossily(&self) -> bool {
+        % if product == "gecko":
+        matches!(*self,
+            % for property in data.longhands:
+            % if property.predefined_type == "RGBAColor":
+            LonghandId::${property.camel_case} |
+            % endif
+            % endfor
+            LonghandId::BackgroundImage |
+            LonghandId::BorderImageSource |
+            LonghandId::BoxShadow |
+            LonghandId::MaskImage |
+            LonghandId::MozBorderBottomColors |
+            LonghandId::MozBorderLeftColors |
+            LonghandId::MozBorderRightColors |
+            LonghandId::MozBorderTopColors |
+            LonghandId::TextShadow
+        )
+        % else:
+        false
+        % endif
+    }
 }
 
 /// An identifier for a given shorthand property.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Copy, Debug, Eq, PartialEq, ToCss)]
 pub enum ShorthandId {
     % for property in data.shorthands:
         /// ${property.name}
@@ -2137,16 +2169,21 @@ impl ComputedValuesInner {
     pub fn custom_properties(&self) -> Option<Arc<::custom_properties::CustomPropertiesMap>> {
         self.custom_properties.clone()
     }
 
     /// Whether this style has a -moz-binding value. This is always false for
     /// Servo for obvious reasons.
     pub fn has_moz_binding(&self) -> bool { false }
 
+    /// Whether we're a visited style.
+    pub fn is_style_if_visited(&self) -> bool {
+        self.flags.contains(IS_STYLE_IF_VISITED)
+    }
+
     /// Returns whether this style's display value is equal to contents.
     ///
     /// Since this isn't supported in Servo, this is always false for Servo.
     pub fn is_display_contents(&self) -> bool { false }
 
     #[inline]
     /// Returns whether the "content" property for the given style is completely
     /// ineffective, and would yield an empty `::before` or `::after`
@@ -2539,22 +2576,27 @@ pub struct StyleBuilder<'a> {
     reset_style: &'a ComputedValues,
 
     /// The style we're inheriting from explicitly, or none if we're the root of
     /// a subtree.
     parent_style: Option<<&'a ComputedValues>,
 
     /// The rule node representing the ordered list of rules matched for this
     /// node.
-    rules: Option<StrongRuleNode>,
+    pub rules: Option<StrongRuleNode>,
 
     custom_properties: Option<Arc<::custom_properties::CustomPropertiesMap>>,
 
     /// The pseudo-element this style will represent.
-    pseudo: Option<<&'a PseudoElement>,
+    pub pseudo: Option<<&'a PseudoElement>,
+
+    /// Whether we have mutated any reset structs since the the last time
+    /// `clear_modified_reset` was called.  This is used to tell whether the
+    /// `StyleAdjuster` did any work.
+    modified_reset: bool,
 
     /// 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: FontComputationData,
     /// Flags for the computed value.
@@ -2575,17 +2617,17 @@ impl<'a> StyleBuilder<'a> {
         parent_style: Option<<&'a ComputedValues>,
         parent_style_ignoring_first_line: Option<<&'a ComputedValues>,
         pseudo: Option<<&'a PseudoElement>,
         cascade_flags: CascadeFlags,
         rules: Option<StrongRuleNode>,
         custom_properties: Option<Arc<::custom_properties::CustomPropertiesMap>>,
         writing_mode: WritingMode,
         font_size_keyword: FontComputationData,
-        flags: ComputedValueFlags,
+        mut flags: ComputedValueFlags,
         visited_style: Option<Arc<ComputedValues>>,
     ) -> Self {
         debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some());
         #[cfg(feature = "gecko")]
         debug_assert!(parent_style.is_none() ||
                       ptr::eq(parent_style.unwrap(),
                               parent_style_ignoring_first_line.unwrap()) ||
                       parent_style.unwrap().pseudo() == Some(PseudoElement::FirstLine));
@@ -2597,39 +2639,49 @@ impl<'a> StyleBuilder<'a> {
         // backgrounds, for example.  This code doesn't attempt to make it play
         // nice with inherited_style_ignoring_first_line.
         let reset_style = if cascade_flags.contains(INHERIT_ALL) {
             inherited_style
         } else {
             reset_style
         };
 
+        if cascade_flags.contains(VISITED_DEPENDENT_ONLY) {
+            flags.insert(IS_STYLE_IF_VISITED);
+        }
+
         StyleBuilder {
             device,
             parent_style,
             inherited_style,
             inherited_style_ignoring_first_line,
             reset_style,
             pseudo,
             rules,
+            modified_reset: false,
             custom_properties,
             writing_mode,
             font_size_keyword,
             flags,
             visited_style,
             % for style_struct in data.active_style_structs():
             % if style_struct.inherited:
             ${style_struct.ident}: StyleStructRef::Borrowed(inherited_style.${style_struct.name_lower}_arc()),
             % else:
             ${style_struct.ident}: StyleStructRef::Borrowed(reset_style.${style_struct.name_lower}_arc()),
             % endif
             % endfor
         }
     }
 
+    /// Whether we're a visited style.
+    pub fn is_style_if_visited(&self) -> bool {
+        self.flags.contains(IS_STYLE_IF_VISITED)
+    }
+
     /// Creates a StyleBuilder holding only references to the structs of `s`, in
     /// order to create a derived style.
     pub fn for_derived_style(
         device: &'a Device,
         style_to_derive_from: &'a ComputedValues,
         parent_style: Option<<&'a ComputedValues>,
         pseudo: Option<<&'a PseudoElement>,
     ) -> Self {
@@ -2641,44 +2693,57 @@ impl<'a> StyleBuilder<'a> {
         StyleBuilder {
             device,
             parent_style,
             inherited_style,
             // None of our callers pass in ::first-line parent styles.
             inherited_style_ignoring_first_line: inherited_style,
             reset_style,
             pseudo,
+            modified_reset: false,
             rules: None, // FIXME(emilio): Dubious...
             custom_properties: style_to_derive_from.custom_properties(),
             writing_mode: style_to_derive_from.writing_mode,
             font_size_keyword: style_to_derive_from.font_computation_data,
             flags: style_to_derive_from.flags,
             visited_style: style_to_derive_from.clone_visited_style(),
             % for style_struct in data.active_style_structs():
             ${style_struct.ident}: StyleStructRef::Borrowed(
                 style_to_derive_from.${style_struct.name_lower}_arc()
             ),
             % endfor
         }
     }
 
+    /// Copy the reset properties from `style`.
+    pub fn copy_reset_from(&mut self, style: &'a ComputedValues) {
+        % for style_struct in data.active_style_structs():
+        % if not style_struct.inherited:
+        self.${style_struct.ident} =
+            StyleStructRef::Borrowed(style.${style_struct.name_lower}_arc());
+        % endif
+        % endfor
+    }
+
     % for property in data.longhands:
     % if property.ident != "font_size":
     /// Inherit `${property.ident}` from our parent style.
     #[allow(non_snake_case)]
     pub fn inherit_${property.ident}(&mut self) {
         let inherited_struct =
         % if property.style_struct.inherited:
             self.inherited_style.get_${property.style_struct.name_lower}();
         % else:
-            self.inherited_style_ignoring_first_line.get_${property.style_struct.name_lower}();
+            self.inherited_style_ignoring_first_line
+                .get_${property.style_struct.name_lower}();
         % endif
 
         % if not property.style_struct.inherited:
         self.flags.insert(::properties::computed_value_flags::INHERITS_RESET_STYLE);
+        self.modified_reset = true;
         % endif
 
         % if property.ident == "content":
         self.flags.insert(::properties::computed_value_flags::INHERITS_CONTENT);
         % endif
 
         % if property.ident == "display":
         self.flags.insert(::properties::computed_value_flags::INHERITS_DISPLAY);
@@ -2694,32 +2759,40 @@ impl<'a> StyleBuilder<'a> {
     }
 
     /// Reset `${property.ident}` to the initial value.
     #[allow(non_snake_case)]
     pub fn reset_${property.ident}(&mut self) {
         let reset_struct =
             self.reset_style.get_${property.style_struct.name_lower}();
 
+        % if not property.style_struct.inherited:
+        self.modified_reset = true;
+        % endif
+
         self.${property.style_struct.ident}.mutate()
             .reset_${property.ident}(
                 reset_struct,
                 % if property.logical:
                 self.writing_mode,
                 % endif
             );
     }
 
     % if not property.is_vector:
     /// Set the `${property.ident}` to the computed value `value`.
     #[allow(non_snake_case)]
     pub fn set_${property.ident}(
         &mut self,
         value: longhands::${property.ident}::computed_value::T
     ) {
+        % if not property.style_struct.inherited:
+        self.modified_reset = true;
+        % endif
+
         <% props_need_device = ["content", "list_style_type", "font_variant_alternates"] %>
         self.${property.style_struct.ident}.mutate()
             .set_${property.ident}(
                 value,
                 % if property.logical:
                 self.writing_mode,
                 % elif product == "gecko" and property.ident in props_need_device:
                 self.device,
@@ -2773,21 +2846,27 @@ impl<'a> StyleBuilder<'a> {
     % for style_struct in data.active_style_structs():
         /// Gets an immutable view of the current `${style_struct.name}` style.
         pub fn get_${style_struct.name_lower}(&self) -> &style_structs::${style_struct.name} {
             &self.${style_struct.ident}
         }
 
         /// Gets a mutable view of the current `${style_struct.name}` style.
         pub fn mutate_${style_struct.name_lower}(&mut self) -> &mut style_structs::${style_struct.name} {
+            % if not property.style_struct.inherited:
+            self.modified_reset = true;
+            % endif
             self.${style_struct.ident}.mutate()
         }
 
         /// Gets a mutable view of the current `${style_struct.name}` style.
         pub fn take_${style_struct.name_lower}(&mut self) -> UniqueArc<style_structs::${style_struct.name}> {
+            % if not property.style_struct.inherited:
+            self.modified_reset = true;
+            % endif
             self.${style_struct.ident}.take()
         }
 
         /// Gets a mutable view of the current `${style_struct.name}` style.
         pub fn put_${style_struct.name_lower}(&mut self, s: UniqueArc<style_structs::${style_struct.name}>) {
             self.${style_struct.ident}.put(s)
         }
 
@@ -2826,16 +2905,26 @@ impl<'a> StyleBuilder<'a> {
 
     /// Whether this style has a top-layer style.
     #[cfg(feature = "gecko")]
     pub fn in_top_layer(&self) -> bool {
         matches!(self.get_box().clone__moz_top_layer(),
                  longhands::_moz_top_layer::computed_value::T::top)
     }
 
+    /// Clears the "have any reset structs been modified" flag.
+    fn clear_modified_reset(&mut self) {
+        self.modified_reset = false;
+    }
+
+    /// Returns whether we have mutated any reset structs since the the last
+    /// time `clear_modified_reset` was called.
+    fn modified_reset(&self) -> bool {
+        self.modified_reset
+    }
 
     /// Turns this `StyleBuilder` into a proper `ComputedValues` instance.
     pub fn build(self) -> Arc<ComputedValues> {
         ComputedValues::new(
             self.device,
             self.parent_style,
             self.pseudo,
             self.custom_properties,
@@ -3010,17 +3099,19 @@ pub fn cascade(
     rule_node: &StrongRuleNode,
     guards: &StylesheetGuards,
     parent_style: Option<<&ComputedValues>,
     parent_style_ignoring_first_line: Option<<&ComputedValues>,
     layout_parent_style: Option<<&ComputedValues>,
     visited_style: Option<Arc<ComputedValues>>,
     font_metrics_provider: &FontMetricsProvider,
     flags: CascadeFlags,
-    quirks_mode: QuirksMode
+    quirks_mode: QuirksMode,
+    rule_cache: Option<<&RuleCache>,
+    rule_cache_conditions: &mut RuleCacheConditions,
 ) -> Arc<ComputedValues> {
     debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some());
     #[cfg(feature = "gecko")]
     debug_assert!(parent_style.is_none() ||
                   ptr::eq(parent_style.unwrap(),
                           parent_style_ignoring_first_line.unwrap()) ||
                   parent_style.unwrap().pseudo() == Some(PseudoElement::FirstLine));
     let empty = SmallBitVec::new();
@@ -3070,16 +3161,18 @@ pub fn cascade(
         iter_declarations,
         parent_style,
         parent_style_ignoring_first_line,
         layout_parent_style,
         visited_style,
         font_metrics_provider,
         flags,
         quirks_mode,
+        rule_cache,
+        rule_cache_conditions,
     )
 }
 
 /// 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,
@@ -3088,16 +3181,18 @@ pub fn apply_declarations<'a, F, I>(
     iter_declarations: F,
     parent_style: Option<<&ComputedValues>,
     parent_style_ignoring_first_line: Option<<&ComputedValues>,
     layout_parent_style: Option<<&ComputedValues>,
     visited_style: Option<Arc<ComputedValues>>,
     font_metrics_provider: &FontMetricsProvider,
     flags: CascadeFlags,
     quirks_mode: QuirksMode,
+    rule_cache: Option<<&RuleCache>,
+    rule_cache_conditions: &mut RuleCacheConditions,
 ) -> Arc<ComputedValues>
 where
     F: Fn() -> I,
     I: Iterator<Item = (&'a PropertyDeclaration, CascadeLevel)>,
 {
     debug_assert!(layout_parent_style.is_none() || parent_style.is_some());
     debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some());
     #[cfg(feature = "gecko")]
@@ -3144,21 +3239,23 @@ where
             flags,
             Some(rules.clone()),
             custom_properties,
             WritingMode::empty(),
             inherited_style.font_computation_data,
             ComputedValueFlags::empty(),
             visited_style,
         ),
-        font_metrics_provider: font_metrics_provider,
         cached_system_font: None,
         in_media_query: false,
-        quirks_mode: quirks_mode,
         for_smil_animation: false,
+        for_non_inherited_property: None,
+        font_metrics_provider,
+        quirks_mode,
+        rule_cache_conditions: RefCell::new(rule_cache_conditions),
     };
 
     let ignore_colors = !device.use_document_colors();
     let default_background_color_decl = if ignore_colors {
         let color = device.default_background_color();
         Some(PropertyDeclaration::BackgroundColor(color.into()))
     } else {
         None
@@ -3171,26 +3268,31 @@ where
     // Declaration blocks are stored in increasing precedence order, we want
     // them in decreasing order here.
     //
     // We could (and used to) use a pattern match here, but that bloats this
     // function to over 100K of compiled code!
     //
     // To improve i-cache behavior, we outline the individual functions and use
     // virtual dispatch instead.
+    let mut apply_reset = true;
     % for category_to_cascade_now in ["early", "other"]:
         % if category_to_cascade_now == "early":
             // Pull these out so that we can compute them in a specific order
             // without introducing more iterations.
             let mut font_size = None;
             let mut font_family = None;
         % endif
         for (declaration, cascade_level) in iter_declarations() {
             let mut declaration = match *declaration {
                 PropertyDeclaration::WithVariables(id, ref unparsed) => {
+                    if !id.inherited() {
+                        context.rule_cache_conditions.borrow_mut()
+                            .set_uncacheable();
+                    }
                     Cow::Owned(unparsed.substitute_variables(
                         id,
                         &context.builder.custom_properties,
                         context.quirks_mode
                     ))
                 }
                 ref d => Cow::Borrowed(d)
             };
@@ -3203,16 +3305,20 @@ where
             // Only a few properties are allowed to depend on the visited state
             // of links.  When cascading visited styles, we can save time by
             // only processing these properties.
             if flags.contains(VISITED_DEPENDENT_ONLY) &&
                !longhand_id.is_visited_dependent() {
                 continue
             }
 
+            if !apply_reset && !longhand_id.inherited() {
+                continue;
+            }
+
             // When document colors are disabled, skip properties that are
             // marked as ignored in that mode, if they come from a UA or
             // user style sheet.
             if ignore_colors &&
                longhand_id.is_ignored_when_document_colors_disabled() &&
                !matches!(cascade_level,
                          CascadeLevel::UANormal |
                          CascadeLevel::UserNormal |
@@ -3370,26 +3476,26 @@ where
                       font_family.is_some() {
                 let discriminant = LonghandId::FontSize as usize;
                 let size = PropertyDeclaration::CSSWideKeyword(
                     LonghandId::FontSize, CSSWideKeyword::Inherit);
 
                 (CASCADE_PROPERTY[discriminant])(&size, &mut context);
             % endif
             }
-        % endif
+
+            if let Some(style) = rule_cache.and_then(|c| c.find(&context.builder)) {
+                context.builder.copy_reset_from(style);
+                apply_reset = false;
+            }
+        % endif // category == "early"
     % endfor
 
     let mut builder = context.builder;
 
-    {
-        StyleAdjuster::new(&mut builder)
-            .adjust(layout_parent_style, flags);
-    }
-
     % if product == "gecko":
         if let Some(ref mut bg) = builder.get_background_if_mutated() {
             bg.fill_arrays();
         }
 
         if let Some(ref mut svg) = builder.get_svg_if_mutated() {
             svg.fill_arrays();
         }
@@ -3399,16 +3505,32 @@ where
         if seen.contains(LonghandId::FontStyle) ||
            seen.contains(LonghandId::FontWeight) ||
            seen.contains(LonghandId::FontStretch) ||
            seen.contains(LonghandId::FontFamily) {
             builder.mutate_font().compute_font_hash();
         }
     % endif
 
+    builder.clear_modified_reset();
+
+    StyleAdjuster::new(&mut builder)
+        .adjust(layout_parent_style, flags);
+
+    if builder.modified_reset() || !apply_reset {
+        // If we adjusted any reset structs, we can't cache this ComputedValues.
+        //
+        // Also, if we re-used existing reset structs, don't bother caching it
+        // back again. (Aside from being wasted effort, it will be wrong, since
+        // context.rule_cache_conditions won't be set appropriately if we
+        // didn't compute those reset properties.)
+        context.rule_cache_conditions.borrow_mut()
+            .set_uncacheable();
+    }
+
     builder.build()
 }
 
 /// See StyleAdjuster::adjust_for_border_width.
 pub fn adjust_border_width(style: &mut StyleBuilder) {
     % for side in ["top", "right", "bottom", "left"]:
         // Like calling to_computed_value, which wouldn't type check.
         if style.get_border().clone_border_${side}_style().none_or_hidden() &&
new file mode 100644
--- /dev/null
+++ b/servo/components/style/rule_cache.rs
@@ -0,0 +1,159 @@
+/* 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/. */
+
+//! A cache from rule node to computed values, in order to cache reset
+//! properties.
+
+use fnv::FnvHashMap;
+use logical_geometry::WritingMode;
+use properties::{ComputedValues, StyleBuilder};
+use rule_tree::StrongRuleNode;
+use selector_parser::PseudoElement;
+use servo_arc::Arc;
+use smallvec::SmallVec;
+use values::computed::NonNegativeLength;
+
+/// The conditions for caching and matching a style in the rule cache.
+#[derive(Clone, Debug, Default)]
+pub struct RuleCacheConditions {
+    uncacheable: bool,
+    font_size: Option<NonNegativeLength>,
+    writing_mode: Option<WritingMode>,
+}
+
+impl RuleCacheConditions {
+    /// Sets the style as depending in the font-size value.
+    pub fn set_font_size_dependency(&mut self, font_size: NonNegativeLength) {
+        debug_assert!(self.font_size.map_or(true, |f| f == font_size));
+        self.font_size = Some(font_size);
+    }
+
+    /// Sets the style as uncacheable.
+    pub fn set_uncacheable(&mut self) {
+        self.uncacheable = true;
+    }
+
+    /// Sets the style as depending in the writing-mode value `writing_mode`.
+    pub fn set_writing_mode_dependency(&mut self, writing_mode: WritingMode) {
+        debug_assert!(self.writing_mode.map_or(true, |wm| wm == writing_mode));
+        self.writing_mode = Some(writing_mode);
+    }
+
+    /// Returns whether the current style's reset properties are cacheable.
+    fn cacheable(&self) -> bool {
+        !self.uncacheable
+    }
+
+    /// Returns whether `style` matches the conditions.
+    fn matches(&self, style: &StyleBuilder) -> bool {
+        if self.uncacheable {
+            return false;
+        }
+
+        if let Some(fs) = self.font_size {
+            if style.get_font().clone_font_size() != fs {
+                return false;
+            }
+        }
+
+        if let Some(wm) = self.writing_mode {
+            if style.writing_mode != wm {
+                return false;
+            }
+        }
+
+        true
+    }
+}
+
+/// A TLS cache from rules matched to computed values.
+pub struct RuleCache {
+    // FIXME(emilio): Consider using LRUCache or something like that?
+    map: FnvHashMap<StrongRuleNode, SmallVec<[(RuleCacheConditions, Arc<ComputedValues>); 1]>>,
+}
+
+impl RuleCache {
+    /// Creates an empty `RuleCache`.
+    pub fn new() -> Self {
+        Self {
+            map: FnvHashMap::default(),
+        }
+    }
+
+    /// Finds a node in the properties matched cache.
+    ///
+    /// This needs to receive a `StyleBuilder` with the `early` properties
+    /// already applied.
+    pub fn find(
+        &self,
+        builder_with_early_props: &StyleBuilder,
+    ) -> Option<&ComputedValues> {
+        if builder_with_early_props.is_style_if_visited() {
+            // FIXME(emilio): We can probably do better, does it matter much?
+            return None;
+        }
+
+        // A pseudo-element with property restrictions can result in different
+        // computed values if it's also used for a non-pseudo.
+        if builder_with_early_props.pseudo
+           .and_then(|p| p.property_restriction())
+           .is_some() {
+            return None;
+        }
+
+        let rules = match builder_with_early_props.rules {
+            Some(ref rules) => rules,
+            None => return None,
+        };
+
+        self.map.get(rules).and_then(|cached_values| {
+            for &(ref conditions, ref values) in cached_values.iter() {
+                if conditions.matches(builder_with_early_props) {
+                    debug!("Using cached reset style with conditions {:?}", conditions);
+                    return Some(&**values)
+                }
+            }
+            None
+        })
+    }
+
+    /// Inserts a node into the rules cache if possible.
+    ///
+    /// Returns whether the style was inserted into the cache.
+    pub fn insert_if_possible(
+        &mut self,
+        style: &Arc<ComputedValues>,
+        pseudo: Option<&PseudoElement>,
+        conditions: &RuleCacheConditions,
+    ) -> bool {
+        if !conditions.cacheable() {
+            return false;
+        }
+
+        if style.is_style_if_visited() {
+            // FIXME(emilio): We can probably do better, does it matter much?
+            return false;
+        }
+
+        // A pseudo-element with property restrictions can result in different
+        // computed values if it's also used for a non-pseudo.
+        if pseudo.and_then(|p| p.property_restriction()).is_some() {
+            return false;
+        }
+
+        let rules = match style.rules {
+            Some(ref r) => r.clone(),
+            None => return false,
+        };
+
+        debug!("Inserting cached reset style with conditions {:?}", conditions);
+        self.map
+            .entry(rules)
+            .or_insert_with(SmallVec::new)
+            .push((conditions.clone(), style.clone()));
+
+        true
+    }
+
+}
--- a/servo/components/style/rule_tree/mod.rs
+++ b/servo/components/style/rule_tree/mod.rs
@@ -814,17 +814,17 @@ impl MallocSizeOf for RuleNode {
 }
 
 #[derive(Clone)]
 struct WeakRuleNode {
     p: NonZeroPtrMut<RuleNode>,
 }
 
 /// A strong reference to a rule node.
-#[derive(Debug, PartialEq)]
+#[derive(Debug, Eq, Hash, PartialEq)]
 pub struct StrongRuleNode {
     p: NonZeroPtrMut<RuleNode>,
 }
 
 #[cfg(feature = "servo")]
 impl HeapSizeOf for StrongRuleNode {
     fn heap_size_of_children(&self) -> usize { 0 }
 }
--- a/servo/components/style/servo/media_queries.rs
+++ b/servo/components/style/servo/media_queries.rs
@@ -8,17 +8,19 @@ use app_units::Au;
 use context::QuirksMode;
 use cssparser::{Parser, RGBA};
 use euclid::{ScaleFactor, Size2D, TypedSize2D};
 use font_metrics::ServoMetricsProvider;
 use media_queries::MediaType;
 use parser::ParserContext;
 use properties::{ComputedValues, StyleBuilder};
 use properties::longhands::font_size;
+use rule_cache::RuleCacheConditions;
 use selectors::parser::SelectorParseError;
+use std::cell::RefCell;
 use std::fmt;
 use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
 use style_traits::{CSSPixel, DevicePixel, ToCss, ParseError};
 use style_traits::viewport::ViewportConstraints;
 use values::computed::{self, ToComputedValue};
 use values::specified;
 
 /// A device is a structure that represents the current media a given document
@@ -239,29 +241,32 @@ pub enum Range<T> {
     Max(T),
     /// Exactly the inner value.
     Eq(T),
 }
 
 impl Range<specified::Length> {
     fn to_computed_range(&self, device: &Device, quirks_mode: QuirksMode) -> Range<Au> {
         let default_values = device.default_computed_values();
+        let mut conditions = RuleCacheConditions::default();
         // http://dev.w3.org/csswg/mediaqueries3/#units
         // em units are relative to the initial font-size.
         let context = computed::Context {
             is_root_element: false,
             builder: StyleBuilder::for_derived_style(device, default_values, None, None),
             // Servo doesn't support font metrics
             // A real provider will be needed here once we do; since
             // ch units can exist in media queries.
             font_metrics_provider: &ServoMetricsProvider,
             in_media_query: true,
             cached_system_font: None,
             quirks_mode: quirks_mode,
             for_smil_animation: false,
+            for_non_inherited_property: None,
+            rule_cache_conditions: RefCell::new(&mut conditions),
         };
 
         match *self {
             Range::Min(ref width) => Range::Min(Au::from(width.to_computed_value(&context))),
             Range::Max(ref width) => Range::Max(Au::from(width.to_computed_value(&context))),
             Range::Eq(ref width) => Range::Eq(Au::from(width.to_computed_value(&context)))
         }
     }
--- a/servo/components/style/style_resolver.rs
+++ b/servo/components/style/style_resolver.rs
@@ -569,26 +569,36 @@ where
         }
         if self.element.is_native_anonymous() || pseudo.is_some() {
             cascade_flags.insert(PROHIBIT_DISPLAY_CONTENTS);
         } else if self.element.is_root() {
             cascade_flags.insert(IS_ROOT_ELEMENT);
         }
 
         let implemented_pseudo = self.element.implemented_pseudo_element();
+        let pseudo = pseudo.or(implemented_pseudo.as_ref());
+
+        let mut conditions = Default::default();
         let values =
             cascade(
                 self.context.shared.stylist.device(),
-                pseudo.or(implemented_pseudo.as_ref()),
+                pseudo,
                 rules.unwrap_or(self.context.shared.stylist.rule_tree().root()),
                 &self.context.shared.guards,
                 parent_style,
                 parent_style,
                 layout_parent_style,
                 style_if_visited,
                 &self.context.thread_local.font_metrics_provider,
                 cascade_flags,
                 self.context.shared.quirks_mode(),
+                Some(&self.context.thread_local.rule_cache),
+                &mut conditions,
             );
 
+        self.context
+            .thread_local
+            .rule_cache
+            .insert_if_possible(&values, pseudo, &conditions);
+
         values
     }
 }
--- a/servo/components/style/stylesheets/viewport_rule.rs
+++ b/servo/components/style/stylesheets/viewport_rule.rs
@@ -12,20 +12,22 @@ use context::QuirksMode;
 use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser, parse_important};
 use cssparser::{CowRcStr, ToCss as ParserToCss};
 use error_reporting::{ContextualParseError, ParseErrorReporter};
 use euclid::TypedSize2D;
 use font_metrics::get_metrics_provider_for_product;
 use media_queries::Device;
 use parser::{ParserContext, ParserErrorContext};
 use properties::StyleBuilder;
+use rule_cache::RuleCacheConditions;
 use selectors::parser::SelectorParseError;
 use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
 use std::ascii::AsciiExt;
 use std::borrow::Cow;
+use std::cell::RefCell;
 use std::fmt;
 use std::iter::Enumerate;
 use std::str::Chars;
 use style_traits::{PinchZoomFactor, ToCss, ParseError, StyleParseError};
 use style_traits::viewport::{Orientation, UserZoom, ViewportConstraints, Zoom};
 use stylesheets::{StylesheetInDocument, Origin};
 use values::computed::{Context, ToComputedValue};
 use values::specified::{NoCalcLength, LengthOrPercentageOrAuto, ViewportPercentageLength};
@@ -702,24 +704,27 @@ impl MaybeNew for ViewportConstraints {
         // Note: DEVICE-ADAPT § 5. states that relative length values are
         // resolved against initial values
         let initial_viewport = device.au_viewport_size();
 
         let provider = get_metrics_provider_for_product();
 
         let default_values = device.default_computed_values();
 
+        let mut conditions = RuleCacheConditions::default();
         let context = Context {
             is_root_element: false,
             builder: StyleBuilder::for_derived_style(device, default_values, None, None),
             font_metrics_provider: &provider,
             cached_system_font: None,
             in_media_query: false,
             quirks_mode: quirks_mode,
             for_smil_animation: false,
+            for_non_inherited_property: None,
+            rule_cache_conditions: RefCell::new(&mut conditions),
         };
 
         // DEVICE-ADAPT § 9.3 Resolving 'extend-to-zoom'
         let extend_width;
         let extend_height;
         if let Some(extend_zoom) = max!(initial_zoom, max_zoom) {
             let scale_factor = 1. / extend_zoom;
             extend_width = Some(initial_viewport.width.scale_by(scale_factor));
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -766,16 +766,18 @@ impl Stylist {
             guards,
             parent,
             parent,
             parent,
             None,
             font_metrics,
             cascade_flags,
             self.quirks_mode,
+            /* rule_cache = */ None,
+            &mut Default::default(),
         )
     }
 
     /// Returns the rule node for given precomputed pseudo-element.
     ///
     /// If we want to include extra declarations to this precomputed pseudo-element,
     /// we can provide a vector of ApplicableDeclarationBlock to extra_declarations
     /// argument. This is useful for providing extra @page rules.
@@ -982,16 +984,18 @@ impl Stylist {
                 guards,
                 Some(inherited_style),
                 Some(inherited_style_ignoring_first_line),
                 Some(layout_parent_style_for_visited),
                 None,
                 font_metrics,
                 cascade_flags,
                 self.quirks_mode,
+                /* rule_cache = */ None,
+                &mut Default::default(),
             ))
         } else {
             None
         };
 
         // We may not have non-visited rules, if we only had visited ones.  In
         // that case we want to use the root rulenode for our non-visited rules.
         let rules = inputs.rules.as_ref().unwrap_or(self.rule_tree.root());
@@ -1007,16 +1011,18 @@ impl Stylist {
             guards,
             Some(parent_style),
             Some(parent_style_ignoring_first_line),
             Some(layout_parent_style),
             visited_values,
             font_metrics,
             cascade_flags,
             self.quirks_mode,
+            /* rule_cache = */ None,
+            &mut Default::default(),
         )
     }
 
     fn has_rules_for_pseudo(&self, pseudo: &PseudoElement) -> bool {
         self.cascade_data
             .iter_origins()
             .any(|(d, _)| d.has_rules_for_pseudo(pseudo))
     }
@@ -1620,16 +1626,18 @@ impl Stylist {
             guards,
             Some(parent_style),
             Some(parent_style),
             Some(parent_style),
             None,
             &metrics,
             CascadeFlags::empty(),
             self.quirks_mode,
+            /* rule_cache = */ None,
+            &mut Default::default(),
         )
     }
 
     /// Accessor for a shared reference to the device.
     pub fn device(&self) -> &Device {
         &self.device
     }
 
--- a/servo/components/style/values/computed/length.rs
+++ b/servo/components/style/values/computed/length.rs
@@ -861,16 +861,20 @@ impl ToComputedValue for specified::MozL
 
     #[inline]
     fn to_computed_value(&self, context: &Context) -> MozLength {
         match *self {
             specified::MozLength::LengthOrPercentageOrAuto(ref lopoa) => {
                 MozLength::LengthOrPercentageOrAuto(lopoa.to_computed_value(context))
             }
             specified::MozLength::ExtremumLength(ref ext) => {
+                debug_assert!(context.for_non_inherited_property.is_some(),
+                              "should check whether we're a non-inherited property");
+                context.rule_cache_conditions.borrow_mut()
+                    .set_writing_mode_dependency(context.builder.writing_mode);
                 MozLength::ExtremumLength(ext.clone())
             }
         }
     }
 
     #[inline]
     fn from_computed_value(computed: &MozLength) -> Self {
         match *computed {
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -6,20 +6,22 @@
 
 use {Atom, Namespace};
 use context::QuirksMode;
 use euclid::Size2D;
 use font_metrics::FontMetricsProvider;
 use media_queries::Device;
 #[cfg(feature = "gecko")]
 use properties;
-use properties::{ComputedValues, StyleBuilder};
+use properties::{ComputedValues, LonghandId, StyleBuilder};
+use rule_cache::RuleCacheConditions;
 #[cfg(feature = "servo")]
 use servo_url::ServoUrl;
 use std::{f32, fmt};
+use std::cell::RefCell;
 #[cfg(feature = "servo")]
 use std::sync::Arc;
 use style_traits::ToCss;
 use style_traits::cursor::Cursor;
 use super::{CSSFloat, CSSInteger};
 use super::generics::{GreaterThanOrEqualToOne, NonNegative};
 use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth};
 use super::generics::grid::{TrackSize as GenericTrackSize, TrackList as GenericTrackList};
@@ -111,16 +113,27 @@ pub struct Context<'a> {
     /// The quirks mode of this context.
     pub quirks_mode: QuirksMode,
 
     /// Whether this computation is being done for a SMIL animation.
     ///
     /// This is used to allow certain properties to generate out-of-range
     /// values, which SMIL allows.
     pub for_smil_animation: bool,
+
+    /// The property we are computing a value for, if it is a non-inherited
+    /// property.  None if we are computed a value for an inherited property
+    /// or not computing for a property at all (e.g. in a media query
+    /// evaluation).
+    pub for_non_inherited_property: Option<LonghandId>,
+
+    /// The conditions to cache a rule node on the rule cache.
+    ///
+    /// FIXME(emilio): Drop the refcell.
+    pub rule_cache_conditions: RefCell<&'a mut RuleCacheConditions>,
 }
 
 impl<'a> Context<'a> {
     /// Whether the current element is the root element.
     pub fn is_root_element(&self) -> bool {
         self.is_root_element
     }
 
--- a/servo/components/style/values/specified/color.rs
+++ b/servo/components/style/values/specified/color.rs
@@ -244,42 +244,60 @@ impl Color {
 fn convert_nscolor_to_computedcolor(color: nscolor) -> ComputedColor {
     use gecko::values::convert_nscolor_to_rgba;
     ComputedColor::rgba(convert_nscolor_to_rgba(color))
 }
 
 impl ToComputedValue for Color {
     type ComputedValue = ComputedColor;
 
-    fn to_computed_value(&self, _context: &Context) -> ComputedColor {
+    fn to_computed_value(&self, context: &Context) -> ComputedColor {
         match *self {
-            Color::CurrentColor => ComputedColor::currentcolor(),
+            Color::CurrentColor => {
+                if let Some(longhand) = context.for_non_inherited_property {
+                    if longhand.stores_complex_colors_lossily() {
+                        context.rule_cache_conditions.borrow_mut()
+                            .set_uncacheable();
+                    }
+                }
+                ComputedColor::currentcolor()
+            }
             Color::Numeric { ref parsed, .. } => ComputedColor::rgba(*parsed),
-            Color::Complex(ref complex) => *complex,
+            Color::Complex(ref complex) => {
+                if complex.foreground_ratio != 0 {
+                    if let Some(longhand) = context.for_non_inherited_property {
+                        if longhand.stores_complex_colors_lossily() {
+                            context.rule_cache_conditions.borrow_mut()
+                                .set_uncacheable();
+                        }
+                    }
+                }
+                *complex
+            }
             #[cfg(feature = "gecko")]
             Color::System(system) =>
-                convert_nscolor_to_computedcolor(system.to_computed_value(_context)),
+                convert_nscolor_to_computedcolor(system.to_computed_value(context)),
             #[cfg(feature = "gecko")]
             Color::Special(special) => {
                 use self::gecko::SpecialColorKeyword as Keyword;
-                let pres_context = _context.device().pres_context();
+                let pres_context = context.device().pres_context();
                 convert_nscolor_to_computedcolor(match special {
                     Keyword::MozDefaultColor => pres_context.mDefaultColor,
                     Keyword::MozDefaultBackgroundColor => pres_context.mBackgroundColor,
                     Keyword::MozHyperlinktext => pres_context.mLinkColor,
                     Keyword::MozActiveHyperlinktext => pres_context.mActiveLinkColor,
                     Keyword::MozVisitedHyperlinktext => pres_context.mVisitedLinkColor,
                 })
             }
             #[cfg(feature = "gecko")]
             Color::InheritFromBodyQuirk => {
                 use dom::TElement;
                 use gecko::wrapper::GeckoElement;
                 use gecko_bindings::bindings::Gecko_GetBody;
-                let pres_context = _context.device().pres_context();
+                let pres_context = context.device().pres_context();
                 let body = unsafe { Gecko_GetBody(pres_context) }.map(GeckoElement);
                 let data = body.as_ref().and_then(|wrap| wrap.borrow_data());
                 if let Some(data) = data {
                     ComputedColor::rgba(data.styles.primary()
                                             .get_color()
                                             .clone_color())
                 } else {
                     convert_nscolor_to_computedcolor(pres_context.mDefaultColor)
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -127,19 +127,28 @@ impl FontRelativeLength {
                                                 context.in_media_query,
                                                 context.device())
         }
 
         let reference_font_size = base_size.resolve(context);
 
         match *self {
             FontRelativeLength::Em(length) => {
+                if !matches!(base_size, FontBaseSize::InheritedStyle) {
+                    context.rule_cache_conditions.borrow_mut()
+                        .set_font_size_dependency(
+                            reference_font_size.into()
+                        );
+                }
                 (reference_font_size, length)
             },
             FontRelativeLength::Ex(length) => {
+                if context.for_non_inherited_property.is_some() {
+                    context.rule_cache_conditions.borrow_mut().set_uncacheable();
+                }
                 let reference_size = match query_font_metrics(context, reference_font_size) {
                     FontMetricsQueryResult::Available(metrics) => {
                         metrics.x_height
                     },
                     // https://drafts.csswg.org/css-values/#ex
                     //
                     //     In the cases where it is impossible or impractical to
                     //     determine the x-height, a value of 0.5em must be
@@ -147,16 +156,19 @@ impl FontRelativeLength {
                     //
                     FontMetricsQueryResult::NotAvailable => {
                         reference_font_size.scale_by(0.5)
                     },
                 };
                 (reference_size, length)
             },
             FontRelativeLength::Ch(length) => {
+                if context.for_non_inherited_property.is_some() {
+                    context.rule_cache_conditions.borrow_mut().set_uncacheable();
+                }
                 let reference_size = match query_font_metrics(context, reference_font_size) {
                     FontMetricsQueryResult::Available(metrics) => {
                         metrics.zero_advance_measure
                     },
                     // https://drafts.csswg.org/css-values/#ch
                     //
                     //     In the cases where it is impossible or impractical to
                     //     determine the measure of the “0” glyph, it must be
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -4,16 +4,17 @@
 
 use cssparser::{Parser, ParserInput};
 use cssparser::ToCss as ParserToCss;
 use env_logger::LogBuilder;
 use malloc_size_of::MallocSizeOfOps;
 use selectors::Element;
 use selectors::matching::{MatchingContext, MatchingMode, matches_selector};
 use servo_arc::{Arc, ArcBorrow, RawOffsetArc};
+use std::cell::RefCell;
 use std::env;
 use std::fmt::Write;
 use std::iter;
 use std::ptr;
 use style::applicable_declarations::ApplicableDeclarationBlock;
 use style::context::{CascadeInputs, QuirksMode, SharedStyleContext, StyleContext};
 use style::context::ThreadLocalStyleContext;
 use style::data::ElementStyles;
@@ -111,16 +112,17 @@ use style::parser::{ParserContext, self}
 use style::properties::{CascadeFlags, ComputedValues, Importance};
 use style::properties::{IS_FIELDSET_CONTENT, IS_LINK, IS_VISITED_LINK, LonghandIdSet};
 use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, PropertyId, ShorthandId};
 use style::properties::{SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, SourcePropertyDeclaration, StyleBuilder};
 use style::properties::PROHIBIT_DISPLAY_CONTENTS;
 use style::properties::animated_properties::{AnimatableLonghand, AnimationValue};
 use style::properties::animated_properties::compare_property_priority;
 use style::properties::parse_one_declaration_into;
+use style::rule_cache::RuleCacheConditions;
 use style::rule_tree::{CascadeLevel, StyleSource};
 use style::selector_parser::PseudoElementCascadeType;
 use style::shared_lock::{SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard, Locked};
 use style::string_cache::Atom;
 use style::style_adjuster::StyleAdjuster;
 use style::stylesheets::{CssRule, CssRules, CssRuleType, CssRulesHelpers, DocumentRule};
 use style::stylesheets::{FontFeatureValuesRule, ImportRule, KeyframesRule, MediaRule};
 use style::stylesheets::{NamespaceRule, Origin, OriginSet, PageRule, StyleRule};
@@ -3181,30 +3183,33 @@ fn simulate_compute_values_failure(_: &P
 
 fn create_context<'a>(
     per_doc_data: &'a PerDocumentStyleDataImpl,
     font_metrics_provider: &'a FontMetricsProvider,
     style: &'a ComputedValues,
     parent_style: Option<&'a ComputedValues>,
     pseudo: Option<&'a PseudoElement>,
     for_smil_animation: bool,
+    rule_cache_conditions: &'a mut RuleCacheConditions,
 ) -> Context<'a> {
     Context {
         is_root_element: false,
         builder: StyleBuilder::for_derived_style(
             per_doc_data.stylist.device(),
             style,
             parent_style,
             pseudo,
         ),
         font_metrics_provider: font_metrics_provider,
         cached_system_font: None,
         in_media_query: false,
         quirks_mode: per_doc_data.stylist.quirks_mode(),
         for_smil_animation,
+        for_non_inherited_property: None,
+        rule_cache_conditions: RefCell::new(rule_cache_conditions),
     }
 }
 
 struct PropertyAndIndex {
     property: PropertyId,
     index: usize,
 }
 
@@ -3264,23 +3269,25 @@ pub extern "C" fn Servo_GetComputedKeyfr
     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()).map(|x| &**x);
 
     let pseudo = style.pseudo();
+    let mut conditions = Default::default();
     let mut context = create_context(
         &data,
         &metrics,
         &style,
         parent_style,
         pseudo.as_ref(),
         /* for_smil_animation = */ false,
+        &mut conditions,
     );
 
     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];
@@ -3351,23 +3358,25 @@ pub extern "C" fn Servo_GetAnimationValu
     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()).map(|x| &**x);
 
     let pseudo = style.pseudo();
+    let mut conditions = Default::default();
     let mut context = create_context(
         &data,
         &metrics,
         &style,
         parent_style,
         pseudo.as_ref(),
-        /* for_smil_animation = */ true
+        /* for_smil_animation = */ true,
+        &mut conditions,
     );
 
     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);
@@ -3387,23 +3396,25 @@ pub extern "C" fn Servo_AnimationValue_C
     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()).map(|x| &**x);
 
     let pseudo = style.pseudo();
+    let mut conditions = Default::default();
     let mut context = create_context(
         &data,
         &metrics,
         style,
         parent_style,
         pseudo.as_ref(),
-        /* for_smil_animation = */ false
+        /* for_smil_animation = */ false,
+        &mut conditions,
     );
 
     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).declaration_importance_iter().next() {