style: Add support for resolving default computed styles. draft
authorCameron McCormack <cam@mcc.id.au>
Wed, 24 May 2017 14:38:10 +0800
changeset 583534 b8cfc1647628498135eaa9be007e2d70ab7cb8f9
parent 583533 776fea6748da0283b8646f6a591175c04116ad24
child 630090 dfaa0f94ebc632ea6ecad4f13c038f2c4075de3c
push id60428
push userbmo:cam@mcc.id.au
push dateWed, 24 May 2017 07:04:24 +0000
milestone55.0a1
style: Add support for resolving default computed styles. MozReview-Commit-ID: 6lt7TDgTNuE
servo/components/style/gecko/wrapper.rs
servo/components/style/matching.rs
servo/components/style/stylist.rs
servo/components/style/traversal.rs
servo/ports/geckolib/glue.rs
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -67,16 +67,18 @@ use selectors::Element;
 use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint};
 use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
 use shared_lock::Locked;
 use sink::Push;
 use std::cell::RefCell;
 use std::collections::HashMap;
 use std::fmt;
 use std::hash::{Hash, Hasher};
+use std::mem;
+use std::ops::DerefMut;
 use std::ptr;
 use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
 use stylearc::Arc;
 use stylesheets::UrlExtraData;
 use stylist::ApplicableDeclarationBlock;
 
 /// A simple wrapper over a non-null Gecko node (`nsINode`) pointer.
 ///
@@ -417,16 +419,39 @@ impl<'le> GeckoElement<'le> {
                 debug!("Creating ElementData for {:?}", self);
                 let ptr = Box::into_raw(Box::new(AtomicRefCell::new(ElementData::new(None))));
                 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, new_element_data: Option<ElementData>) -> Option<ElementData> {
+        match (self.get_data(), new_element_data) {
+            (Some(old_data), Some(new_element_data)) => {
+                Some(mem::replace(old_data.borrow_mut().deref_mut(), new_element_data))
+            }
+            (Some(old_data), None) => {
+                let old_element_data = mem::replace(old_data.borrow_mut().deref_mut(), ElementData::new(None));
+                self.0.mServoData.set(ptr::null_mut());
+                Some(old_element_data)
+            }
+            (None, Some(new_element_data)) => {
+                let ptr = Box::into_raw(Box::new(AtomicRefCell::new(new_element_data)));
+                self.0.mServoData.set(ptr);
+                None
+            }
+            (None, None) => None,
+        }
+    }
+
     #[inline]
     fn may_have_animations(&self) -> bool {
         self.as_node().get_bool_flag(nsINode_BooleanFlag::ElementHasAnimations)
     }
 
     #[inline]
     fn has_id(&self) -> bool {
         self.as_node().get_bool_flag(nsINode_BooleanFlag::ElementHasID)
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -19,17 +19,17 @@ use restyle_hints::{RESTYLE_CSS_ANIMATIO
 use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RESTYLE_SMIL};
 use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode};
 use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
 use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, StyleRelations};
 use selectors::matching::AFFECTED_BY_PSEUDO_ELEMENTS;
 use shared_lock::StylesheetGuards;
 use sharing::{StyleSharingBehavior, StyleSharingResult};
 use stylearc::Arc;
-use stylist::ApplicableDeclarationList;
+use stylist::{ApplicableDeclarationList, RuleInclusion};
 
 /// The way a style should be inherited.
 enum InheritMode {
     /// Inherit from the parent element, as normal CSS dictates, _or_ from the
     /// closest non-Native Anonymous element in case this is Native Anonymous
     /// Content.
     Normal,
     /// Inherit from the primary style, this is used while computing eager
@@ -249,17 +249,23 @@ trait PrivateMatchMethods: TElement {
             // parent if it's eager, and recascade otherwise.
             //
             // We also recascade if the eager pseudo-style has any animation
             // rules, because we don't cascade those during the eager traversal.
             //
             // We could make that a bit better if the complexity cost is not too
             // big, but given further restyles are posted directly to
             // pseudo-elements, it doesn't seem worth the effort at a glance.
-            if pseudo.is_eager() && self.get_animation_rules().is_empty() {
+            //
+            // For the same reason as described in match_primary, if we are
+            // computing default styles, we aren't guaranteed the parent
+            // will have eagerly computed our styles, so we just handled it
+            // below like a lazy pseudo.
+            let only_default_rules = context.shared.traversal_flags.for_default_styles();
+            if pseudo.is_eager() && self.get_animation_rules().is_empty() && !only_default_rules {
                 let parent = self.parent_element().unwrap();
                 let parent_data = parent.borrow_data().unwrap();
                 let pseudo_style =
                     parent_data.styles().pseudos.get(&pseudo).unwrap();
                 return pseudo_style.values().clone()
             }
         }
 
@@ -677,21 +683,32 @@ pub trait MatchMethods : TElement {
     /// Returns RulesMatchedResult which indicates whether the primary rule node changed
     /// and whether the change includes important rules.
     fn match_primary(&self,
                      context: &mut StyleContext<Self>,
                      data: &mut ElementData,
                      relations: &mut StyleRelations)
                      -> RulesMatchedResult
     {
+        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 {
-            if pseudo.is_eager() {
-                // If it's an eager element-backed pseudo, just grab the matched
-                // rules from the parent, and update animations.
+            if pseudo.is_eager() && !only_default_rules {
+                // If it's an eager element-backed pseudo, we can generally just
+                // grab the matched rules from the parent, and then update
+                // animations.
+                //
+                // However, if we're computing default styles, then we might
+                // have traversed to this pseudo-implementing element without
+                // any pseudo styles stored on the parent.  For example, if
+                // document-level style sheets cause the element to exist, due
+                // to ::before rules, then those rules won't be found when
+                // computing default styles on the parent, so we won't have
+                // bothered to store pseudo styles there.  In this case, we just
+                // treat it like a lazily computed pseudo.
                 let parent = self.parent_element().unwrap();
                 let parent_data = parent.borrow_data().unwrap();
                 let pseudo_style =
                     parent_data.styles().pseudos.get(&pseudo).unwrap();
                 let mut rules = pseudo_style.rules.clone();
                 let animation_rules = self.get_animation_rules();
 
                 // Handle animations here.
@@ -735,31 +752,37 @@ pub trait MatchMethods : TElement {
         let mut applicable_declarations = ApplicableDeclarationList::new();
 
         let stylist = &context.shared.stylist;
         let style_attribute = self.style_attribute();
         let smil_override = self.get_smil_override();
         let animation_rules = self.get_animation_rules();
         let bloom = context.thread_local.bloom_filter.filter();
 
+        let rule_inclusion = if only_default_rules {
+            RuleInclusion::DefaultOnly
+        } else {
+            RuleInclusion::All
+        };
 
         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 mut matching_context =
             MatchingContext::new(MatchingMode::Normal, Some(bloom));
 
         // Compute the primary rule node.
         stylist.push_applicable_declarations(self,
                                              implemented_pseudo.as_ref(),
                                              style_attribute,
                                              smil_override,
                                              animation_rules,
+                                             rule_inclusion,
                                              &mut applicable_declarations,
                                              &mut matching_context,
                                              &mut set_selector_flags);
 
         *relations = matching_context.relations;
 
         let primary_rule_node =
             compute_rule_node::<Self>(stylist.rule_tree(),
@@ -813,16 +836,22 @@ pub trait MatchMethods : TElement {
 
         // 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_tree = stylist.rule_tree();
         let bloom_filter = context.thread_local.bloom_filter.filter();
 
+        let rule_inclusion = if context.shared.traversal_flags.for_default_styles() {
+            RuleInclusion::DefaultOnly
+        } else {
+            RuleInclusion::All
+        };
+
         let mut matching_context =
             MatchingContext::new(MatchingMode::ForStatelessPseudoElement,
                                  Some(bloom_filter));
 
         // Compute rule nodes for eagerly-cascaded pseudo-elements.
         let mut matches_different_pseudos = false;
         let mut rule_nodes_changed = false;
         SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
@@ -830,16 +859,17 @@ pub trait MatchMethods : TElement {
             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);
 
             if !applicable_declarations.is_empty() {
                 let new_rules =
                     compute_rule_node::<Self>(rule_tree,
                                               &mut applicable_declarations,
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -8,17 +8,17 @@ use {Atom, LocalName, Namespace};
 use bit_vec::BitVec;
 use context::{QuirksMode, SharedStyleContext};
 use data::ComputedStyle;
 use dom::{AnimationRules, TElement};
 use element_state::ElementState;
 use error_reporting::RustLogReporter;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")]
-use gecko_bindings::structs::nsIAtom;
+use gecko_bindings::structs::{nsIAtom, StyleRuleInclusion};
 use keyframes::KeyframesAnimation;
 use media_queries::Device;
 use properties::{self, CascadeFlags, ComputedValues};
 #[cfg(feature = "servo")]
 use properties::INHERIT_ALL;
 use properties::PropertyDeclarationBlock;
 use restyle_hints::{HintComputationContext, DependencySet, RestyleHint};
 use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
@@ -200,16 +200,37 @@ pub struct ExtraStyleData<'a> {
     pub marker: PhantomData<&'a usize>,
 }
 
 #[cfg(feature = "servo")]
 impl<'a> ExtraStyleData<'a> {
     fn clear(&mut self) {}
 }
 
+/// What cascade levels to include when styling elements.
+#[derive(Copy, Clone, PartialEq)]
+pub enum RuleInclusion {
+    /// Include rules for style sheets at all cascade levels.  This is the
+    /// normal rule inclusion mode.
+    All,
+    /// Only include rules from UA and user level sheets.  Used to implement
+    /// `getDefaultComputedStyle`.
+    DefaultOnly,
+}
+
+#[cfg(feature = "gecko")]
+impl From<StyleRuleInclusion> for RuleInclusion {
+    fn from(value: StyleRuleInclusion) -> Self {
+        match value {
+            StyleRuleInclusion::All => RuleInclusion::All,
+            StyleRuleInclusion::DefaultOnly => RuleInclusion::DefaultOnly,
+        }
+    }
+}
+
 impl Stylist {
     /// Construct a new `Stylist`, using given `Device` and `QuirksMode`.
     /// If more members are added here, think about whether they should
     /// be reset in clear().
     #[inline]
     pub fn new(device: Device, quirks_mode: QuirksMode) -> Self {
         let mut stylist = Stylist {
             viewport_constraints: None,
@@ -619,23 +640,24 @@ impl Stylist {
     /// :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>
         where E: TElement,
     {
         let rule_node =
-            match self.lazy_pseudo_rules(guards, element, pseudo) {
+            match self.lazy_pseudo_rules(guards, element, pseudo, rule_inclusion) {
                 Some(rule_node) => rule_node,
                 None => return None
             };
 
         // Read the comment on `precomputed_values_for_pseudo` to see why it's
         // difficult to assert that display: contents nodes never arrive here
         // (tl;dr: It doesn't apply for replaced elements and such, but the
         // computed value is still "contents").
@@ -656,17 +678,18 @@ impl Stylist {
 
     /// 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,
                                 element: &E,
-                                pseudo: &PseudoElement)
+                                pseudo: &PseudoElement,
+                                rule_inclusion: RuleInclusion)
                                 -> Option<StrongRuleNode>
         where E: TElement
     {
         debug_assert!(pseudo.is_lazy());
         if self.pseudos_map.get(pseudo).is_none() {
             return None
         }
 
@@ -674,16 +697,22 @@ impl Stylist {
         // already, so we can directly apply the parent flags.
         let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| {
             if cfg!(feature = "servo") {
                 // Servo calls this function from the worker, but only for internal
                 // pseudos, so we should never generate selector flags here.
                 unreachable!("internal pseudo generated slow selector flags?");
             }
 
+            // No need to bother setting the selector flags when we're computing
+            // default styles.
+            if rule_inclusion == RuleInclusion::DefaultOnly {
+                return;
+            }
+
             // Gecko calls this from sequential mode, so we can directly apply
             // the flags.
             debug_assert!(thread_state::get() == thread_state::LAYOUT);
             let self_flags = flags.for_self();
             if !self_flags.is_empty() {
                 unsafe { element.set_selector_flags(self_flags); }
             }
             let parent_flags = flags.for_parent();
@@ -697,16 +726,17 @@ impl Stylist {
         let mut declarations = ApplicableDeclarationList::new();
         let mut matching_context =
             MatchingContext::new(MatchingMode::ForStatelessPseudoElement, None);
         self.push_applicable_declarations(element,
                                           Some(pseudo),
                                           None,
                                           None,
                                           AnimationRules(None, None),
+                                          rule_inclusion,
                                           &mut declarations,
                                           &mut matching_context,
                                           &mut set_selector_flags);
         if declarations.is_empty() {
             return None
         }
 
         let rule_node =
@@ -826,16 +856,17 @@ impl Stylist {
     /// which kind of rules have matched.
     pub fn push_applicable_declarations<E, V, F>(
                                         &self,
                                         element: &E,
                                         pseudo_element: Option<&PseudoElement>,
                                         style_attribute: Option<&Arc<Locked<PropertyDeclarationBlock>>>,
                                         smil_override: Option<&Arc<Locked<PropertyDeclarationBlock>>>,
                                         animation_rules: AnimationRules,
+                                        rule_inclusion: RuleInclusion,
                                         applicable_declarations: &mut V,
                                         context: &mut MatchingContext,
                                         flags_setter: &mut F)
         where E: TElement,
               V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock> + ::std::fmt::Debug,
               F: FnMut(&E, ElementSelectorFlags),
     {
         debug_assert!(!self.is_device_dirty);
@@ -861,26 +892,28 @@ impl Stylist {
             element.closest_non_native_anonymous_ancestor().unwrap()
         } else {
             *element
         };
 
         debug!("Determining if style is shareable: pseudo: {}",
                pseudo_element.is_some());
 
+        let only_default_rules = rule_inclusion == RuleInclusion::DefaultOnly;
+
         // Step 1: Normal user-agent rules.
         map.user_agent.get_all_matching_rules(element,
                                               &rule_hash_target,
                                               applicable_declarations,
                                               context,
                                               flags_setter,
                                               CascadeLevel::UANormal);
         debug!("UA normal: {:?}", context.relations);
 
-        if pseudo_element.is_none() {
+        if pseudo_element.is_none() && !only_default_rules {
             // Step 2: Presentational hints.
             let length_before_preshints = applicable_declarations.len();
             element.synthesize_presentational_hints_for_legacy_attributes(applicable_declarations);
             if applicable_declarations.len() != length_before_preshints {
                 if cfg!(debug_assertions) {
                     for declaration in &applicable_declarations[length_before_preshints..] {
                         assert_eq!(declaration.level, CascadeLevel::PresHints);
                     }
@@ -895,24 +928,30 @@ impl Stylist {
         // inaccurate, would be equivalent to something like:
         //
         //     element.matches_user_and_author_rules() ||
         //     (is_implemented_pseudo &&
         //      rule_hash_target.matches_user_and_author_rules())
         //
         // Which may be more what you would probably expect.
         if rule_hash_target.matches_user_and_author_rules() {
-            // Step 3: User and author normal rules.
+            // Step 3a: User normal rules.
             map.user.get_all_matching_rules(element,
                                             &rule_hash_target,
                                             applicable_declarations,
                                             context,
                                             flags_setter,
                                             CascadeLevel::UserNormal);
             debug!("user normal: {:?}", context.relations);
+        } else {
+            debug!("skipping user rules");
+        }
+
+        if rule_hash_target.matches_user_and_author_rules() && !only_default_rules {
+            // Step 3b: Author normal rules.
             map.author.get_all_matching_rules(element,
                                               &rule_hash_target,
                                               applicable_declarations,
                                               context,
                                               flags_setter,
                                               CascadeLevel::AuthorNormal);
             debug!("author normal: {:?}", context.relations);
 
@@ -951,24 +990,29 @@ impl Stylist {
             debug!("skipping non-agent rules");
         }
 
         //
         // Steps 7-10 correspond to !important rules, and are handled during
         // rule tree insertion.
         //
 
-        // Step 11: Transitions.
-        // The transitions sheet (CSS transitions that are tied to CSS markup)
-        if let Some(anim) = animation_rules.1 {
-            Push::push(
-                applicable_declarations,
-                ApplicableDeclarationBlock::from_declarations(anim, CascadeLevel::Transitions));
+        if !only_default_rules {
+            // Step 11: Transitions.
+            // The transitions sheet (CSS transitions that are tied to CSS markup)
+            if let Some(anim) = animation_rules.1 {
+                Push::push(
+                    applicable_declarations,
+                    ApplicableDeclarationBlock::from_declarations(anim, CascadeLevel::Transitions));
+            }
+            debug!("transition: {:?}", context.relations);
+        } else {
+            debug!("skipping transition rules");
         }
-        debug!("transition: {:?}", context.relations);
+
         debug!("push_applicable_declarations: shareable: {:?}", context.relations);
     }
 
     /// Return whether the device is dirty, that is, whether the screen size or
     /// media type have changed (for now).
     #[inline]
     pub fn is_device_dirty(&self) -> bool {
         self.is_device_dirty
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -36,16 +36,18 @@ bitflags! {
         /// Traverse only elements for animation restyles.
         const ANIMATION_ONLY = 0x02,
         /// Traverse without generating any change hints.
         const FOR_RECONSTRUCT = 0x04,
         /// Traverse triggered by CSS rule changes.
         /// Traverse and update all elements with CSS animations since
         /// @keyframes rules may have changed
         const FOR_CSS_RULE_CHANGES = 0x08,
+        /// Only include user agent style sheets when selector matching.
+        const FOR_DEFAULT_STYLES = 0x10,
     }
 }
 
 impl TraversalFlags {
     /// Returns true if the traversal is for animation-only restyles.
     pub fn for_animation_only(&self) -> bool {
         self.contains(ANIMATION_ONLY)
     }
@@ -59,16 +61,22 @@ impl TraversalFlags {
     pub fn for_reconstruct(&self) -> bool {
         self.contains(FOR_RECONSTRUCT)
     }
 
     /// Returns true if the traversal is triggered by CSS rule changes.
     pub fn for_css_rule_changes(&self) -> bool {
         self.contains(FOR_CSS_RULE_CHANGES)
     }
+
+    /// Returns true if the traversal is to compute the default computed
+    /// styles for an element.
+    pub fn for_default_styles(&self) -> bool {
+        self.contains(FOR_DEFAULT_STYLES)
+    }
 }
 
 /// This structure exists to enforce that callers invoke pre_traverse, and also
 /// to pass information from the pre-traversal into the primary traversal.
 pub struct PreTraverseToken {
     traverse: bool,
     unstyled_children_only: bool,
 }
@@ -532,20 +540,23 @@ fn resolve_style_internal<E, F>(context:
 
         // Compute our style.
         context.thread_local.begin_element(element, &data);
         element.match_and_cascade(context,
                                   &mut data,
                                   StyleSharingBehavior::Disallow);
         context.thread_local.end_element(element);
 
-        // Conservatively mark us as having dirty descendants, since there might
-        // be other unstyled siblings we miss when walking straight up the parent
-        // chain.
-        unsafe { element.note_descendants::<DirtyDescendants>() };
+        if !context.shared.traversal_flags.for_default_styles() {
+            // Conservatively mark us as having dirty descendants, since there might
+            // be other unstyled siblings we miss when walking straight up the parent
+            // 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() {
         display_none_root = Some(element);
     }
 
@@ -593,16 +604,57 @@ pub fn resolve_style<E, F, G, H>(context
             curr = match curr.parent_element() {
                 Some(parent) => parent,
                 None => break,
             };
         }
     }
 }
 
+/// Manually resolve default styles for the given Element, which are the styles
+/// only taking into account user agent and user cascade levels.  The resolved
+/// style is made available via a callback, and will be dropped by the time this
+/// function returns.
+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)
+{
+    // Save and clear out element data from the element and its ancestors.
+    let mut old_data: Vec<(E, Option<ElementData>)> = vec![];
+    {
+        let mut e = element;
+        loop {
+            old_data.push((e, set_data(e, None)));
+            match e.parent_element() {
+                Some(parent) => e = parent,
+                None => break,
+            }
+        }
+    }
+
+    // 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());
+
+    // 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.
 #[inline]
 #[allow(unsafe_code)]
 pub fn recalc_style_at<E, D>(traversal: &D,
                              traversal_data: &PerLevelTraversalData,
                              context: &mut StyleContext<E>,
                              element: E,
                              data: &mut ElementData)
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -66,16 +66,17 @@ use style::gecko_bindings::structs;
 use style::gecko_bindings::structs::{CSSPseudoElementType, CompositeOperation};
 use style::gecko_bindings::structs::{RawServoStyleRule, ServoStyleSheet};
 use style::gecko_bindings::structs::{SheetParsingMode, nsIAtom, nsCSSPropertyID};
 use style::gecko_bindings::structs::{nsCSSFontFaceRule, nsCSSCounterStyleRule};
 use style::gecko_bindings::structs::{nsRestyleHint, nsChangeHint};
 use style::gecko_bindings::structs::Loader;
 use style::gecko_bindings::structs::RawGeckoPresContextOwned;
 use style::gecko_bindings::structs::ServoElementSnapshotTable;
+use style::gecko_bindings::structs::StyleRuleInclusion;
 use style::gecko_bindings::structs::URLExtraData;
 use style::gecko_bindings::structs::nsCSSValueSharedList;
 use style::gecko_bindings::structs::nsCompatibility;
 use style::gecko_bindings::structs::nsresult;
 use style::gecko_bindings::sugar::ownership::{FFIArcHelpers, HasFFI, HasArcFFI, HasBoxFFI};
 use style::gecko_bindings::sugar::ownership::{HasSimpleFFI, Strong};
 use style::gecko_bindings::sugar::refptr::RefPtr;
 use style::gecko_properties::{self, style_structs};
@@ -95,21 +96,23 @@ use style::sequential;
 use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard, Locked};
 use style::string_cache::Atom;
 use style::style_adjuster::StyleAdjuster;
 use style::stylearc::Arc;
 use style::stylesheets::{CssRule, CssRules, CssRuleType, CssRulesHelpers};
 use style::stylesheets::{ImportRule, KeyframesRule, MediaRule, NamespaceRule, Origin};
 use style::stylesheets::{PageRule, Stylesheet, StyleRule, SupportsRule, DocumentRule};
 use style::stylesheets::StylesheetLoader as StyleStylesheetLoader;
+use style::stylist::RuleInclusion;
 use style::supports::parse_condition_or_declaration;
 use style::thread_state;
 use style::timer::Timer;
-use style::traversal::{ANIMATION_ONLY, FOR_CSS_RULE_CHANGES, FOR_RECONSTRUCT, UNSTYLED_CHILDREN_ONLY};
-use style::traversal::{resolve_style, DomTraversal, TraversalDriver, TraversalFlags};
+use style::traversal::{ANIMATION_ONLY, DomTraversal, FOR_CSS_RULE_CHANGES, FOR_RECONSTRUCT};
+use style::traversal::{FOR_DEFAULT_STYLES, TraversalDriver, TraversalFlags, UNSTYLED_CHILDREN_ONLY};
+use style::traversal::{resolve_style, resolve_default_style};
 use style::values::{CustomIdent, KeyframesName};
 use style_traits::ToCss;
 use super::stylesheet_loader::StylesheetLoader;
 
 /*
  * For Gecko->Servo function calls, we need to redeclare the same signature that was declared in
  * the C header in Gecko. In order to catch accidental mismatches, we run rust-bindgen against
  * those signatures as well, giving us a second declaration of all the Servo_* functions in this
@@ -1157,17 +1160,18 @@ pub extern "C" fn Servo_ResolvePseudoSty
         };
     }
 
     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, data.styles(), doc_data) {
+    match get_pseudo_style(&guard, element, &pseudo, RuleInclusion::All,
+                           data.styles(), doc_data) {
         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 => Strong::null(),
     }
 }
 
@@ -1189,16 +1193,17 @@ pub extern "C" fn Servo_HasAuthorSpecifi
                                                    &guards,
                                                    rule_type_mask,
                                                    author_colors_allowed)
 }
 
 fn get_pseudo_style(guard: &SharedRwLockReadGuard,
                     element: GeckoElement,
                     pseudo: &PseudoElement,
+                    rule_inclusion: RuleInclusion,
                     styles: &ElementStyles,
                     doc_data: &PerDocumentStyleData)
                     -> Option<Arc<ComputedValues>>
 {
     match pseudo.cascade_type() {
         PseudoElementCascadeType::Eager => styles.pseudos.get(&pseudo).map(|s| s.values().clone()),
         PseudoElementCascadeType::Precomputed => unreachable!("No anonymous boxes"),
         PseudoElementCascadeType::Lazy => {
@@ -1208,16 +1213,17 @@ fn get_pseudo_style(guard: &SharedRwLock
             } else {
                 styles.primary.values()
             };
             let guards = StylesheetGuards::same(guard);
             let metrics = get_metrics_provider_for_product();
             d.stylist.lazily_compute_pseudo_element_style(&guards,
                                                           &element,
                                                           &pseudo,
+                                                          rule_inclusion,
                                                           base,
                                                           &metrics)
                      .map(|s| s.values().clone())
         },
     }
 }
 
 #[no_mangle]
@@ -2220,55 +2226,75 @@ pub extern "C" fn Servo_ResolveStyle(ele
     }
 
     data.styles().primary.values().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)
      -> ServoComputedValuesStrong
 {
     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 rule_inclusion = RuleInclusion::from(rule_inclusion);
     let finish = |styles: &ElementStyles| -> Arc<ComputedValues> {
         PseudoElement::from_pseudo_type(pseudo_type).and_then(|ref pseudo| {
-            get_pseudo_style(&guard, element, pseudo, styles, doc_data)
+            get_pseudo_style(&guard, element, pseudo, rule_inclusion, styles, doc_data)
         }).unwrap_or_else(|| styles.primary.values().clone())
     };
 
     // In the common case we already have the style. Check that before setting
-    // up all the computation machinery.
-    let mut result = element.mutate_data()
-                            .and_then(|d| d.get_styles().map(&finish));
-    if result.is_some() {
-        return result.unwrap().into_strong();
+    // 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)) {
+            return result.into_strong();
+        }
     }
 
+    let traversal_flags = match rule_inclusion {
+        RuleInclusion::All => TraversalFlags::empty(),
+        RuleInclusion::DefaultOnly => FOR_DEFAULT_STYLES,
+    };
+
     // We don't have the style ready. Go ahead and compute it as necessary.
+    let mut result = None;
     let data = doc_data.borrow();
     let shared = create_shared_context(&global_style_data,
                                        &guard,
                                        &data,
-                                       TraversalFlags::empty(),
+                                       traversal_flags,
                                        unsafe { &*snapshots });
     let mut tlc = ThreadLocalStyleContext::new(&shared);
     let mut context = StyleContext {
         shared: &shared,
         thread_local: &mut tlc,
     };
     let ensure = |el: GeckoElement| { unsafe { el.ensure_data(); } };
-    let clear = |el: GeckoElement| el.clear_data();
-    resolve_style(&mut context, element, &ensure, &clear,
-                  |styles| result = Some(finish(styles)));
+
+    match rule_inclusion {
+        RuleInclusion::All => {
+            let clear = |el: GeckoElement| el.clear_data();
+            resolve_style(&mut context, element, &ensure, &clear,
+                          |styles| result = Some(finish(styles)));
+        }
+        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)));
+        }
+    }
 
     result.unwrap().into_strong()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_GetComputedKeyframeValues(keyframes: RawGeckoKeyframeListBorrowed,
                                                   style: ServoComputedValuesBorrowed,
                                                   parent_style: ServoComputedValuesBorrowedOrNull,