servo: Merge #18087 - style: Cleanup a bit Stylist and SelectorMap (from emilio:stylist-clear-cleanup); r=heycam
authorEmilio Cobos Álvarez <emilio@crisal.io>
Wed, 16 Aug 2017 02:56:13 -0500
changeset 374993 17ace082e5f5f1082f49080ccd9df1a65599ef7b
parent 374992 16c102a1cc3964c2a17cb853df653e26010ad5b1
child 374994 f25c91cca4c3816ddfcd6a3dafba5f77a264d5a7
push id48885
push userservo-vcs-sync@mozilla.com
push dateWed, 16 Aug 2017 09:22:54 +0000
treeherderautoland@17ace082e5f5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
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 #18087 - style: Cleanup a bit Stylist and SelectorMap (from emilio:stylist-clear-cleanup); r=heycam Bug: 1390255 Reviewed-by: heycam Source-Repo: https://github.com/servo/servo Source-Revision: b300800b2c450f325c0308e9dcd7ec0113498e37
servo/components/style/gecko/data.rs
servo/components/style/gecko/generated/bindings.rs
servo/components/style/invalidation/element/invalidation_map.rs
servo/components/style/selector_map.rs
servo/components/style/stylesheet_set.rs
servo/components/style/stylesheets/mod.rs
servo/components/style/stylesheets/origin.rs
servo/components/style/stylist.rs
servo/ports/geckolib/glue.rs
servo/tests/unit/style/stylist.rs
--- a/servo/components/style/gecko/data.rs
+++ b/servo/components/style/gecko/data.rs
@@ -12,17 +12,17 @@ use gecko_bindings::structs::RawGeckoPre
 use gecko_bindings::structs::nsIDocument;
 use gecko_bindings::sugar::ownership::{HasArcFFI, HasBoxFFI, HasFFI, HasSimpleFFI};
 use invalidation::media_queries::{MediaListKey, ToMediaListKey};
 use media_queries::{Device, MediaList};
 use properties::ComputedValues;
 use servo_arc::Arc;
 use shared_lock::{Locked, StylesheetGuards, SharedRwLockReadGuard};
 use stylesheet_set::StylesheetSet;
-use stylesheets::{Origin, PerOrigin, StylesheetContents, StylesheetInDocument};
+use stylesheets::{PerOrigin, StylesheetContents, StylesheetInDocument};
 use stylist::{ExtraStyleData, Stylist};
 
 /// Little wrapper to a Gecko style sheet.
 #[derive(PartialEq, Eq, Debug)]
 pub struct GeckoStyleSheet(*const ServoStyleSheet);
 
 impl ToMediaListKey for ::gecko::data::GeckoStyleSheet {
     fn to_media_list_key(&self) -> MediaListKey {
@@ -159,50 +159,39 @@ impl PerDocumentStyleDataImpl {
         where E: TElement,
     {
         if !self.stylesheets.has_changed() {
             return;
         }
 
         let author_style_disabled = self.stylesheets.author_style_disabled();
 
-        let iter = self.stylesheets.flush(document_element);
+        let (iter, dirty_origins) = self.stylesheets.flush(document_element);
         self.stylist.rebuild(
             iter,
             &StylesheetGuards::same(guard),
             /* ua_sheets = */ None,
-            /* stylesheets_changed = */ true,
             author_style_disabled,
             &mut self.extra_style_data,
+            dirty_origins,
         );
     }
 
     /// Returns whether private browsing is enabled.
     pub fn is_private_browsing_enabled(&self) -> bool {
         let doc =
             self.stylist.device().pres_context().mDocument.raw::<nsIDocument>();
         unsafe { bindings::Gecko_IsPrivateBrowsingEnabled(doc) }
     }
 
     /// Get the default computed values for this document.
     pub fn default_computed_values(&self) -> &Arc<ComputedValues> {
         self.stylist.device().default_computed_values_arc()
     }
 
-    /// Clear the stylist.  This will be a no-op if the stylist is
-    /// already cleared; the stylist handles that.
-    pub fn clear_stylist(&mut self) {
-        self.stylist.clear();
-    }
-
-    /// Clear the stylist's data for the specified origin.
-    pub fn clear_stylist_origin(&mut self, origin: &Origin) {
-        self.stylist.clear_origin(origin);
-    }
-
     /// Returns whether visited links are enabled.
     fn visited_links_enabled(&self) -> bool {
         unsafe { bindings::Gecko_AreVisitedLinksEnabled() }
     }
     /// Returns whether visited styles are enabled.
     pub fn visited_styles_enabled(&self) -> bool {
         self.visited_links_enabled() && !self.is_private_browsing_enabled()
     }
--- a/servo/components/style/gecko/generated/bindings.rs
+++ b/servo/components/style/gecko/generated/bindings.rs
@@ -1983,19 +1983,16 @@ extern "C" {
                                           RawServoStyleSheetContentsBorrowed)
      -> OriginFlags;
 }
 extern "C" {
     pub fn Servo_StyleSet_Init(pres_context: RawGeckoPresContextOwned)
      -> *mut RawServoStyleSet;
 }
 extern "C" {
-    pub fn Servo_StyleSet_Clear(set: RawServoStyleSetBorrowed);
-}
-extern "C" {
     pub fn Servo_StyleSet_RebuildCachedData(set: RawServoStyleSetBorrowed);
 }
 extern "C" {
     pub fn Servo_StyleSet_MediumFeaturesChanged(set: RawServoStyleSetBorrowed,
                                                 viewport_units_used:
                                                     *mut bool) -> u8;
 }
 extern "C" {
--- a/servo/components/style/invalidation/element/invalidation_map.rs
+++ b/servo/components/style/invalidation/element/invalidation_map.rs
@@ -190,18 +190,18 @@ impl InvalidationMap {
     {
         self.collect_invalidations_for(selector, quirks_mode)
     }
 
     /// Clears this map, leaving it empty.
     pub fn clear(&mut self) {
         self.class_to_selector.clear();
         self.id_to_selector.clear();
-        self.state_affecting_selectors = SelectorMap::new();
-        self.other_attribute_affecting_selectors = SelectorMap::new();
+        self.state_affecting_selectors.clear();
+        self.other_attribute_affecting_selectors.clear();
         self.has_id_attribute_selectors = false;
         self.has_class_attribute_selectors = false;
     }
 
     fn collect_invalidations_for(
         &mut self,
         selector: &Selector<SelectorImpl>,
         quirks_mode: QuirksMode)
--- a/servo/components/style/selector_map.rs
+++ b/servo/components/style/selector_map.rs
@@ -10,17 +10,16 @@ use applicable_declarations::ApplicableD
 use context::QuirksMode;
 use dom::TElement;
 use pdqsort::sort_by;
 use precomputed_hash::PrecomputedHash;
 use rule_tree::CascadeLevel;
 use selector_parser::SelectorImpl;
 use selectors::matching::{matches_selector, MatchingContext, ElementSelectorFlags};
 use selectors::parser::{Component, Combinator, SelectorIter};
-use selectors::parser::LocalName as LocalNameSelector;
 use smallvec::{SmallVec, VecLike};
 use std::collections::{HashMap, HashSet};
 use std::collections::hash_map;
 use std::hash::{BuildHasherDefault, Hash, Hasher};
 use stylist::Rule;
 
 /// A hasher implementation that doesn't hash anything, because it expects its
 /// input to be a suitable u32 hash.
@@ -97,38 +96,47 @@ pub trait SelectorMapEntry : Sized + Clo
 pub struct SelectorMap<T> {
     /// A hash from an ID to rules which contain that ID selector.
     pub id_hash: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[T; 1]>>,
     /// A hash from a class name to rules which contain that class selector.
     pub class_hash: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[T; 1]>>,
     /// A hash from local name to rules which contain that local name selector.
     pub local_name_hash: PrecomputedHashMap<LocalName, SmallVec<[T; 1]>>,
     /// Rules that don't have ID, class, or element selectors.
-    pub other: Vec<T>,
+    pub other: SmallVec<[T; 1]>,
     /// The number of entries in this map.
     pub count: usize,
 }
 
 #[inline]
 fn sort_by_key<T, F: Fn(&T) -> K, K: Ord>(v: &mut [T], f: F) {
     sort_by(v, |a, b| f(a).cmp(&f(b)))
 }
 
 impl<T> SelectorMap<T> {
     /// Trivially constructs an empty `SelectorMap`.
     pub fn new() -> Self {
         SelectorMap {
             id_hash: MaybeCaseInsensitiveHashMap::new(),
             class_hash: MaybeCaseInsensitiveHashMap::new(),
             local_name_hash: HashMap::default(),
-            other: Vec::new(),
+            other: SmallVec::new(),
             count: 0,
         }
     }
 
+    /// Clears the hashmap retaining storage.
+    pub fn clear(&mut self) {
+        self.id_hash.clear();
+        self.class_hash.clear();
+        self.local_name_hash.clear();
+        self.other.clear();
+        self.count = 0;
+    }
+
     /// Returns whether there are any entries in the map.
     pub fn is_empty(&self) -> bool {
         self.count == 0
     }
 
     /// Returns the number of entries.
     pub fn len(&self) -> usize {
         self.count
@@ -226,47 +234,52 @@ impl SelectorMap<Rule> {
     }
 }
 
 impl<T: SelectorMapEntry> SelectorMap<T> {
     /// Inserts into the correct hash, trying id, class, and localname.
     pub fn insert(&mut self, entry: T, quirks_mode: QuirksMode) {
         self.count += 1;
 
-        if let Some(id_name) = get_id_name(entry.selector()) {
-            self.id_hash.entry(id_name, quirks_mode).or_insert_with(SmallVec::new).push(entry);
-            return;
-        }
-
-        if let Some(class_name) = get_class_name(entry.selector()) {
-            self.class_hash.entry(class_name, quirks_mode).or_insert_with(SmallVec::new).push(entry);
-            return;
-        }
+        let vector = match find_bucket(entry.selector()) {
+            Bucket::ID(id) => {
+                self.id_hash
+                    .entry(id.clone(), quirks_mode)
+                    .or_insert_with(SmallVec::new)
+            }
+            Bucket::Class(class) => {
+                self.class_hash
+                    .entry(class.clone(), quirks_mode)
+                    .or_insert_with(SmallVec::new)
+            }
+            Bucket::LocalName { name, lower_name } => {
+                // If the local name in the selector isn't lowercase, insert it
+                // into the rule hash twice. This means that, during lookup, we
+                // can always find the rules based on the local name of the
+                // element, regardless of whether it's an html element in an
+                // html document (in which case we match against lower_name) or
+                // not (in which case we match against name).
+                //
+                // In the case of a non-html-element-in-html-document with a
+                // lowercase localname and a non-lowercase selector, the
+                // rulehash lookup may produce superfluous selectors, but the
+                // subsequent selector matching work will filter them out.
+                if name != lower_name {
+                    find_push(&mut self.local_name_hash, lower_name.clone(), entry.clone());
+                }
+                self.local_name_hash
+                    .entry(name.clone())
+                    .or_insert_with(SmallVec::new)
+            }
+            Bucket::Universal => {
+                &mut self.other
+            }
+        };
 
-        if let Some(LocalNameSelector { name, lower_name }) = get_local_name(entry.selector()) {
-            // If the local name in the selector isn't lowercase, insert it into
-            // the rule hash twice. This means that, during lookup, we can always
-            // find the rules based on the local name of the element, regardless
-            // of whether it's an html element in an html document (in which case
-            // we match against lower_name) or not (in which case we match against
-            // name).
-            //
-            // In the case of a non-html-element-in-html-document with a
-            // lowercase localname and a non-lowercase selector, the rulehash
-            // lookup may produce superfluous selectors, but the subsequent
-            // selector matching work will filter them out.
-            if name != lower_name {
-                find_push(&mut self.local_name_hash, lower_name, entry.clone());
-            }
-            find_push(&mut self.local_name_hash, name, entry);
-
-            return;
-        }
-
-        self.other.push(entry);
+        vector.push(entry);
     }
 
     /// Looks up entries by id, class, local name, and other (in order).
     ///
     /// Each entry is passed to the callback, which returns true to continue
     /// iterating entries, or false to terminate the lookup.
     ///
     /// Returns false if the callback ever returns false.
@@ -370,84 +383,75 @@ impl<T: SelectorMapEntry> SelectorMap<T>
                 }
             }
         }
 
         true
     }
 }
 
-/// Searches a compound selector from left to right. If the compound selector
-/// is a pseudo-element, it's ignored.
-///
-/// The first non-None value returned from |f| is returned.
+enum Bucket<'a> {
+    ID(&'a Atom),
+    Class(&'a Atom),
+    LocalName { name: &'a LocalName, lower_name: &'a LocalName, },
+    Universal,
+}
+
+fn specific_bucket_for<'a>(
+    component: &'a Component<SelectorImpl>
+) -> Bucket<'a> {
+    match *component {
+        Component::ID(ref id) => Bucket::ID(id),
+        Component::Class(ref class) => Bucket::Class(class),
+        Component::LocalName(ref selector) => {
+            Bucket::LocalName {
+                name: &selector.name,
+                lower_name: &selector.lower_name,
+            }
+        }
+        _ => Bucket::Universal
+    }
+}
+
+/// Searches a compound selector from left to right, and returns the appropriate
+/// bucket for it.
 #[inline(always)]
-fn find_from_left<F, R>(
-    mut iter: SelectorIter<SelectorImpl>,
-    mut f: F
-) -> Option<R>
-where
-    F: FnMut(&Component<SelectorImpl>) -> Option<R>,
-{
-    for ss in &mut iter {
-        if let Some(r) = f(ss) {
-            return Some(r)
+fn find_bucket<'a>(mut iter: SelectorIter<'a, SelectorImpl>) -> Bucket<'a> {
+    let mut current_bucket = Bucket::Universal;
+
+    loop {
+        // We basically want to find the most specific bucket,
+        // where:
+        //
+        //   id > class > local name > universal.
+        //
+        for ss in &mut iter {
+            let new_bucket = specific_bucket_for(ss);
+            match new_bucket {
+                Bucket::ID(..) => return new_bucket,
+                Bucket::Class(..) => {
+                    current_bucket = new_bucket;
+                }
+                Bucket::LocalName { .. } => {
+                    if matches!(current_bucket, Bucket::Universal) {
+                        current_bucket = new_bucket;
+                    }
+                }
+                Bucket::Universal => {},
+            }
         }
-    }
 
-    // Effectively, pseudo-elements are ignored, given only state pseudo-classes
-    // may appear before them.
-    if iter.next_sequence() == Some(Combinator::PseudoElement) {
-        for ss in &mut iter {
-            if let Some(r) = f(ss) {
-                return Some(r)
-            }
+        // Effectively, pseudo-elements are ignored, given only state
+        // pseudo-classes may appear before them.
+        if iter.next_sequence() != Some(Combinator::PseudoElement) {
+            break;
         }
     }
 
-    None
-}
-
-/// Retrieve the first ID name in the selector, or None otherwise.
-#[inline(always)]
-pub fn get_id_name(iter: SelectorIter<SelectorImpl>)
-                   -> Option<Atom> {
-    find_from_left(iter, |ss| {
-        if let Component::ID(ref id) = *ss {
-            return Some(id.clone());
-        }
-        None
-    })
-}
-
-/// Retrieve the FIRST class name in the selector, or None otherwise.
-#[inline(always)]
-pub fn get_class_name(iter: SelectorIter<SelectorImpl>)
-                      -> Option<Atom> {
-    find_from_left(iter, |ss| {
-        if let Component::Class(ref class) = *ss {
-            return Some(class.clone());
-        }
-        None
-    })
-}
-
-/// Retrieve the name if it is a type selector, or None otherwise.
-#[inline(always)]
-pub fn get_local_name(iter: SelectorIter<SelectorImpl>)
-                      -> Option<LocalNameSelector<SelectorImpl>> {
-    find_from_left(iter, |ss| {
-        if let Component::LocalName(ref n) = *ss {
-            return Some(LocalNameSelector {
-                name: n.name.clone(),
-                lower_name: n.lower_name.clone(),
-            })
-        }
-        None
-    })
+    return current_bucket
 }
 
 #[inline]
 fn find_push<Str: Eq + Hash, V, VL>(map: &mut PrecomputedHashMap<Str, VL>,
                                     key: Str,
                                     value: V)
     where VL: VecLike<V> + Default
 {
--- a/servo/components/style/stylesheet_set.rs
+++ b/servo/components/style/stylesheet_set.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! A centralized set of stylesheets for a document.
 
 use dom::TElement;
 use invalidation::stylesheets::StylesheetInvalidationSet;
 use shared_lock::SharedRwLockReadGuard;
 use std::slice;
-use stylesheets::{Origin, PerOrigin, StylesheetInDocument};
+use stylesheets::{OriginSet, PerOrigin, StylesheetInDocument};
 use stylist::Stylist;
 
 /// Entry for a StylesheetSet. We don't bother creating a constructor, because
 /// there's no sensible defaults for the member variables.
 pub struct StylesheetSetEntry<S>
 where
     S: StylesheetInDocument + PartialEq + 'static,
 {
@@ -164,56 +164,49 @@ where
             .any(|(d, _)| d.dirty)
     }
 
     /// Flush the current set, unmarking it as dirty, and returns an iterator
     /// over the new stylesheet list.
     pub fn flush<E>(
         &mut self,
         document_element: Option<E>,
-    ) -> StylesheetIterator<S>
+    ) -> (StylesheetIterator<S>, OriginSet)
     where
         E: TElement,
     {
         debug!("StylesheetSet::flush");
         debug_assert!(self.has_changed());
 
-        for (data, _) in self.invalidation_data.iter_mut_origins() {
+        let mut origins = OriginSet::empty();
+        for (data, origin) in self.invalidation_data.iter_mut_origins() {
             if data.dirty {
                 data.invalidations.flush(document_element);
                 data.dirty = false;
+                origins |= origin;
             }
         }
 
-        self.iter()
+        (self.iter(), origins)
     }
 
     /// Returns an iterator over the current list of stylesheets.
     pub fn iter(&self) -> StylesheetIterator<S> {
         StylesheetIterator(self.entries.iter())
     }
 
-    /// Mark the stylesheets as dirty, because something external may have
-    /// invalidated it.
-    ///
-    /// FIXME(emilio): Make this more granular.
-    pub fn force_dirty(&mut self) {
-        for (data, _) in self.invalidation_data.iter_mut_origins() {
+    /// Mark the stylesheets for the specified origin as dirty, because
+    /// something external may have invalidated it.
+    pub fn force_dirty(&mut self, origins: OriginSet) {
+        for origin in origins.iter() {
+            let data = self.invalidation_data.borrow_mut_for_origin(&origin);
             data.invalidations.invalidate_fully();
             data.dirty = true;
         }
     }
-
-    /// Mark the stylesheets for the specified origin as dirty, because
-    /// something external may have invalidated it.
-    pub fn force_dirty_origin(&mut self, origin: &Origin) {
-        let data = self.invalidation_data.borrow_mut_for_origin(origin);
-        data.invalidations.invalidate_fully();
-        data.dirty = true;
-    }
 }
 
 struct InvalidationData {
     /// The stylesheet invalidations for this origin that we still haven't
     /// processed.
     invalidations: StylesheetInvalidationSet,
 
     /// Whether the sheets for this origin in the `StylesheetSet`'s entry list
--- a/servo/components/style/stylesheets/mod.rs
+++ b/servo/components/style/stylesheets/mod.rs
@@ -39,17 +39,17 @@ pub use self::font_feature_values_rule::
 pub use self::import_rule::ImportRule;
 pub use self::keyframes_rule::KeyframesRule;
 pub use self::loader::StylesheetLoader;
 pub use self::media_rule::MediaRule;
 pub use self::memory::{MallocSizeOf, MallocSizeOfFn, MallocSizeOfWithGuard};
 #[cfg(feature = "gecko")]
 pub use self::memory::{MallocSizeOfWithRepeats, SizeOfState};
 pub use self::namespace_rule::NamespaceRule;
-pub use self::origin::{Origin, OriginSet, PerOrigin, PerOriginClear};
+pub use self::origin::{Origin, OriginSet, PerOrigin};
 pub use self::page_rule::PageRule;
 pub use self::rule_parser::{State, TopLevelRuleParser};
 pub use self::rule_list::{CssRules, CssRulesHelpers};
 pub use self::rules_iterator::{AllRules, EffectiveRules, NestedRuleIterationCondition, RulesIterator};
 pub use self::stylesheet::{Namespaces, Stylesheet, StylesheetContents, StylesheetInDocument, UserAgentStylesheets};
 pub use self::style_rule::StyleRule;
 pub use self::supports_rule::SupportsRule;
 pub use self::viewport_rule::ViewportRule;
--- a/servo/components/style/stylesheets/origin.rs
+++ b/servo/components/style/stylesheets/origin.rs
@@ -163,30 +163,16 @@ impl<T> PerOrigin<T> {
         PerOriginIterMut {
             data: self,
             cur: 0,
             _marker: PhantomData,
         }
     }
 }
 
-/// An object that can be cleared.
-pub trait PerOriginClear {
-    /// Clears the object.
-    fn clear(&mut self);
-}
-
-impl<T> PerOriginClear for PerOrigin<T> where T: PerOriginClear {
-    fn clear(&mut self) {
-        self.user_agent.clear();
-        self.user.clear();
-        self.author.clear();
-    }
-}
-
 /// Iterator over `PerOrigin<T>`, from highest level (author) to lowest
 /// (user agent).
 ///
 /// We rely on this specific order for correctly looking up @font-face,
 /// @counter-style and @keyframes rules.
 pub struct PerOriginIter<'a, T: 'a> {
     data: &'a PerOrigin<T>,
     cur: i8,
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -35,17 +35,17 @@ use selectors::visitor::SelectorVisitor;
 use servo_arc::{Arc, ArcBorrow};
 use shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards};
 use smallvec::VecLike;
 use std::fmt::Debug;
 use style_traits::viewport::ViewportConstraints;
 #[cfg(feature = "gecko")]
 use stylesheets::{CounterStyleRule, FontFaceRule};
 use stylesheets::{CssRule, StyleRule};
-use stylesheets::{StylesheetInDocument, Origin, OriginSet, PerOrigin, PerOriginClear};
+use stylesheets::{StylesheetInDocument, Origin, OriginSet, PerOrigin};
 use stylesheets::UserAgentStylesheets;
 use stylesheets::keyframes_rule::KeyframesAnimation;
 use stylesheets::viewport_rule::{self, MaybeNew, ViewportRule};
 use thread_state;
 
 pub use ::fnv::FnvHashMap;
 
 /// This structure holds all the selectors and device characteristics
@@ -177,107 +177,38 @@ impl Stylist {
     pub fn each_invalidation_map<F>(&self, mut f: F)
         where F: FnMut(&InvalidationMap)
     {
         for (data, _) in self.cascade_data.iter_origins() {
             f(&data.invalidation_map)
         }
     }
 
-    /// Clear the stylist's state, effectively resetting it to more or less
-    /// the state Stylist::new creates.
-    ///
-    /// We preserve the state of the following members:
-    ///   device: Someone might have set this on us.
-    ///   quirks_mode: Again, someone might have set this on us.
-    ///   num_rebuilds: clear() followed by rebuild() should just increment this
-    ///   rule_tree: So we can re-use rule nodes across rebuilds.
-    ///
-    /// We don't just use struct update syntax with Stylist::new(self.device)
-    /// beause for some of our members we can clear them instead of creating new
-    /// objects.  This does cause unfortunate code duplication with
-    /// Stylist::new.
-    pub fn clear(&mut self) {
-        self.cascade_data.clear();
-        self.precomputed_pseudo_element_decls.clear();
-        self.viewport_constraints = None;
-
-        // XXX(heycam) Why do this, if we are preserving the Device?
-        self.is_device_dirty = true;
-    }
-
-    /// Clear the stylist's state for the specified origin.
-    pub fn clear_origin(&mut self, origin: &Origin) {
-        self.cascade_data.borrow_mut_for_origin(origin).clear();
-
-        if *origin == Origin::UserAgent {
-            // We only collect these declarations from UA sheets.
-            self.precomputed_pseudo_element_decls.clear();
-        }
-
-        // The stored `ViewportConstraints` contains data from rules across
-        // all origins.
-        self.viewport_constraints = None;
-
-        // XXX(heycam) Why do this, if we are preserving the Device?
-        self.is_device_dirty = true;
-    }
-
-    /// Returns whether any origin's `CascadeData` has been cleared.
-    fn any_origin_cleared(&self) -> bool {
-        self.cascade_data
-            .iter_origins()
-            .any(|(d, _)| d.is_cleared)
-    }
-
     /// Rebuild the stylist for the given document stylesheets, and optionally
     /// with a set of user agent stylesheets.
     ///
     /// This method resets all the style data each time the stylesheets change
     /// (which is indicated by the `stylesheets_changed` parameter), or the
     /// device is dirty, which means we need to re-evaluate media queries.
     pub fn rebuild<'a, I, S>(
         &mut self,
         doc_stylesheets: I,
         guards: &StylesheetGuards,
         ua_stylesheets: Option<&UserAgentStylesheets>,
-        stylesheets_changed: bool,
         author_style_disabled: bool,
-        extra_data: &mut PerOrigin<ExtraStyleData>
+        extra_data: &mut PerOrigin<ExtraStyleData>,
+        mut origins_to_rebuild: OriginSet,
     ) -> bool
     where
         I: Iterator<Item = &'a S> + Clone,
         S: StylesheetInDocument + ToMediaListKey + 'static,
     {
-        debug_assert!(!self.any_origin_cleared() || self.is_device_dirty);
-
-        // Determine the origins that actually need updating.
-        //
-        // XXX(heycam): What is the relationship between `stylesheets_changed`
-        // and the `is_cleared` fields on each origin's `CascadeData`?  Can
-        // we avoid passing in `stylesheets_changed`?
-        let mut to_update: PerOrigin<bool> = Default::default();
-
-        // If we're provided with a list of UA and user style sheets, then
-        // we must update those cascade levels. (Servo does this, but Gecko
-        // just includes the UA and User sheets in `doc_stylesheets`.)
-        if ua_stylesheets.is_some() {
-            to_update.user_agent = true;
-            to_update.user = true;
-        }
-
-        for (data, origin) in self.cascade_data.iter_mut_origins() {
-            if data.is_cleared {
-                data.is_cleared = false;
-                *to_update.borrow_mut_for_origin(&origin) = true;
-            }
-        }
-
-        if !(self.is_device_dirty || stylesheets_changed) {
-            return false;
+        debug_assert!(!origins_to_rebuild.is_empty() || self.is_device_dirty);
+        if self.is_device_dirty {
+            origins_to_rebuild = OriginSet::all();
         }
 
         self.num_rebuilds += 1;
 
         // Update viewport_constraints regardless of which origins'
         // `CascadeData` we're updating.
         self.viewport_constraints = None;
         if viewport_rule::enabled() {
@@ -302,23 +233,23 @@ impl Stylist {
                                                &cascaded_rule,
                                                self.quirks_mode);
 
             if let Some(ref constraints) = self.viewport_constraints {
                 self.device.account_for_viewport_rule(constraints);
             }
         }
 
-        // XXX(heycam): We should probably just move the `extra_data` to be
-        // stored on the `Stylist` instead of Gecko's `PerDocumentStyleData`.
-        // That would let us clear it inside `clear()` and `clear_origin()`.
-        for (update, origin) in to_update.iter_origins() {
-            if *update {
-                extra_data.borrow_mut_for_origin(&origin).clear();
-            }
+        for origin in origins_to_rebuild.iter() {
+            extra_data.borrow_mut_for_origin(&origin).clear();
+            self.cascade_data.borrow_mut_for_origin(&origin).clear();
+        }
+
+        if origins_to_rebuild.contains(Origin::UserAgent.into()) {
+            self.precomputed_pseudo_element_decls.clear();
         }
 
         if let Some(ua_stylesheets) = ua_stylesheets {
             for stylesheet in &ua_stylesheets.user_or_user_agent_stylesheets {
                 debug_assert!(matches!(
                     stylesheet.contents(guards.ua_or_user).origin,
                     Origin::UserAgent | Origin::User));
                 self.add_stylesheet(stylesheet, guards.ua_or_user, extra_data);
@@ -332,21 +263,20 @@ impl Stylist {
                 self.add_stylesheet(&ua_stylesheets.quirks_mode_stylesheet,
                                     guards.ua_or_user, extra_data);
             }
         }
 
         // Only add stylesheets for origins we are updating, and only add
         // Author level sheets if author style is not disabled.
         let sheets_to_add = doc_stylesheets.filter(|s| {
-            match s.contents(guards.author).origin {
-                Origin::UserAgent => to_update.user_agent,
-                Origin::Author => to_update.author && !author_style_disabled,
-                Origin::User => to_update.user,
-            }
+            let sheet_origin = s.contents(guards.author).origin;
+
+            origins_to_rebuild.contains(sheet_origin.into()) &&
+                (!matches!(sheet_origin, Origin::Author) || !author_style_disabled)
         });
 
         for stylesheet in sheets_to_add {
             self.add_stylesheet(stylesheet, guards.author, extra_data);
         }
 
         self.is_device_dirty = false;
         true
@@ -362,26 +292,29 @@ impl Stylist {
         stylesheets_changed: bool,
         author_style_disabled: bool,
         extra_data: &mut PerOrigin<ExtraStyleData>
     ) -> bool
     where
         I: Iterator<Item = &'a S> + Clone,
         S: StylesheetInDocument + ToMediaListKey + 'static,
     {
-        debug_assert!(!self.any_origin_cleared() || self.is_device_dirty);
-
         // We have to do a dirtiness check before clearing, because if
         // we're not actually dirty we need to no-op here.
         if !(self.is_device_dirty || stylesheets_changed) {
             return false;
         }
-        self.clear();
-        self.rebuild(doc_stylesheets, guards, ua_stylesheets, stylesheets_changed,
-                     author_style_disabled, extra_data)
+        self.rebuild(
+            doc_stylesheets,
+            guards,
+            ua_stylesheets,
+            author_style_disabled,
+            extra_data,
+            OriginSet::all(),
+        )
     }
 
     fn add_stylesheet<S>(
         &mut self,
         stylesheet: &S,
         guard: &SharedRwLockReadGuard,
         _extra_data: &mut PerOrigin<ExtraStyleData>
     )
@@ -524,44 +457,34 @@ impl Stylist {
         }
     }
 
     /// Returns whether the given attribute might appear in an attribute
     /// selector of some rule in the stylist.
     pub fn might_have_attribute_dependency(&self,
                                            local_name: &LocalName)
                                            -> bool {
-        if self.any_origin_cleared() || self.is_device_dirty {
-            // We can't tell what attributes are in our style rules until
-            // we rebuild.
-            true
-        } else if *local_name == local_name!("style") {
+        if *local_name == local_name!("style") {
             self.cascade_data
                 .iter_origins()
                 .any(|(d, _)| d.style_attribute_dependency)
         } else {
             self.cascade_data
                 .iter_origins()
                 .any(|(d, _)| {
                     d.attribute_dependencies
                         .might_contain_hash(local_name.get_hash())
                 })
         }
     }
 
     /// Returns whether the given ElementState bit might be relied upon by a
     /// selector of some rule in the stylist.
     pub fn might_have_state_dependency(&self, state: ElementState) -> bool {
-        if self.any_origin_cleared() || self.is_device_dirty {
-            // We can't tell what states our style rules rely on until
-            // we rebuild.
-            true
-        } else {
-            self.has_state_dependency(state)
-        }
+        self.has_state_dependency(state)
     }
 
     /// Returns whether the given ElementState bit is relied upon by a selector
     /// of some rule in the stylist.
     pub fn has_state_dependency(&self, state: ElementState) -> bool {
         self.cascade_data
             .iter_origins()
             .any(|(d, _)| d.state_dependencies.intersects(state))
@@ -1435,24 +1358,27 @@ pub struct ExtraStyleData {
 #[cfg(feature = "gecko")]
 impl ExtraStyleData {
     /// Add the given @font-face rule.
     fn add_font_face(&mut self, rule: &Arc<Locked<FontFaceRule>>) {
         self.font_faces.push(rule.clone());
     }
 
     /// Add the given @counter-style rule.
-    fn add_counter_style(&mut self, guard: &SharedRwLockReadGuard,
-                         rule: &Arc<Locked<CounterStyleRule>>) {
+    fn add_counter_style(
+        &mut self,
+        guard: &SharedRwLockReadGuard,
+        rule: &Arc<Locked<CounterStyleRule>>,
+    ) {
         let name = rule.read_with(guard).mName.raw::<nsIAtom>().into();
         self.counter_styles.insert(name, rule.clone());
     }
 }
 
-impl PerOriginClear for ExtraStyleData {
+impl ExtraStyleData {
     fn clear(&mut self) {
         #[cfg(feature = "gecko")]
         {
             self.font_faces.clear();
             self.counter_styles.clear();
         }
     }
 }
@@ -1684,21 +1610,16 @@ struct CascadeData {
     /// style rule appears in a stylesheet, needed to sort them by source order.
     rules_source_order: u32,
 
     /// The total number of selectors.
     num_selectors: usize,
 
     /// The total number of declarations.
     num_declarations: usize,
-
-    /// If true, the `CascadeData` is in a cleared state (e.g. just-constructed,
-    /// or had `clear()` called on it with no following `rebuild()` on the
-    /// `Stylist`).
-    is_cleared: bool,
 }
 
 impl CascadeData {
     fn new() -> Self {
         Self {
             element_map: SelectorMap::new(),
             pseudos_map: PerPseudoElementMap::default(),
             animations: Default::default(),
@@ -1707,53 +1628,45 @@ impl CascadeData {
             style_attribute_dependency: false,
             state_dependencies: ElementState::empty(),
             mapped_ids: NonCountingBloomFilter::new(),
             selectors_for_cache_revalidation: SelectorMap::new(),
             effective_media_query_results: EffectiveMediaQueryResults::new(),
             rules_source_order: 0,
             num_selectors: 0,
             num_declarations: 0,
-            is_cleared: true,
         }
     }
 
     #[inline]
     fn borrow_for_pseudo(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> {
         match pseudo {
             Some(pseudo) => self.pseudos_map.get(&pseudo.canonical()),
             None => Some(&self.element_map),
         }
     }
 
     fn has_rules_for_pseudo(&self, pseudo: &PseudoElement) -> bool {
         self.pseudos_map.get(pseudo).is_some()
     }
-}
 
-impl PerOriginClear for CascadeData {
     fn clear(&mut self) {
-        if self.is_cleared {
-            return;
-        }
-
-        self.element_map = SelectorMap::new();
-        self.pseudos_map = Default::default();
-        self.animations = Default::default();
+        self.element_map.clear();
+        self.pseudos_map.clear();
+        self.animations.clear();
         self.invalidation_map.clear();
         self.attribute_dependencies.clear();
         self.style_attribute_dependency = false;
         self.state_dependencies = ElementState::empty();
         self.mapped_ids.clear();
-        self.selectors_for_cache_revalidation = SelectorMap::new();
+        self.selectors_for_cache_revalidation.clear();
         self.effective_media_query_results.clear();
         self.rules_source_order = 0;
         self.num_selectors = 0;
         self.num_declarations = 0;
-        self.is_cleared = true;
     }
 }
 
 impl Default for CascadeData {
     fn default() -> Self {
         CascadeData::new()
     }
 }
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -112,17 +112,17 @@ use style::rule_tree::StyleSource;
 use style::selector_parser::PseudoElementCascadeType;
 use style::sequential;
 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, MallocSizeOfWithGuard};
 use style::stylesheets::{MediaRule, NamespaceRule, Origin, OriginSet, PageRule, SizeOfState, StyleRule};
-use style::stylesheets::{StylesheetContents, StylesheetInDocument, SupportsRule};
+use style::stylesheets::{StylesheetContents, SupportsRule};
 use style::stylesheets::StylesheetLoader as StyleStylesheetLoader;
 use style::stylesheets::keyframes_rule::{Keyframe, KeyframeSelector, KeyframesStepValue};
 use style::stylesheets::supports_rule::parse_condition_or_declaration;
 use style::stylist::RuleInclusion;
 use style::thread_state;
 use style::timer::Timer;
 use style::traversal::{DomTraversal, TraversalDriver};
 use style::traversal::resolve_style;
@@ -886,19 +886,17 @@ pub extern "C" fn Servo_StyleSet_AppendS
     raw_data: RawServoStyleSetBorrowed,
     sheet: *const ServoStyleSheet,
 ) {
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
     let mut data = &mut *data;
     let guard = global_style_data.shared_lock.read();
     let sheet = unsafe { GeckoStyleSheet::new(sheet) };
-    let origin = sheet.contents(&guard).origin;
     data.stylesheets.append_stylesheet(&data.stylist, sheet, &guard);
-    data.clear_stylist_origin(&origin);
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSet_MediumFeaturesChanged(
     raw_data: RawServoStyleSetBorrowed,
     viewport_units_used: *mut bool,
 ) -> u8 {
     let global_style_data = &*GLOBAL_STYLE_DATA;
@@ -937,55 +935,49 @@ pub extern "C" fn Servo_StyleSet_Prepend
     raw_data: RawServoStyleSetBorrowed,
     sheet: *const ServoStyleSheet,
 ) {
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
     let mut data = &mut *data;
     let guard = global_style_data.shared_lock.read();
     let sheet = unsafe { GeckoStyleSheet::new(sheet) };
-    let origin = sheet.contents(&guard).origin;
     data.stylesheets.prepend_stylesheet(&data.stylist, sheet, &guard);
-    data.clear_stylist_origin(&origin);
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSet_InsertStyleSheetBefore(
     raw_data: RawServoStyleSetBorrowed,
     sheet: *const ServoStyleSheet,
     before_sheet: *const ServoStyleSheet
 ) {
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
     let mut data = &mut *data;
     let guard = global_style_data.shared_lock.read();
     let sheet = unsafe { GeckoStyleSheet::new(sheet) };
-    let origin = sheet.contents(&guard).origin;
     data.stylesheets.insert_stylesheet_before(
         &data.stylist,
         sheet,
         unsafe { GeckoStyleSheet::new(before_sheet) },
         &guard,
     );
-    data.clear_stylist_origin(&origin);
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSet_RemoveStyleSheet(
     raw_data: RawServoStyleSetBorrowed,
     sheet: *const ServoStyleSheet
 ) {
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
     let mut data = &mut *data;
     let guard = global_style_data.shared_lock.read();
     let sheet = unsafe { GeckoStyleSheet::new(sheet) };
-    let origin = sheet.contents(&guard).origin;
     data.stylesheets.remove_stylesheet(&data.stylist, sheet, &guard);
-    data.clear_stylist_origin(&origin);
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSet_FlushStyleSheets(
     raw_data: RawServoStyleSetBorrowed,
     doc_element: RawGeckoElementBorrowedOrNull,
 ) {
     let global_style_data = &*GLOBAL_STYLE_DATA;
@@ -997,20 +989,17 @@ pub extern "C" fn Servo_StyleSet_FlushSt
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSet_NoteStyleSheetsChanged(
     raw_data: RawServoStyleSetBorrowed,
     author_style_disabled: bool,
     changed_origins: OriginFlags,
 ) {
     let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
-    for origin in OriginSet::from(changed_origins).iter() {
-        data.stylesheets.force_dirty_origin(&origin);
-        data.clear_stylist_origin(&origin);
-    }
+    data.stylesheets.force_dirty(OriginSet::from(changed_origins));
     data.stylesheets.set_author_style_disabled(author_style_disabled);
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSheet_HasRules(
     raw_contents: RawServoStyleSheetContentsBorrowed
 ) -> bool {
     let global_style_data = &*GLOBAL_STYLE_DATA;
@@ -1935,22 +1924,16 @@ pub extern "C" fn Servo_StyleSet_Init(pr
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSet_RebuildCachedData(raw_data: RawServoStyleSetBorrowed) {
     let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
     data.stylist.device_mut().rebuild_cached_data();
 }
 
 #[no_mangle]
-pub extern "C" fn Servo_StyleSet_Clear(raw_data: RawServoStyleSetBorrowed) {
-    let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
-    data.clear_stylist();
-}
-
-#[no_mangle]
 pub extern "C" fn Servo_StyleSet_Drop(data: RawServoStyleSetOwned) {
     let _ = data.into_box::<PerDocumentStyleData>();
 }
 
 
 /// Updating the stylesheets and redoing selector matching is always happens
 /// before the document element is inserted. Therefore we don't need to call
 /// `force_dirty` here.
--- a/servo/tests/unit/style/stylist.rs
+++ b/servo/tests/unit/style/stylist.rs
@@ -162,55 +162,25 @@ fn test_revalidation_selectors() {
 fn test_rule_ordering_same_specificity() {
     let (rules_list, _) = get_mock_rules(&["a.intro", "img.sidebar"]);
     let a = &rules_list[0][0];
     let b = &rules_list[1][0];
     assert!((a.specificity(), a.source_order) < ((b.specificity(), b.source_order)),
             "The rule that comes later should win.");
 }
 
-
-#[test]
-fn test_get_id_name() {
-    let (rules_list, _) = get_mock_rules(&[".intro", "#top"]);
-    assert_eq!(selector_map::get_id_name(rules_list[0][0].selector.iter()), None);
-    assert_eq!(selector_map::get_id_name(rules_list[1][0].selector.iter()), Some(Atom::from("top")));
-}
-
-#[test]
-fn test_get_class_name() {
-    let (rules_list, _) = get_mock_rules(&[".intro.foo", "#top"]);
-    assert_eq!(selector_map::get_class_name(rules_list[0][0].selector.iter()), Some(Atom::from("intro")));
-    assert_eq!(selector_map::get_class_name(rules_list[1][0].selector.iter()), None);
-}
-
-#[test]
-fn test_get_local_name() {
-    let (rules_list, _) = get_mock_rules(&["img.foo", "#top", "IMG", "ImG"]);
-    let check = |i: usize, names: Option<(&str, &str)>| {
-        assert!(selector_map::get_local_name(rules_list[i][0].selector.iter())
-                == names.map(|(name, lower_name)| LocalNameSelector {
-                        name: LocalName::from(name),
-                        lower_name: LocalName::from(lower_name) }))
-    };
-    check(0, Some(("img", "img")));
-    check(1, None);
-    check(2, Some(("IMG", "img")));
-    check(3, Some(("ImG", "img")));
-}
-
 #[test]
 fn test_insert() {
     let (rules_list, _) = get_mock_rules(&[".intro.foo", "#top"]);
     let mut selector_map = SelectorMap::new();
     selector_map.insert(rules_list[1][0].clone(), QuirksMode::NoQuirks);
     assert_eq!(1, selector_map.id_hash.get(&Atom::from("top"), QuirksMode::NoQuirks).unwrap()[0].source_order);
     selector_map.insert(rules_list[0][0].clone(), QuirksMode::NoQuirks);
-    assert_eq!(0, selector_map.class_hash.get(&Atom::from("intro"), QuirksMode::NoQuirks).unwrap()[0].source_order);
-    assert!(selector_map.class_hash.get(&Atom::from("foo"), QuirksMode::NoQuirks).is_none());
+    assert_eq!(0, selector_map.class_hash.get(&Atom::from("foo"), QuirksMode::NoQuirks).unwrap()[0].source_order);
+    assert!(selector_map.class_hash.get(&Atom::from("intro"), QuirksMode::NoQuirks).is_none());
 }
 
 fn mock_stylist() -> Stylist {
     let device = Device::new(MediaType::screen(), TypedSize2D::new(0f32, 0f32), ScaleFactor::new(1.0));
     Stylist::new(device, QuirksMode::NoQuirks)
 }
 
 #[test]