Bug 1357583: style: Make effective_rules return an iterator, stop refcounting the Device. r?heycam draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Sat, 27 May 2017 00:02:35 +0200
changeset 585484 644e4fb2e5c8fd17834e54ac8eee621fd95d976c
parent 585330 e2f1accb18f2acc64b752d6238e7a5abbcba42c8
child 585485 b16f24d0edcab91158239c29f098a59907fe909b
push id61125
push userbmo:emilio+bugs@crisal.io
push dateFri, 26 May 2017 23:01:10 +0000
reviewersheycam
bugs1357583
milestone55.0a1
Bug 1357583: style: Make effective_rules return an iterator, stop refcounting the Device. r?heycam This makes the code cleaner, and also documents the fact that effective_rules recurses into imports. No we're not adding the imported stylesheets twice, and we share code with the invalidation analysis. MozReview-Commit-ID: DOF2AViTlmR
servo/components/style/gecko/data.rs
servo/components/style/gecko_string_cache/mod.rs
servo/components/style/invalidation/mod.rs
servo/components/style/stylesheets.rs
servo/components/style/stylist.rs
--- a/servo/components/style/gecko/data.rs
+++ b/servo/components/style/gecko/data.rs
@@ -69,17 +69,17 @@ impl PerDocumentStyleData {
     }
 }
 
 impl PerDocumentStyleDataImpl {
     /// Reset the device state because it may have changed.
     ///
     /// Implies also a stylesheet flush.
     pub fn reset_device(&mut self, guard: &SharedRwLockReadGuard) {
-        Arc::get_mut(self.stylist.device_mut()).unwrap().reset();
+        self.stylist.device_mut().reset();
         self.stylesheets.force_dirty();
         self.flush_stylesheets::<GeckoElement>(guard, None);
     }
 
     /// Recreate the style data if the stylesheets have changed.
     pub fn flush_stylesheets<E>(&mut self,
                                 guard: &SharedRwLockReadGuard,
                                 document_element: Option<E>)
--- a/servo/components/style/gecko_string_cache/mod.rs
+++ b/servo/components/style/gecko_string_cache/mod.rs
@@ -29,19 +29,19 @@ pub mod atom_macro {
     include!(concat!(env!("OUT_DIR"), "/gecko/atom_macro.rs"));
 }
 
 #[macro_use]
 pub mod namespace;
 
 pub use self::namespace::{Namespace, WeakNamespace};
 
-// macro_rules! local_name {
-//     ($s: tt) => { atom!($s) }
-// }
+macro_rules! local_name {
+    ($s: tt) => { atom!($s) }
+}
 
 /// A strong reference to a Gecko atom.
 #[derive(PartialEq, Eq)]
 pub struct Atom(*mut WeakAtom);
 
 /// An atom *without* a strong reference.
 ///
 /// Only usable as `&'a WeakAtom`,
--- a/servo/components/style/invalidation/mod.rs
+++ b/servo/components/style/invalidation/mod.rs
@@ -8,17 +8,17 @@
 
 use Atom;
 use data::StoredRestyleHint;
 use dom::{TElement, TNode};
 use fnv::FnvHashSet;
 use selector_parser::SelectorImpl;
 use selectors::parser::{Component, Selector};
 use shared_lock::SharedRwLockReadGuard;
-use stylesheets::{CssRule, CssRules, Stylesheet};
+use stylesheets::{CssRule, Stylesheet};
 use stylist::Stylist;
 
 /// A style scope represents a kind of subtree that may need to be restyled.
 #[derive(Debug, Hash, Eq, PartialEq)]
 enum InvalidationScope {
     /// All the descendants of an element with a given id.
     ID(Atom),
     /// All the descendants of an element with a given class name.
@@ -90,20 +90,23 @@ impl StylesheetInvalidationSet {
         }
 
         if stylesheet.disabled() ||
            !stylesheet.is_effective_for_device(stylist.device(), guard) {
             debug!(" > Stylesheet was not effective");
             return; // Nothing to do here.
         }
 
-        self.collect_invalidations_for_rule_list(
-            stylesheet.rules.read_with(guard),
-            stylist,
-            guard);
+        for rule in stylesheet.effective_rules(stylist.device(), guard) {
+            self.collect_invalidations_for_rule(rule, guard);
+            if self.fully_invalid {
+                self.invalid_scopes.clear();
+                break;
+            }
+        }
 
         debug!(" > resulting invalidations: {:?}", self.invalid_scopes);
         debug!(" > fully_invalid: {}", self.fully_invalid);
     }
 
     /// Clears the invalidation set, invalidating elements as needed if
     /// `document_element` is provided.
     pub fn flush<E>(&mut self, document_element: Option<E>)
@@ -174,33 +177,16 @@ impl StylesheetInvalidationSet {
             debug!("Children of {:?} changed, setting dirty descendants",
                    element);
             unsafe { element.set_dirty_descendants() }
         }
 
         return any_children_invalid
     }
 
-    /// Collects invalidations for a given list of CSS rules.
-    ///
-    /// TODO(emilio): Convert stylesheet.effective_rules into an iterator to
-    /// share code. This needs the ability to stop ASAP.
-    fn collect_invalidations_for_rule_list(
-        &mut self,
-        rules: &CssRules,
-        stylist: &Stylist,
-        guard: &SharedRwLockReadGuard)
-    {
-        for rule in &rules.0 {
-            if !self.collect_invalidations_for_rule(rule, stylist, guard) {
-                return;
-            }
-        }
-    }
-
     fn scan_component(
         component: &Component<SelectorImpl>,
         scope: &mut Option<InvalidationScope>)
     {
         match *component {
             Component::Class(ref class) => {
                 if scope.as_ref().map_or(true, |s| !s.is_id()) {
                     *scope = Some(InvalidationScope::Class(class.clone()));
@@ -256,97 +242,50 @@ impl StylesheetInvalidationSet {
                 // If we didn't found a scope, any selector could match this, so
                 // let's just bail out.
                 self.fully_invalid = true;
             }
         }
     }
 
     /// Collects invalidations for a given CSS rule.
-    ///
-    /// Returns true if it needs to keep collecting invalidations for subsequent
-    /// rules in this list.
     fn collect_invalidations_for_rule(
         &mut self,
         rule: &CssRule,
-        stylist: &Stylist,
         guard: &SharedRwLockReadGuard)
-        -> bool
     {
         use stylesheets::CssRule::*;
         debug!("StylesheetInvalidationSet::collect_invalidations_for_rule");
         debug_assert!(!self.fully_invalid, "Not worth to be here!");
 
         match *rule {
-            Document(ref lock) => {
-                let doc_rule = lock.read_with(guard);
-                if !doc_rule.condition.evaluate(stylist.device()) {
-                    return false; // Won't apply anything else after this.
-                }
-            }
             Style(ref lock) => {
                 let style_rule = lock.read_with(guard);
                 for selector in &style_rule.selectors.0 {
                     self.collect_scopes(selector);
                     if self.fully_invalid {
-                        return false;
+                        return;
                     }
                 }
             }
-            Namespace(..) => {
-                // Irrelevant to which selector scopes match.
-            }
-            // NB: We need to do it here, we won't visit the appropriate sheet
-            // otherwise!
-            Import(ref lock) => {
-                let import_rule = lock.read_with(guard);
-
-                let mq = import_rule.stylesheet.media.read_with(guard);
-                if !mq.evaluate(stylist.device(), stylist.quirks_mode()) {
-                    return true;
-                }
-
-                self.collect_invalidations_for_rule_list(
-                    import_rule.stylesheet.rules.read_with(guard),
-                    stylist,
-                    guard);
-            }
-            Media(ref lock) => {
-                let media_rule = lock.read_with(guard);
-
-                let mq = media_rule.media_queries.read_with(guard);
-                if !mq.evaluate(stylist.device(), stylist.quirks_mode()) {
-                    return true;
-                }
-
-                self.collect_invalidations_for_rule_list(
-                    media_rule.rules.read_with(guard),
-                    stylist,
-                    guard);
-            }
-            Supports(ref lock) => {
-                let supports_rule = lock.read_with(guard);
-                if !supports_rule.enabled {
-                    return true;
-                }
-
-                self.collect_invalidations_for_rule_list(
-                    supports_rule.rules.read_with(guard),
-                    stylist,
-                    guard);
+            Document(..) |
+            Namespace(..) |
+            Import(..) |
+            Media(..) |
+            Supports(..) => {
+                // Do nothing, relevant nested rules are visited as part of the
+                // iteration.
             }
             FontFace(..) |
             CounterStyle(..) |
             Keyframes(..) |
             Page(..) |
             Viewport(..) => {
                 debug!(" > Found unsupported rule, marking the whole subtree invalid.");
                 // TODO(emilio): Can we do better here?
                 //
                 // At least in `@page`, we could check the relevant media, I
                 // guess.
                 self.fully_invalid = true;
             }
         }
-
-        return !self.fully_invalid
     }
 }
--- a/servo/components/style/stylesheets.rs
+++ b/servo/components/style/stylesheets.rs
@@ -32,19 +32,21 @@ use parser::{PARSING_MODE_DEFAULT, Parse
 use properties::{PropertyDeclarationBlock, parse_property_declaration_list};
 use selector_parser::{SelectorImpl, SelectorParser};
 use selectors::parser::SelectorList;
 #[cfg(feature = "servo")]
 use servo_config::prefs::PREFS;
 #[cfg(not(feature = "gecko"))]
 use servo_url::ServoUrl;
 use shared_lock::{SharedRwLock, Locked, ToCssWithGuard, SharedRwLockReadGuard};
+use smallvec::SmallVec;
 use std::borrow::Borrow;
 use std::cell::Cell;
 use std::fmt;
+use std::slice;
 use std::sync::atomic::{AtomicBool, Ordering};
 use str::starts_with_ignore_ascii_case;
 use style_traits::ToCss;
 use stylearc::Arc;
 use stylist::FnvHashMap;
 use supports::SupportsCondition;
 use values::{CustomIdent, KeyframesName};
 use values::specified::url::SpecifiedUrl;
@@ -338,26 +340,16 @@ pub enum CssRuleType {
     // https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#extentions-to-cssrule-interface
     Document            = 13,
     // https://drafts.csswg.org/css-fonts-3/#om-fontfeaturevalues
     FontFeatureValues   = 14,
     // https://drafts.csswg.org/css-device-adapt/#css-rule-interface
     Viewport            = 15,
 }
 
-/// Result type for with_nested_rules_mq_and_doc_rule()
-pub enum NestedRulesResult<'a> {
-    /// Only rules
-    Rules(&'a [CssRule]),
-    /// Rules with media queries
-    RulesWithMediaQueries(&'a [CssRule], &'a MediaList),
-    /// Rules with document rule
-    RulesWithDocument(&'a [CssRule], &'a DocumentRule)
-}
-
 #[allow(missing_docs)]
 pub enum SingleRuleParseError {
     Syntax,
     Hierarchy,
 }
 
 impl CssRule {
     #[allow(missing_docs)]
@@ -381,70 +373,16 @@ impl CssRule {
         match *self {
             // CssRule::Charset(..) => State::Start,
             CssRule::Import(..) => State::Imports,
             CssRule::Namespace(..) => State::Namespaces,
             _ => State::Body,
         }
     }
 
-    /// Call `f` with the slice of rules directly contained inside this rule.
-    ///
-    /// Note that only some types of rules can contain rules. An empty slice is
-    /// used for others.
-    ///
-    /// This will not recurse down unsupported @supports rules
-    pub fn with_nested_rules_mq_and_doc_rule<F, R>(&self, guard: &SharedRwLockReadGuard, mut f: F) -> R
-    where F: FnMut(NestedRulesResult) -> R {
-        match *self {
-            CssRule::Import(ref lock) => {
-                let rule = lock.read_with(guard);
-                let media = rule.stylesheet.media.read_with(guard);
-                let rules = rule.stylesheet.rules.read_with(guard);
-                // FIXME(emilio): Include the nested rules if the stylesheet is
-                // loaded.
-                f(NestedRulesResult::RulesWithMediaQueries(&rules.0, &media))
-            }
-            CssRule::Namespace(_) |
-            CssRule::Style(_) |
-            CssRule::FontFace(_) |
-            CssRule::CounterStyle(_) |
-            CssRule::Viewport(_) |
-            CssRule::Keyframes(_) |
-            CssRule::Page(_) => {
-                f(NestedRulesResult::Rules(&[]))
-            }
-            CssRule::Media(ref lock) => {
-                let media_rule = lock.read_with(guard);
-                let mq = media_rule.media_queries.read_with(guard);
-                let rules = &media_rule.rules.read_with(guard).0;
-                f(NestedRulesResult::RulesWithMediaQueries(rules, &mq))
-            }
-            CssRule::Supports(ref lock) => {
-                let supports_rule = lock.read_with(guard);
-                let enabled = supports_rule.enabled;
-                if enabled {
-                    let rules = &supports_rule.rules.read_with(guard).0;
-                    f(NestedRulesResult::Rules(rules))
-                } else {
-                    f(NestedRulesResult::Rules(&[]))
-                }
-            }
-            CssRule::Document(ref lock) => {
-                if cfg!(feature = "gecko") {
-                    let document_rule = lock.read_with(guard);
-                    let rules = &document_rule.rules.read_with(guard).0;
-                    f(NestedRulesResult::RulesWithDocument(rules, &document_rule))
-                } else {
-                    unimplemented!()
-                }
-            }
-        }
-    }
-
     // input state is None for a nested rule
     // Returns a parsed CSS rule and the final state of the parser
     #[allow(missing_docs)]
     pub fn parse(css: &str,
                  parent_stylesheet: &Stylesheet,
                  state: Option<State>,
                  loader: Option<&StylesheetLoader>)
                  -> Result<(Self, State), SingleRuleParseError> {
@@ -901,16 +839,211 @@ impl DocumentRule {
         DocumentRule {
             condition: self.condition.clone(),
             rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock))),
             source_location: self.source_location.clone(),
         }
     }
 }
 
+/// A trait that describes statically which rules are iterated for a given
+/// RulesIterator.
+pub trait NestedRuleIterationCondition {
+    /// Whether we should process the nested rules in a given `@import` rule.
+    fn process_import(
+        guard: &SharedRwLockReadGuard,
+        device: &Device,
+        quirks_mode: QuirksMode,
+        rule: &ImportRule)
+        -> bool;
+
+    /// Whether we should process the nested rules in a given `@media` rule.
+    fn process_media(
+        guard: &SharedRwLockReadGuard,
+        device: &Device,
+        quirks_mode: QuirksMode,
+        rule: &MediaRule)
+        -> bool;
+
+    /// Whether we should process the nested rules in a given `@-moz-document` rule.
+    fn process_document(
+        guard: &SharedRwLockReadGuard,
+        device: &Device,
+        quirks_mode: QuirksMode,
+        rule: &DocumentRule)
+        -> bool;
+
+    /// Whether we should process the nested rules in a given `@supports` rule.
+    fn process_supports(
+        guard: &SharedRwLockReadGuard,
+        device: &Device,
+        quirks_mode: QuirksMode,
+        rule: &SupportsRule)
+        -> bool;
+}
+
+/// A struct that represents the condition that a rule applies to the document.
+pub struct EffectiveRules;
+
+impl NestedRuleIterationCondition for EffectiveRules {
+    /// Whether we should process the nested rules in a given `@import` rule.
+    fn process_import(
+        guard: &SharedRwLockReadGuard,
+        device: &Device,
+        quirks_mode: QuirksMode,
+        rule: &ImportRule)
+        -> bool
+    {
+        rule.stylesheet.media.read_with(guard).evaluate(device, quirks_mode)
+    }
+
+    /// Whether we should process the nested rules in a given `@media` rule.
+    fn process_media(
+        guard: &SharedRwLockReadGuard,
+        device: &Device,
+        quirks_mode: QuirksMode,
+        rule: &MediaRule)
+        -> bool
+    {
+        rule.media_queries.read_with(guard).evaluate(device, quirks_mode)
+    }
+
+    /// Whether we should process the nested rules in a given `@-moz-document` rule.
+    fn process_document(
+        _: &SharedRwLockReadGuard,
+        device: &Device,
+        _: QuirksMode,
+        rule: &DocumentRule)
+        -> bool
+    {
+        rule.condition.evaluate(device)
+    }
+
+    /// Whether we should process the nested rules in a given `@supports` rule.
+    fn process_supports(
+        _: &SharedRwLockReadGuard,
+        _: &Device,
+        _: QuirksMode,
+        rule: &SupportsRule)
+        -> bool
+    {
+        rule.enabled
+    }
+}
+
+/// An iterator over all the effective rules of a stylesheet.
+///
+/// NOTE: This iterator recurses into `@import` rules.
+pub type EffectiveRulesIterator<'a, 'b> = RulesIterator<'a, 'b, EffectiveRules>;
+
+/// An iterator over a list of rules.
+pub struct RulesIterator<'a, 'b, C>
+    where 'b: 'a,
+          C: NestedRuleIterationCondition + 'static,
+{
+    device: &'a Device,
+    quirks_mode: QuirksMode,
+    guard: &'a SharedRwLockReadGuard<'b>,
+    stack: SmallVec<[slice::Iter<'a, CssRule>; 3]>,
+    _phantom: ::std::marker::PhantomData<C>,
+}
+
+impl<'a, 'b, C> RulesIterator<'a, 'b, C>
+    where 'b: 'a,
+          C: NestedRuleIterationCondition + 'static,
+{
+    /// Creates a new `RulesIterator` to iterate over `rules`.
+    pub fn new(
+        device: &'a Device,
+        quirks_mode: QuirksMode,
+        guard: &'a SharedRwLockReadGuard<'b>,
+        rules: &'a CssRules)
+        -> Self
+    {
+        let mut stack = SmallVec::new();
+        stack.push(rules.0.iter());
+        Self {
+            device: device,
+            quirks_mode: quirks_mode,
+            guard: guard,
+            stack: stack,
+            _phantom: ::std::marker::PhantomData,
+        }
+    }
+}
+
+impl<'a, 'b, C> Iterator for RulesIterator<'a, 'b, C>
+    where 'b: 'a,
+          C: NestedRuleIterationCondition + 'static,
+{
+    type Item = &'a CssRule;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        while let Some(mut nested_iter) = self.stack.pop() {
+             let rule = match nested_iter.next() {
+                Some(r) => r,
+                None => continue,
+            };
+
+            let sub_iter = match *rule {
+                CssRule::Import(ref import_rule) => {
+                    let import_rule = import_rule.read_with(self.guard);
+
+                    if C::process_import(self.guard, self.device, self.quirks_mode, import_rule) {
+                        Some(import_rule.stylesheet.rules.read_with(self.guard).0.iter())
+                    } else {
+                        None
+                    }
+                }
+                CssRule::Document(ref doc_rule) => {
+                    let doc_rule = doc_rule.read_with(self.guard);
+                    if C::process_document(self.guard, self.device, self.quirks_mode, doc_rule) {
+                        Some(doc_rule.rules.read_with(self.guard).0.iter())
+                    } else {
+                        None
+                    }
+                }
+                CssRule::Media(ref lock) => {
+                    let media_rule = lock.read_with(self.guard);
+                    if C::process_media(self.guard, self.device, self.quirks_mode, media_rule) {
+                        Some(media_rule.rules.read_with(self.guard).0.iter())
+                    } else {
+                        None
+                    }
+                }
+                CssRule::Supports(ref lock) => {
+                    let supports_rule = lock.read_with(self.guard);
+                    if C::process_supports(self.guard, self.device, self.quirks_mode, supports_rule) {
+                        Some(supports_rule.rules.read_with(self.guard).0.iter())
+                    } else {
+                        None
+                    }
+                }
+                CssRule::Namespace(_) |
+                CssRule::Style(_) |
+                CssRule::FontFace(_) |
+                CssRule::CounterStyle(_) |
+                CssRule::Viewport(_) |
+                CssRule::Keyframes(_) |
+                CssRule::Page(_) => None,
+            };
+
+            self.stack.push(nested_iter);
+
+            if let Some(sub_iter) = sub_iter {
+                self.stack.push(sub_iter);
+            }
+
+            return Some(rule);
+        }
+
+        None
+    }
+}
+
 impl Stylesheet {
     /// Updates an empty stylesheet from a given string of text.
     pub fn update_from_str(existing: &Stylesheet,
                            css: &str,
                            url_data: &UrlExtraData,
                            stylesheet_loader: Option<&StylesheetLoader>,
                            error_reporter: &ParseErrorReporter,
                            line_number_offset: u64) {
@@ -1030,24 +1163,28 @@ impl Stylesheet {
     ///
     /// Always true if no associated MediaList exists.
     pub fn is_effective_for_device(&self, device: &Device, guard: &SharedRwLockReadGuard) -> bool {
         self.media.read_with(guard).evaluate(device, self.quirks_mode)
     }
 
     /// Return an iterator over the effective rules within the style-sheet, as
     /// according to the supplied `Device`.
-    ///
-    /// If a condition does not hold, its associated conditional group rule and
-    /// nested rules will be skipped. Use `rules` if all rules need to be
-    /// examined.
     #[inline]
-    pub fn effective_rules<F>(&self, device: &Device, guard: &SharedRwLockReadGuard, mut f: F)
-    where F: FnMut(&CssRule) {
-        effective_rules(&self.rules.read_with(guard).0, device, self.quirks_mode, guard, &mut f);
+    pub fn effective_rules<'a, 'b>(
+        &'a self,
+        device: &'a Device,
+        guard: &'a SharedRwLockReadGuard<'b>)
+        -> EffectiveRulesIterator<'a, 'b>
+    {
+        EffectiveRulesIterator::new(
+            device,
+            self.quirks_mode,
+            guard,
+            &self.rules.read_with(guard))
     }
 
     /// Returns whether the stylesheet has been explicitly disabled through the
     /// CSSOM.
     pub fn disabled(&self) -> bool {
         self.disabled.load(Ordering::SeqCst)
     }
 
@@ -1087,61 +1224,30 @@ impl Clone for Stylesheet {
             dirty_on_viewport_size_change: AtomicBool::new(
                 self.dirty_on_viewport_size_change.load(Ordering::SeqCst)),
             disabled: AtomicBool::new(self.disabled.load(Ordering::SeqCst)),
             quirks_mode: self.quirks_mode,
         }
     }
 }
 
-fn effective_rules<F>(rules: &[CssRule],
-                      device: &Device,
-                      quirks_mode: QuirksMode,
-                      guard: &SharedRwLockReadGuard,
-                      f: &mut F)
-    where F: FnMut(&CssRule)
-{
-    for rule in rules {
-        f(rule);
-        rule.with_nested_rules_mq_and_doc_rule(guard, |result| {
-            let rules = match result {
-                NestedRulesResult::Rules(rules) => {
-                    rules
-                },
-                NestedRulesResult::RulesWithMediaQueries(rules, media_queries) => {
-                    if !media_queries.evaluate(device, quirks_mode) {
-                        return;
-                    }
-                    rules
-                },
-                NestedRulesResult::RulesWithDocument(rules, doc_rule) => {
-                    if !doc_rule.condition.evaluate(device) {
-                        return;
-                    }
-                    rules
-                },
-            };
-            effective_rules(rules, device, quirks_mode, guard, f)
-        })
-    }
-}
-
 macro_rules! rule_filter {
     ($( $method: ident($variant:ident => $rule_type: ident), )+) => {
         impl Stylesheet {
             $(
                 #[allow(missing_docs)]
                 pub fn $method<F>(&self, device: &Device, guard: &SharedRwLockReadGuard, mut f: F)
-                where F: FnMut(&$rule_type) {
-                    self.effective_rules(device, guard, |rule| {
+                    where F: FnMut(&$rule_type),
+                {
+                    for rule in self.effective_rules(device, guard) {
                         if let CssRule::$variant(ref lock) = *rule {
                             let rule = lock.read_with(guard);
                             f(&rule)
                         }
-                    })
+                    }
                 }
             )+
         }
     }
 }
 
 rule_filter! {
     effective_style_rules(Style => StyleRule),
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -74,20 +74,17 @@ pub struct Stylist {
     ///
     /// With Gecko, the device is never changed. Gecko manually tracks whether
     /// the device data should be reconstructed, and "resets" the state of the
     /// device.
     ///
     /// On Servo, on the other hand, the device is a really cheap representation
     /// that is recreated each time some constraint changes and calling
     /// `set_device`.
-    ///
-    /// In both cases, the device is actually _owned_ by the Stylist, and it's
-    /// only an `Arc` so we can implement `add_stylesheet` more idiomatically.
-    device: Arc<Device>,
+    device: Device,
 
     /// Viewport constraints based on the current device.
     viewport_constraints: Option<ViewportConstraints>,
 
     /// If true, the quirks-mode stylesheet is applied.
     quirks_mode: QuirksMode,
 
     /// If true, the device has changed, and the stylist needs to be updated.
@@ -229,17 +226,17 @@ impl From<StyleRuleInclusion> for RuleIn
 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,
-            device: Arc::new(device),
+            device: device,
             is_device_dirty: true,
             is_cleared: true,
             quirks_mode: quirks_mode,
 
             element_map: PerPseudoElementSelectorMap::new(),
             pseudos_map: Default::default(),
             animations: Default::default(),
             precomputed_pseudo_element_decls: Default::default(),
@@ -359,18 +356,17 @@ impl Stylist {
                 doc_stylesheets.clone(), guards.author, &self.device
             ).finish(),
         };
 
         self.viewport_constraints =
             ViewportConstraints::maybe_new(&self.device, &cascaded_rule, self.quirks_mode);
 
         if let Some(ref constraints) = self.viewport_constraints {
-            Arc::get_mut(&mut self.device).unwrap()
-                .account_for_viewport_rule(constraints);
+            self.device.account_for_viewport_rule(constraints);
         }
 
         SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
             self.pseudos_map.insert(pseudo, PerPseudoElementSelectorMap::new());
         });
 
         extra_data.clear();
 
@@ -431,36 +427,52 @@ impl Stylist {
     fn add_stylesheet<'a>(&mut self,
                           stylesheet: &Stylesheet,
                           guard: &SharedRwLockReadGuard,
                           extra_data: &mut ExtraStyleData<'a>) {
         if stylesheet.disabled() || !stylesheet.is_effective_for_device(&self.device, guard) {
             return;
         }
 
-        // Cheap `Arc` clone so that the closure below can borrow `&mut Stylist`.
-        let device = self.device.clone();
-
-        stylesheet.effective_rules(&device, guard, |rule| {
+        for rule in stylesheet.effective_rules(&self.device, guard) {
             match *rule {
                 CssRule::Style(ref locked) => {
                     let style_rule = locked.read_with(&guard);
                     self.num_declarations += style_rule.block.read_with(&guard).len();
                     for selector in &style_rule.selectors.0 {
                         self.num_selectors += 1;
-                        self.add_rule_to_map(selector, locked, stylesheet);
+
+                        let map = if let Some(pseudo) = selector.pseudo_element() {
+                            self.pseudos_map
+                                .entry(pseudo.canonical())
+                                .or_insert_with(PerPseudoElementSelectorMap::new)
+                                .borrow_for_origin(&stylesheet.origin)
+                        } else {
+                            self.element_map.borrow_for_origin(&stylesheet.origin)
+                        };
+
+                        map.insert(Rule::new(selector.clone(),
+                                             locked.clone(),
+                                             self.rules_source_order));
+
                         self.dependencies.note_selector(selector);
-                        self.note_for_revalidation(selector);
-                        self.note_attribute_and_state_dependencies(selector);
+                        if needs_revalidation(selector) {
+                            self.selectors_for_cache_revalidation.insert(selector.inner.clone());
+                        }
+                        selector.visit(&mut AttributeAndStateDependencyVisitor {
+                            attribute_dependencies: &mut self.attribute_dependencies,
+                            style_attribute_dependency: &mut self.style_attribute_dependency,
+                            state_dependencies: &mut self.state_dependencies,
+                        });
                     }
                     self.rules_source_order += 1;
                 }
-                CssRule::Import(ref import) => {
-                    let import = import.read_with(guard);
-                    self.add_stylesheet(&import.stylesheet, guard, extra_data)
+                CssRule::Import(..) => {
+                    // effective_rules visits the inner stylesheet if
+                    // appropriate.
                 }
                 CssRule::Keyframes(ref keyframes_rule) => {
                     let keyframes_rule = keyframes_rule.read_with(guard);
                     debug!("Found valid keyframes rule: {:?}", *keyframes_rule);
 
                     // Don't let a prefixed keyframes animation override a non-prefixed one.
                     let needs_insertion = keyframes_rule.vendor_prefix.is_none() ||
                         self.animations.get(keyframes_rule.name.as_atom()).map_or(true, |rule|
@@ -478,74 +490,37 @@ impl Stylist {
                 }
                 #[cfg(feature = "gecko")]
                 CssRule::CounterStyle(ref rule) => {
                     extra_data.add_counter_style(guard, &rule);
                 }
                 // We don't care about any other rule.
                 _ => {}
             }
-        });
-    }
-
-    #[inline]
-    fn add_rule_to_map(&mut self,
-                       selector: &Selector<SelectorImpl>,
-                       rule: &Arc<Locked<StyleRule>>,
-                       stylesheet: &Stylesheet)
-    {
-        let map = if let Some(pseudo) = selector.pseudo_element() {
-            self.pseudos_map
-                .entry(pseudo.canonical())
-                .or_insert_with(PerPseudoElementSelectorMap::new)
-                .borrow_for_origin(&stylesheet.origin)
-        } else {
-            self.element_map.borrow_for_origin(&stylesheet.origin)
-        };
-
-        map.insert(Rule::new(selector.clone(),
-                             rule.clone(),
-                             self.rules_source_order));
-    }
-
-    #[inline]
-    fn note_for_revalidation(&mut self, selector: &Selector<SelectorImpl>) {
-        if needs_revalidation(selector) {
-            self.selectors_for_cache_revalidation.insert(selector.inner.clone());
         }
     }
 
     /// 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 {
-        #[cfg(feature = "servo")]
-        let style_lower_name = local_name!("style");
-        #[cfg(feature = "gecko")]
-        let style_lower_name = atom!("style");
-
-        if *local_name == style_lower_name {
+        if *local_name == local_name!("style") {
             self.style_attribute_dependency
         } else {
             self.attribute_dependencies.might_contain(local_name)
         }
     }
 
     /// 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.state_dependencies.intersects(state)
     }
 
-    #[inline]
-    fn note_attribute_and_state_dependencies(&mut self, selector: &Selector<SelectorImpl>) {
-        selector.visit(&mut AttributeAndStateDependencyVisitor(self));
-    }
-
     /// Computes the style for a given "precomputed" pseudo-element, taking the
     /// universal rules and applying them.
     ///
     /// If `inherit_all` is true, then all properties are inherited from the
     /// parent; otherwise, non-inherited properties are reset to their initial
     /// values. The flow constructor uses this flag when constructing anonymous
     /// flows.
     pub fn precomputed_values_for_pseudo(&self,
@@ -823,17 +798,17 @@ impl Stylist {
             let mq = stylesheet.media.read_with(guard);
             if mq.evaluate(&self.device, self.quirks_mode) != mq.evaluate(&device, self.quirks_mode) {
                 return true
             }
 
             mq_eval_changed(guard, &stylesheet.rules.read_with(guard).0, &self.device, &device, self.quirks_mode)
         });
 
-        self.device = Arc::new(device);
+        self.device = device;
     }
 
     /// Returns the viewport constraints that apply to this document because of
     /// a @viewport rule.
     pub fn viewport_constraints(&self) -> Option<&ViewportConstraints> {
         self.viewport_constraints.as_ref()
     }
 
@@ -1114,17 +1089,17 @@ impl Stylist {
     }
 
     /// Accessor for a shared reference to the device.
     pub fn device(&self) -> &Device {
         &self.device
     }
 
     /// Accessor for a mutable reference to the device.
-    pub fn device_mut(&mut self) -> &mut Arc<Device> {
+    pub fn device_mut(&mut self) -> &mut Device {
         &mut self.device
     }
 
     /// Accessor for a shared reference to the rule tree.
     pub fn rule_tree(&self) -> &RuleTree {
         &self.rule_tree
     }
 }
@@ -1141,41 +1116,45 @@ impl Drop for Stylist {
         // TODO(emilio): We can at least assert all the elements in the free
         // list are indeed free.
         unsafe { self.rule_tree.gc(); }
     }
 }
 
 /// Visitor to collect names that appear in attribute selectors and any
 /// dependencies on ElementState bits.
-struct AttributeAndStateDependencyVisitor<'a>(&'a mut Stylist);
+struct AttributeAndStateDependencyVisitor<'a> {
+    attribute_dependencies: &'a mut BloomFilter,
+    style_attribute_dependency: &'a mut bool,
+    state_dependencies: &'a mut ElementState,
+}
 
 impl<'a> SelectorVisitor for AttributeAndStateDependencyVisitor<'a> {
     type Impl = SelectorImpl;
 
     fn visit_attribute_selector(&mut self, _ns: &NamespaceConstraint<&Namespace>,
                                 name: &LocalName, lower_name: &LocalName)
                                 -> bool {
         #[cfg(feature = "servo")]
         let style_lower_name = local_name!("style");
         #[cfg(feature = "gecko")]
         let style_lower_name = atom!("style");
 
         if *lower_name == style_lower_name {
-            self.0.style_attribute_dependency = true;
+            *self.style_attribute_dependency = true;
         } else {
-            self.0.attribute_dependencies.insert(&name);
-            self.0.attribute_dependencies.insert(&lower_name);
+            self.attribute_dependencies.insert(&name);
+            self.attribute_dependencies.insert(&lower_name);
         }
         true
     }
 
     fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool {
         if let Component::NonTSPseudoClass(ref p) = *s {
-            self.0.state_dependencies.insert(p.state_flag());
+            self.state_dependencies.insert(p.state_flag());
         }
         true
     }
 }
 
 /// Visitor determine whether a selector requires cache revalidation.
 ///
 /// Note that we just check simple selectors and eagerly return when the first