Bug 1505565 - Split up push_applicable_declarations. r=heycam
authorEmilio Cobos Álvarez <emilio@crisal.io>
Thu, 08 Nov 2018 23:07:40 +0000
changeset 445242 81d1d93099bf6ebdf65657aade2e860c2eebc6c5
parent 445241 ed759d2a9effc6d48c5130d4a199ca618cb998a3
child 445243 63eb34f9b1712fe86b052727765db30fa068d6b7
push id35012
push userbtara@mozilla.com
push dateFri, 09 Nov 2018 05:26:19 +0000
treeherdermozilla-central@63eb34f9b171 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs1505565
milestone65.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
Bug 1505565 - Split up push_applicable_declarations. r=heycam Introduce RuleCollector, which contains all the state we need during the cascade, and allows to reuse a bit of code. Differential Revision: https://phabricator.services.mozilla.com/D11233
servo/components/style/lib.rs
servo/components/style/rule_collector.rs
servo/components/style/stylist.rs
--- a/servo/components/style/lib.rs
+++ b/servo/components/style/lib.rs
@@ -138,16 +138,17 @@ pub mod invalidation;
 #[allow(missing_docs)] // TODO.
 pub mod logical_geometry;
 pub mod matching;
 #[macro_use]
 pub mod media_queries;
 pub mod parallel;
 pub mod parser;
 pub mod rule_cache;
+pub mod rule_collector;
 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 str;
 pub mod style_adjuster;
new file mode 100644
--- /dev/null
+++ b/servo/components/style/rule_collector.rs
@@ -0,0 +1,391 @@
+/* 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/. */
+
+//! Collects a series of applicable rules for a given element.
+
+use applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList};
+use dom::{TElement, TShadowRoot};
+use properties::{AnimationRules, PropertyDeclarationBlock};
+use rule_tree::{CascadeLevel, ShadowCascadeOrder};
+use selector_map::SelectorMap;
+use selector_parser::PseudoElement;
+use selectors::matching::{ElementSelectorFlags, MatchingContext};
+use servo_arc::ArcBorrow;
+use shared_lock::Locked;
+use smallvec::SmallVec;
+use stylesheets::Origin;
+use stylist::{AuthorStylesEnabled, Rule, RuleInclusion, Stylist};
+
+/// An object that we use with all the intermediate state needed for the
+/// cascade.
+///
+/// This is done basically to be able to organize the cascade in smaller
+/// functions, and be able to reason about it easily.
+pub struct RuleCollector<'a, 'b: 'a, E, F: 'a>
+where
+    E: TElement,
+{
+    element: E,
+    rule_hash_target: E,
+    stylist: &'a Stylist,
+    pseudo_element: Option<&'a PseudoElement>,
+    style_attribute: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>,
+    smil_override: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>,
+    animation_rules: AnimationRules,
+    rule_inclusion: RuleInclusion,
+    rules: &'a mut ApplicableDeclarationList,
+    context: &'a mut MatchingContext<'b, E::Impl>,
+    flags_setter: &'a mut F,
+    shadow_cascade_order: ShadowCascadeOrder,
+    matches_user_and_author_rules: bool,
+    matches_document_author_rules: bool,
+}
+
+impl<'a, 'b: 'a, E, F: 'a> RuleCollector<'a, 'b, E, F>
+where
+    E: TElement,
+    F: FnMut(&E, ElementSelectorFlags),
+{
+    /// Trivially construct a new collector.
+    pub fn new(
+        stylist: &'a Stylist,
+        element: E,
+        pseudo_element: Option<&'a PseudoElement>,
+        style_attribute: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>,
+        smil_override: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>,
+        animation_rules: AnimationRules,
+        rule_inclusion: RuleInclusion,
+        rules: &'a mut ApplicableDeclarationList,
+        context: &'a mut MatchingContext<'b, E::Impl>,
+        flags_setter: &'a mut F,
+    ) -> Self {
+        let rule_hash_target = element.rule_hash_target();
+        let matches_user_and_author_rules = rule_hash_target.matches_user_and_author_rules();
+
+        // Gecko definitely has pseudo-elements with style attributes, like
+        // ::-moz-color-swatch.
+        debug_assert!(
+            cfg!(feature = "gecko") || style_attribute.is_none() || pseudo_element.is_none(),
+            "Style attributes do not apply to pseudo-elements"
+        );
+        debug_assert!(pseudo_element.map_or(true, |p| !p.is_precomputed()));
+
+        Self {
+            element,
+            rule_hash_target,
+            stylist,
+            pseudo_element,
+            style_attribute,
+            smil_override,
+            animation_rules,
+            rule_inclusion,
+            context,
+            flags_setter,
+            rules,
+            matches_user_and_author_rules,
+            shadow_cascade_order: 0,
+            matches_document_author_rules: matches_user_and_author_rules,
+        }
+    }
+
+    fn collect_stylist_rules(&mut self, origin: Origin) {
+        let cascade_level = match origin {
+            Origin::UserAgent => CascadeLevel::UANormal,
+            Origin::User => CascadeLevel::UserNormal,
+            Origin::Author => CascadeLevel::SameTreeAuthorNormal,
+        };
+
+        let cascade_data = self.stylist.cascade_data().borrow_for_origin(origin);
+        let map = match cascade_data.normal_rules(self.pseudo_element) {
+            Some(m) => m,
+            None => return,
+        };
+
+        map.get_all_matching_rules(
+            self.element,
+            self.rule_hash_target,
+            self.rules,
+            self.context,
+            self.flags_setter,
+            cascade_level,
+            0,
+        );
+    }
+
+    fn collect_user_agent_rules(&mut self) {
+        self.collect_stylist_rules(Origin::UserAgent);
+    }
+
+    fn collect_user_rules(&mut self) {
+        if !self.matches_user_and_author_rules {
+            return;
+        }
+
+        self.collect_stylist_rules(Origin::User);
+    }
+
+    /// Presentational hints.
+    ///
+    /// These go before author rules, but after user rules, see:
+    /// https://drafts.csswg.org/css-cascade/#preshint
+    fn collect_presentational_hints(&mut self) {
+        if self.pseudo_element.is_some() {
+            return;
+        }
+
+        let length_before_preshints = self.rules.len();
+        self.element
+            .synthesize_presentational_hints_for_legacy_attributes(
+                self.context.visited_handling(),
+                self.rules,
+            );
+        if cfg!(debug_assertions) {
+            if self.rules.len() != length_before_preshints {
+                for declaration in &self.rules[length_before_preshints..] {
+                    assert_eq!(declaration.level(), CascadeLevel::PresHints);
+                }
+            }
+        }
+    }
+
+    fn collect_rules_in_shadow_tree(
+        &mut self,
+        shadow_host: E,
+        map: &SelectorMap<Rule>,
+        cascade_level: CascadeLevel,
+    ) {
+        debug_assert!(shadow_host.shadow_root().is_some());
+        let element = self.element;
+        let rule_hash_target = self.rule_hash_target;
+        let rules = &mut self.rules;
+        let flags_setter = &mut self.flags_setter;
+        let shadow_cascade_order = self.shadow_cascade_order;
+        self.context.with_shadow_host(Some(shadow_host), |context| {
+            map.get_all_matching_rules(
+                element,
+                rule_hash_target,
+                rules,
+                context,
+                flags_setter,
+                cascade_level,
+                shadow_cascade_order,
+            );
+        });
+        self.shadow_cascade_order += 1;
+    }
+
+    /// Collects the rules for the ::slotted pseudo-element.
+    fn collect_slotted_rules(&mut self) {
+        let mut slots = SmallVec::<[_; 3]>::new();
+        let mut current = self.rule_hash_target.assigned_slot();
+        while let Some(slot) = current {
+            debug_assert!(
+                self.matches_user_and_author_rules,
+                "We should not slot NAC anywhere"
+            );
+            slots.push(slot);
+            current = slot.assigned_slot();
+        }
+
+        // Match slotted rules in reverse order, so that the outer slotted rules
+        // come before the inner rules (and thus have less priority).
+        for slot in slots.iter().rev() {
+            let shadow = slot.containing_shadow().unwrap();
+            let data = match shadow.style_data() {
+                Some(d) => d,
+                None => continue,
+            };
+            let slotted_rules = match data.slotted_rules(self.pseudo_element) {
+                Some(r) => r,
+                None => continue,
+            };
+            self.collect_rules_in_shadow_tree(
+                shadow.host(),
+                slotted_rules,
+                CascadeLevel::InnerShadowNormal,
+            );
+        }
+    }
+
+    fn collect_normal_rules_from_containing_shadow_tree(&mut self) {
+        if !self.matches_user_and_author_rules {
+            return;
+        }
+
+        let mut current_containing_shadow = self.rule_hash_target.containing_shadow();
+        while let Some(containing_shadow) = current_containing_shadow {
+            let cascade_data = containing_shadow.style_data();
+            let host = containing_shadow.host();
+            if let Some(map) = cascade_data.and_then(|data| data.normal_rules(self.pseudo_element))
+            {
+                self.collect_rules_in_shadow_tree(host, map, CascadeLevel::SameTreeAuthorNormal);
+            }
+            let host_is_svg_use_element =
+                host.is_svg_element() && host.local_name() == &*local_name!("use");
+            if !host_is_svg_use_element {
+                self.matches_document_author_rules = false;
+                break;
+            }
+
+            debug_assert!(
+                cascade_data.is_none(),
+                "We allow no stylesheets in <svg:use> subtrees"
+            );
+
+            // NOTE(emilio): Hack so <svg:use> matches the rules of the
+            // enclosing tree.
+            //
+            // This is not a problem for invalidation and that kind of stuff
+            // because they still don't match rules based on elements
+            // outside of the shadow tree, and because the <svg:use>
+            // subtrees are immutable and recreated each time the source
+            // tree changes.
+            //
+            // We historically allow cross-document <svg:use> to have these
+            // rules applied, but I think that's not great. Gecko is the
+            // only engine supporting that.
+            //
+            // See https://github.com/w3c/svgwg/issues/504 for the relevant
+            // spec discussion.
+            current_containing_shadow = host.containing_shadow();
+            self.matches_document_author_rules = current_containing_shadow.is_none();
+        }
+    }
+
+    /// Collects the rules for the :host pseudo-class.
+    fn collect_host_rules(&mut self) {
+        let shadow = match self.rule_hash_target.shadow_root() {
+            Some(s) => s,
+            None => return,
+        };
+
+        debug_assert!(
+            self.matches_user_and_author_rules,
+            "NAC should not be a shadow host"
+        );
+
+        let style_data = match shadow.style_data() {
+            Some(d) => d,
+            None => return,
+        };
+
+        let host_rules = match style_data.host_rules(self.pseudo_element) {
+            Some(rules) => rules,
+            None => return,
+        };
+
+        let rule_hash_target = self.rule_hash_target;
+        self.collect_rules_in_shadow_tree(
+            rule_hash_target,
+            host_rules,
+            CascadeLevel::InnerShadowNormal,
+        );
+    }
+
+    fn collect_document_author_rules(&mut self) {
+        if !self.matches_document_author_rules {
+            return;
+        }
+
+        self.collect_stylist_rules(Origin::Author);
+    }
+
+    fn collect_xbl_rules(&mut self) {
+        let element = self.element;
+        let cut_xbl_binding_inheritance =
+            element.each_xbl_cascade_data(|cascade_data, quirks_mode| {
+                let map = match cascade_data.normal_rules(self.pseudo_element) {
+                    Some(m) => m,
+                    None => return,
+                };
+
+                // NOTE(emilio): This is needed because the XBL stylist may
+                // think it has a different quirks mode than the document.
+                let mut matching_context = MatchingContext::new(
+                    self.context.matching_mode(),
+                    self.context.bloom_filter,
+                    self.context.nth_index_cache.as_mut().map(|s| &mut **s),
+                    quirks_mode,
+                );
+                matching_context.pseudo_element_matching_fn =
+                    self.context.pseudo_element_matching_fn;
+
+                // SameTreeAuthorNormal instead of InnerShadowNormal to
+                // preserve behavior, though that's kinda fishy...
+                map.get_all_matching_rules(
+                    self.element,
+                    self.rule_hash_target,
+                    self.rules,
+                    &mut matching_context,
+                    self.flags_setter,
+                    CascadeLevel::SameTreeAuthorNormal,
+                    self.shadow_cascade_order,
+                );
+            });
+
+        self.matches_document_author_rules &= !cut_xbl_binding_inheritance;
+    }
+
+    fn collect_style_attribute_and_animation_rules(&mut self) {
+        if let Some(sa) = self.style_attribute {
+            self.rules
+                .push(ApplicableDeclarationBlock::from_declarations(
+                    sa.clone_arc(),
+                    CascadeLevel::StyleAttributeNormal,
+                ));
+        }
+
+        if let Some(so) = self.smil_override {
+            self.rules
+                .push(ApplicableDeclarationBlock::from_declarations(
+                    so.clone_arc(),
+                    CascadeLevel::SMILOverride,
+                ));
+        }
+
+        // The animations sheet (CSS animations, script-generated
+        // animations, and CSS transitions that are no longer tied to CSS
+        // markup).
+        if let Some(anim) = self.animation_rules.0.take() {
+            self.rules
+                .push(ApplicableDeclarationBlock::from_declarations(
+                    anim,
+                    CascadeLevel::Animations,
+                ));
+        }
+
+        // The transitions sheet (CSS transitions that are tied to CSS
+        // markup).
+        if let Some(anim) = self.animation_rules.1.take() {
+            self.rules
+                .push(ApplicableDeclarationBlock::from_declarations(
+                    anim,
+                    CascadeLevel::Transitions,
+                ));
+        }
+    }
+
+    /// Collects all the rules, leaving the result in `self.rules`.
+    ///
+    /// Note that `!important` rules are handled during rule tree insertion.
+    pub fn collect_all(mut self) {
+        self.collect_user_agent_rules();
+        self.collect_user_rules();
+        if self.rule_inclusion == RuleInclusion::DefaultOnly {
+            return;
+        }
+        self.collect_presentational_hints();
+        // FIXME(emilio): Should the author styles enabled stuff avoid the
+        // presentational hints from getting pushed? See bug 1505770.
+        if self.stylist.author_styles_enabled() == AuthorStylesEnabled::No {
+            return;
+        }
+        self.collect_host_rules();
+        self.collect_slotted_rules();
+        self.collect_normal_rules_from_containing_shadow_tree();
+        self.collect_xbl_rules();
+        self.collect_document_author_rules();
+        self.collect_style_attribute_and_animation_rules();
+    }
+}
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -18,30 +18,30 @@ use invalidation::media_queries::{Effect
 use malloc_size_of::MallocUnconditionalShallowSizeOf;
 #[cfg(feature = "gecko")]
 use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps};
 use media_queries::Device;
 use properties::{self, CascadeMode, ComputedValues};
 use properties::{AnimationRules, PropertyDeclarationBlock};
 use rule_cache::{RuleCache, RuleCacheConditions};
 use rule_tree::{CascadeLevel, RuleTree, ShadowCascadeOrder, StrongRuleNode, StyleSource};
+use rule_collector::RuleCollector;
 use selector_map::{PrecomputedHashMap, PrecomputedHashSet, SelectorMap, SelectorMapEntry};
 use selector_parser::{PerPseudoElementMap, PseudoElement, SelectorImpl, SnapshotMap};
 use selectors::attr::{CaseSensitivity, NamespaceConstraint};
 use selectors::bloom::BloomFilter;
 use selectors::matching::VisitedHandlingMode;
 use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext, MatchingMode};
 use selectors::parser::{AncestorHashes, Combinator, Component, Selector};
 use selectors::parser::{SelectorIter, Visit};
 use selectors::visitor::SelectorVisitor;
 use selectors::NthIndexCache;
 use servo_arc::{Arc, ArcBorrow};
 use shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards};
 use smallbitvec::SmallBitVec;
-use smallvec::SmallVec;
 use std::ops;
 use std::sync::Mutex;
 use style_traits::viewport::ViewportConstraints;
 use stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind};
 use stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher};
 use stylesheets::keyframes_rule::KeyframesAnimation;
 use stylesheets::viewport_rule::{self, MaybeNew, ViewportRule};
 use stylesheets::StyleRule;
@@ -175,20 +175,21 @@ struct UserAgentCascadeData {
 impl UserAgentCascadeData {
     #[cfg(feature = "gecko")]
     fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) {
         self.cascade_data.add_size_of(ops, sizes);
         sizes.mPrecomputedPseudos += self.precomputed_pseudo_element_decls.size_of(ops);
     }
 }
 
-/// All the computed information for a stylesheet.
+/// All the computed information for all the stylesheets that apply to the
+/// document.
 #[derive(Default)]
 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
-struct DocumentCascadeData {
+pub struct DocumentCascadeData {
     #[cfg_attr(
         feature = "servo",
         ignore_malloc_size_of = "Arc, owned by UserAgentCascadeDataCache"
     )]
     user_agent: Arc<UserAgentCascadeData>,
     user: CascadeData,
     author: CascadeData,
     per_origin: PerOrigin<()>,
@@ -205,17 +206,19 @@ impl<'a> Iterator for DocumentCascadeDat
 
     fn next(&mut self) -> Option<Self::Item> {
         let (_, origin) = self.iter.next()?;
         Some((self.cascade_data.borrow_for_origin(origin), origin))
     }
 }
 
 impl DocumentCascadeData {
-    fn borrow_for_origin(&self, origin: Origin) -> &CascadeData {
+    /// Borrows the cascade data for a given origin.
+    #[inline]
+    pub fn borrow_for_origin(&self, origin: Origin) -> &CascadeData {
         match origin {
             Origin::UserAgent => &self.user_agent.cascade_data,
             Origin::Author => &self.author,
             Origin::User => &self.user,
         }
     }
 
     fn iter_origins(&self) -> DocumentCascadeDataIter {
@@ -407,20 +410,26 @@ impl Stylist {
             stylesheets: StylistStylesheetSet::new(),
             cascade_data: Default::default(),
             author_styles_enabled: AuthorStylesEnabled::Yes,
             rule_tree: RuleTree::new(),
             num_rebuilds: 0,
         }
     }
 
-    /// Returns the cascade data for the author level.
+    /// Returns the document cascade data.
     #[inline]
-    pub fn author_cascade_data(&self) -> &CascadeData {
-        &self.cascade_data.author
+    pub fn cascade_data(&self) -> &DocumentCascadeData {
+        &self.cascade_data
+    }
+
+    /// Returns whether author styles are enabled or not.
+    #[inline]
+    pub fn author_styles_enabled(&self) -> AuthorStylesEnabled {
+        self.author_styles_enabled
     }
 
     /// Iterate through all the cascade datas from the document.
     #[inline]
     pub fn iter_origins(&self) -> DocumentCascadeDataIter {
         self.cascade_data.iter_origins()
     }
 
@@ -1093,314 +1102,43 @@ impl Stylist {
         if self.quirks_mode == quirks_mode {
             return;
         }
         self.quirks_mode = quirks_mode;
         self.force_stylesheet_origins_dirty(OriginSet::all());
     }
 
     /// Returns the applicable CSS declarations for the given element.
-    ///
-    /// This corresponds to `ElementRuleCollector` in WebKit, and should push to
-    /// elements in the list in the order defined by:
-    ///
-    /// https://drafts.csswg.org/css-cascade/#cascade-origin
     pub fn push_applicable_declarations<E, F>(
         &self,
         element: E,
         pseudo_element: Option<&PseudoElement>,
         style_attribute: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>,
         smil_override: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>,
         animation_rules: AnimationRules,
         rule_inclusion: RuleInclusion,
         applicable_declarations: &mut ApplicableDeclarationList,
         context: &mut MatchingContext<E::Impl>,
         flags_setter: &mut F,
     ) where
         E: TElement,
         F: FnMut(&E, ElementSelectorFlags),
     {
-        // Gecko definitely has pseudo-elements with style attributes, like
-        // ::-moz-color-swatch.
-        debug_assert!(
-            cfg!(feature = "gecko") || style_attribute.is_none() || pseudo_element.is_none(),
-            "Style attributes do not apply to pseudo-elements"
-        );
-        debug_assert!(pseudo_element.map_or(true, |p| !p.is_precomputed()));
-
-        let rule_hash_target = element.rule_hash_target();
-
-        let matches_user_and_author_rules = rule_hash_target.matches_user_and_author_rules();
-
-        // Normal user-agent rules.
-        if let Some(map) = self
-            .cascade_data
-            .user_agent
-            .cascade_data
-            .normal_rules(pseudo_element)
-        {
-            map.get_all_matching_rules(
-                element,
-                rule_hash_target,
-                applicable_declarations,
-                context,
-                flags_setter,
-                CascadeLevel::UANormal,
-                0,
-            );
-        }
-
-        // NB: the following condition, although it may look somewhat
-        // 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 matches_user_and_author_rules {
-            // User normal rules.
-            if let Some(map) = self.cascade_data.user.normal_rules(pseudo_element) {
-                map.get_all_matching_rules(
-                    element,
-                    rule_hash_target,
-                    applicable_declarations,
-                    context,
-                    flags_setter,
-                    CascadeLevel::UserNormal,
-                    0,
-                );
-            }
-        }
-
-        if rule_inclusion == RuleInclusion::DefaultOnly {
-            return;
-        }
-
-        if pseudo_element.is_none() {
-            // Presentational hints.
-            //
-            // These go before author rules, but after user rules, see:
-            // https://drafts.csswg.org/css-cascade/#preshint
-            let length_before_preshints = applicable_declarations.len();
-            element.synthesize_presentational_hints_for_legacy_attributes(
-                context.visited_handling(),
-                applicable_declarations,
-            );
-            if cfg!(debug_assertions) {
-                if applicable_declarations.len() != length_before_preshints {
-                    for declaration in &applicable_declarations[length_before_preshints..] {
-                        assert_eq!(declaration.level(), CascadeLevel::PresHints);
-                    }
-                }
-            }
-        }
-
-        if self.author_styles_enabled == AuthorStylesEnabled::No {
-            return;
-        }
-
-        let mut match_document_author_rules = matches_user_and_author_rules;
-        let mut shadow_cascade_order = 0;
-
-        // XBL / Shadow DOM rules, which are author rules too.
-        if let Some(shadow) = rule_hash_target.shadow_root() {
-            debug_assert!(
-                matches_user_and_author_rules,
-                "NAC should not be a shadow host"
-            );
-            if let Some(map) = shadow
-                .style_data()
-                .and_then(|data| data.host_rules(pseudo_element))
-            {
-                context.with_shadow_host(Some(rule_hash_target), |context| {
-                    map.get_all_matching_rules(
-                        element,
-                        rule_hash_target,
-                        applicable_declarations,
-                        context,
-                        flags_setter,
-                        CascadeLevel::InnerShadowNormal,
-                        shadow_cascade_order,
-                    );
-                });
-                shadow_cascade_order += 1;
-            }
-        }
-
-        // Match slotted rules in reverse order, so that the outer slotted
-        // rules come before the inner rules (and thus have less priority).
-        let mut slots = SmallVec::<[_; 3]>::new();
-        let mut current = rule_hash_target.assigned_slot();
-        while let Some(slot) = current {
-            debug_assert!(
-                matches_user_and_author_rules,
-                "We should not slot NAC anywhere"
-            );
-            slots.push(slot);
-            current = slot.assigned_slot();
-        }
-
-        for slot in slots.iter().rev() {
-            let shadow = slot.containing_shadow().unwrap();
-            if let Some(map) = shadow
-                .style_data()
-                .and_then(|data| data.slotted_rules(pseudo_element))
-            {
-                context.with_shadow_host(Some(shadow.host()), |context| {
-                    map.get_all_matching_rules(
-                        element,
-                        rule_hash_target,
-                        applicable_declarations,
-                        context,
-                        flags_setter,
-                        CascadeLevel::InnerShadowNormal,
-                        shadow_cascade_order,
-                    );
-                });
-                shadow_cascade_order += 1;
-            }
-        }
-
-        if matches_user_and_author_rules {
-            let mut current_containing_shadow = rule_hash_target.containing_shadow();
-            while let Some(containing_shadow) = current_containing_shadow {
-                let cascade_data = containing_shadow.style_data();
-                let host = containing_shadow.host();
-                if let Some(map) = cascade_data.and_then(|data| data.normal_rules(pseudo_element)) {
-                    context.with_shadow_host(Some(host), |context| {
-                        map.get_all_matching_rules(
-                            element,
-                            rule_hash_target,
-                            applicable_declarations,
-                            context,
-                            flags_setter,
-                            CascadeLevel::SameTreeAuthorNormal,
-                            shadow_cascade_order,
-                        );
-                    });
-                    shadow_cascade_order += 1;
-                }
-
-                let host_is_svg_use_element =
-                    host.is_svg_element() && host.local_name() == &*local_name!("use");
-
-                if !host_is_svg_use_element {
-                    match_document_author_rules = false;
-                    break;
-                }
-
-                debug_assert!(
-                    cascade_data.is_none(),
-                    "We allow no stylesheets in <svg:use> subtrees"
-                );
-
-                // NOTE(emilio): Hack so <svg:use> matches the rules of the
-                // enclosing tree.
-                //
-                // This is not a problem for invalidation and that kind of stuff
-                // because they still don't match rules based on elements
-                // outside of the shadow tree, and because the <svg:use>
-                // subtrees are immutable and recreated each time the source
-                // tree changes.
-                //
-                // We historically allow cross-document <svg:use> to have these
-                // rules applied, but I think that's not great. Gecko is the
-                // only engine supporting that.
-                //
-                // See https://github.com/w3c/svgwg/issues/504 for the relevant
-                // spec discussion.
-                current_containing_shadow = host.containing_shadow();
-                match_document_author_rules = current_containing_shadow.is_none();
-            }
-        }
-
-        let cut_xbl_binding_inheritance =
-            element.each_xbl_cascade_data(|cascade_data, quirks_mode| {
-                if let Some(map) = cascade_data.normal_rules(pseudo_element) {
-                    // NOTE(emilio): This is needed because the XBL stylist may
-                    // think it has a different quirks mode than the document.
-                    let mut matching_context = MatchingContext::new(
-                        context.matching_mode(),
-                        context.bloom_filter,
-                        context.nth_index_cache.as_mut().map(|s| &mut **s),
-                        quirks_mode,
-                    );
-                    matching_context.pseudo_element_matching_fn =
-                        context.pseudo_element_matching_fn;
-
-                    // SameTreeAuthorNormal instead of InnerShadowNormal to
-                    // preserve behavior, though that's kinda fishy...
-                    map.get_all_matching_rules(
-                        element,
-                        rule_hash_target,
-                        applicable_declarations,
-                        &mut matching_context,
-                        flags_setter,
-                        CascadeLevel::SameTreeAuthorNormal,
-                        shadow_cascade_order,
-                    );
-                }
-            });
-
-        match_document_author_rules &= !cut_xbl_binding_inheritance;
-
-        if match_document_author_rules {
-            // Author normal rules.
-            if let Some(map) = self.cascade_data.author.normal_rules(pseudo_element) {
-                map.get_all_matching_rules(
-                    element,
-                    rule_hash_target,
-                    applicable_declarations,
-                    context,
-                    flags_setter,
-                    CascadeLevel::SameTreeAuthorNormal,
-                    shadow_cascade_order,
-                );
-            }
-        }
-
-        // Style attribute ("Normal override declarations").
-        if let Some(sa) = style_attribute {
-            applicable_declarations.push(ApplicableDeclarationBlock::from_declarations(
-                sa.clone_arc(),
-                CascadeLevel::StyleAttributeNormal,
-            ));
-        }
-
-        // Declarations from SVG SMIL animation elements.
-        if let Some(so) = smil_override {
-            applicable_declarations.push(ApplicableDeclarationBlock::from_declarations(
-                so.clone_arc(),
-                CascadeLevel::SMILOverride,
-            ));
-        }
-
-        // The animations sheet (CSS animations, script-generated
-        // animations, and CSS transitions that are no longer tied to CSS
-        // markup).
-        if let Some(anim) = animation_rules.0 {
-            applicable_declarations.push(ApplicableDeclarationBlock::from_declarations(
-                anim.clone(),
-                CascadeLevel::Animations,
-            ));
-        }
-
-        //
-        // !important rules are handled during rule tree insertion.
-        //
-
-        // The transitions sheet (CSS transitions that are tied to CSS
-        // markup).
-        if let Some(anim) = animation_rules.1 {
-            applicable_declarations.push(ApplicableDeclarationBlock::from_declarations(
-                anim.clone(),
-                CascadeLevel::Transitions,
-            ));
-        }
+        RuleCollector::new(
+            self,
+            element,
+            pseudo_element,
+            style_attribute,
+            smil_override,
+            animation_rules,
+            rule_inclusion,
+            applicable_declarations,
+            context,
+            flags_setter,
+        ).collect_all();
     }
 
     /// Given an id, returns whether there might be any rules for that id in any
     /// of our rule maps.
     #[inline]
     pub fn may_have_rules_for_id<E>(&self, id: &WeakAtom, element: E) -> bool
     where
         E: TElement,
@@ -2095,28 +1833,31 @@ impl CascadeData {
 
     /// Returns whether the given attribute might appear in an attribute
     /// selector of some rule.
     #[inline]
     pub fn might_have_attribute_dependency(&self, local_name: &LocalName) -> bool {
         self.attribute_dependencies.contains(local_name)
     }
 
+    /// Returns the normal rule map for a given pseudo-element.
     #[inline]
-    fn normal_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
+    pub fn normal_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
         self.normal_rules.rules(pseudo)
     }
 
+    /// Returns the host pseudo rule map for a given pseudo-element.
     #[inline]
-    fn host_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
+    pub fn host_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
         self.host_rules.as_ref().and_then(|d| d.rules(pseudo))
     }
 
+    /// Returns the slotted rule map for a given pseudo-element.
     #[inline]
-    fn slotted_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
+    pub fn slotted_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
         self.slotted_rules.as_ref().and_then(|d| d.rules(pseudo))
     }
 
     /// Collects all the applicable media query results into `results`.
     ///
     /// This duplicates part of the logic in `add_stylesheet`, which is
     /// a bit unfortunate.
     ///