servo: Merge #17147 - Bug 1357461 - stylo: Cache media query results and only flush and restyle if they changed (from emilio:media-query-caching); r=heycam
authorEmilio Cobos Álvarez <emilio@crisal.io>
Sat, 03 Jun 2017 08:11:23 -0700
changeset 412665 3b2ed0ccc738bed2e66fd69329218b0f86a51849
parent 412664 9b7b1057cfc67dfe606e522d19fff53da246dc5c
child 412666 54e9145b8892d636175b488b0eddc9a480fd8443
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs1357461
milestone55.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 #17147 - Bug 1357461 - stylo: Cache media query results and only flush and restyle if they changed (from emilio:media-query-caching); r=heycam Source-Repo: https://github.com/servo/servo Source-Revision: 7b61d5542194c4a9affced7fd408cfe3af5ddb23
servo/components/style/gecko/data.rs
servo/components/style/gecko/media_queries.rs
servo/components/style/invalidation/media_queries.rs
servo/components/style/invalidation/mod.rs
servo/components/style/invalidation/stylesheets.rs
servo/components/style/stylesheet_set.rs
servo/components/style/stylesheets.rs
servo/components/style/stylist.rs
servo/ports/geckolib/glue.rs
--- a/servo/components/style/gecko/data.rs
+++ b/servo/components/style/gecko/data.rs
@@ -4,17 +4,16 @@
 
 //! Data needed to style a Gecko document.
 
 use Atom;
 use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
 use dom::TElement;
 use fnv::FnvHashMap;
 use gecko::rules::{CounterStyleRule, FontFaceRule};
-use gecko::wrapper::GeckoElement;
 use gecko_bindings::bindings::RawServoStyleSet;
 use gecko_bindings::structs::RawGeckoPresContextOwned;
 use gecko_bindings::structs::nsIDocument;
 use gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI};
 use media_queries::Device;
 use properties::ComputedValues;
 use shared_lock::{Locked, StylesheetGuards, SharedRwLockReadGuard};
 use stylearc::Arc;
@@ -65,25 +64,16 @@ impl PerDocumentStyleData {
 
     /// Get an mutable reference to this style data.
     pub fn borrow_mut(&self) -> AtomicRefMut<PerDocumentStyleDataImpl> {
         self.0.borrow_mut()
     }
 }
 
 impl PerDocumentStyleDataImpl {
-    /// Reset the device state because it may have changed.
-    ///
-    /// Implies also a stylesheet flush.
-    pub fn reset_device(&mut self, guard: &SharedRwLockReadGuard) {
-        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>)
         where E: TElement,
     {
         if !self.stylesheets.has_changed() {
             return;
--- a/servo/components/style/gecko/media_queries.rs
+++ b/servo/components/style/gecko/media_queries.rs
@@ -94,24 +94,31 @@ impl Device {
         Au::new(self.root_font_size.load(Ordering::Relaxed) as i32)
     }
 
     /// Set the font size of the root element (for rem)
     pub fn set_root_font_size(&self, size: Au) {
         self.root_font_size.store(size.0 as isize, Ordering::Relaxed)
     }
 
+    /// Recreates the default computed values.
+    pub fn reset_computed_values(&mut self) {
+        // NB: A following stylesheet flush will populate this if appropriate.
+        self.viewport_override = None;
+        self.default_values = ComputedValues::default_values(unsafe { &*self.pres_context });
+    }
+
     /// Recreates all the temporary state that the `Device` stores.
     ///
     /// This includes the viewport override from `@viewport` rules, and also the
     /// default computed values.
     pub fn reset(&mut self) {
         // NB: A following stylesheet flush will populate this if appropriate.
         self.viewport_override = None;
-        self.default_values = ComputedValues::default_values(unsafe { &*self.pres_context });
+        self.reset_computed_values();
     }
 
     /// Returns the current media type of the device.
     pub fn media_type(&self) -> MediaType {
         unsafe {
             // FIXME(emilio): Gecko allows emulating random media with
             // mIsEmulatingMedia / mMediaEmulated . Refactor both sides so that
             // is supported (probably just making MediaType an Atom).
new file mode 100644
--- /dev/null
+++ b/servo/components/style/invalidation/media_queries.rs
@@ -0,0 +1,133 @@
+/* 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/. */
+
+//! Code related to the invalidation of media-query-affected rules.
+
+use context::QuirksMode;
+use fnv::FnvHashSet;
+use media_queries::Device;
+use shared_lock::SharedRwLockReadGuard;
+use stylesheets::{DocumentRule, ImportRule, MediaRule,  SupportsRule};
+use stylesheets::{NestedRuleIterationCondition, Stylesheet};
+
+/// A key for a given media query result.
+///
+/// NOTE: It happens to be the case that all the media lists we care about
+/// happen to have a stable address, so we can just use an opaque pointer to
+/// represent them.
+///
+/// Also, note that right now when a rule or stylesheet is removed, we do a full
+/// style flush, so there's no need to worry about other item created with the
+/// same pointer address.
+///
+/// If this changes, though, we may need to remove the item from the cache if
+/// present before it goes away.
+#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct MediaListKey(usize);
+
+/// A trait to get a given `MediaListKey` for a given item that can hold a
+/// `MediaList`.
+pub trait ToMediaListKey : Sized {
+    /// Get a `MediaListKey` for this item. This key needs to uniquely identify
+    /// the item.
+    #[allow(unsafe_code)]
+    fn to_media_list_key(&self) -> MediaListKey {
+        use std::mem;
+        MediaListKey(unsafe { mem::transmute(self as *const Self) })
+    }
+}
+
+impl ToMediaListKey for Stylesheet {}
+impl ToMediaListKey for ImportRule {}
+impl ToMediaListKey for MediaRule {}
+
+/// A struct that holds the result of a media query evaluation pass for the
+/// media queries that evaluated successfully.
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct EffectiveMediaQueryResults {
+    /// The set of media lists that matched last time.
+    set: FnvHashSet<MediaListKey>,
+}
+
+impl EffectiveMediaQueryResults {
+    /// Trivially constructs an empty `EffectiveMediaQueryResults`.
+    pub fn new() -> Self {
+        Self {
+            set: FnvHashSet::default(),
+        }
+    }
+
+    /// Resets the results, using an empty key.
+    pub fn clear(&mut self) {
+        self.set.clear()
+    }
+
+    /// Returns whether a given item was known to be effective when the results
+    /// were cached.
+    pub fn was_effective<T>(&self, item: &T) -> bool
+        where T: ToMediaListKey,
+    {
+        self.set.contains(&item.to_media_list_key())
+    }
+
+    /// Notices that an effective item has been seen, and caches it as matching.
+    pub fn saw_effective<T>(&mut self, item: &T)
+        where T: ToMediaListKey,
+    {
+        // NOTE(emilio): We can't assert that we don't cache the same item twice
+        // because of stylesheet reusing... shrug.
+        self.set.insert(item.to_media_list_key());
+    }
+}
+
+/// A filter that filters over effective rules, but allowing all potentially
+/// effective `@media` rules.
+pub struct PotentiallyEffectiveMediaRules;
+
+impl NestedRuleIterationCondition for PotentiallyEffectiveMediaRules {
+    fn process_import(
+        _: &SharedRwLockReadGuard,
+        _: &Device,
+        _: QuirksMode,
+        _: &ImportRule)
+        -> bool
+    {
+        true
+    }
+
+    fn process_media(
+        _: &SharedRwLockReadGuard,
+        _: &Device,
+        _: QuirksMode,
+        _: &MediaRule)
+        -> bool
+    {
+        true
+    }
+
+    /// 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
+    {
+        use stylesheets::EffectiveRules;
+        EffectiveRules::process_document(guard, device, quirks_mode, rule)
+    }
+
+    /// 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
+    {
+        use stylesheets::EffectiveRules;
+        EffectiveRules::process_supports(guard, device, quirks_mode, rule)
+    }
+}
--- a/servo/components/style/invalidation/mod.rs
+++ b/servo/components/style/invalidation/mod.rs
@@ -1,296 +1,8 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-//! A collection of invalidations due to changes in which stylesheets affect a
-//! document.
-
-#![deny(unsafe_code)]
-
-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, Stylesheet};
-use stylist::Stylist;
-
-/// An invalidation 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.
-    Class(Atom),
-}
-
-impl InvalidationScope {
-    fn is_id(&self) -> bool {
-        matches!(*self, InvalidationScope::ID(..))
-    }
-
-    fn matches<E>(&self, element: E) -> bool
-        where E: TElement,
-    {
-        match *self {
-            InvalidationScope::Class(ref class) => {
-                element.has_class(class)
-            }
-            InvalidationScope::ID(ref id) => {
-                match element.get_id() {
-                    Some(element_id) => element_id == *id,
-                    None => false,
-                }
-            }
-        }
-    }
-}
-
-/// A set of invalidations due to stylesheet additions.
-///
-/// TODO(emilio): We might be able to do the same analysis for removals and
-/// media query changes too?
-pub struct StylesheetInvalidationSet {
-    /// The style scopes we know we have to restyle so far.
-    invalid_scopes: FnvHashSet<InvalidationScope>,
-    /// Whether the whole document should be invalid.
-    fully_invalid: bool,
-}
-
-impl StylesheetInvalidationSet {
-    /// Create an empty `StylesheetInvalidationSet`.
-    pub fn new() -> Self {
-        Self {
-            invalid_scopes: FnvHashSet::default(),
-            fully_invalid: false,
-        }
-    }
-
-    /// Mark the DOM tree styles' as fully invalid.
-    pub fn invalidate_fully(&mut self) {
-        debug!("StylesheetInvalidationSet::invalidate_fully");
-        self.invalid_scopes.clear();
-        self.fully_invalid = true;
-    }
-
-    /// Analyze the given stylesheet, and collect invalidations from their
-    /// rules, in order to avoid doing a full restyle when we style the document
-    /// next time.
-    pub fn collect_invalidations_for(
-        &mut self,
-        stylist: &Stylist,
-        stylesheet: &Stylesheet,
-        guard: &SharedRwLockReadGuard)
-    {
-        debug!("StylesheetInvalidationSet::collect_invalidations_for");
-        if self.fully_invalid {
-            debug!(" > Fully invalid already");
-            return;
-        }
-
-        if stylesheet.disabled() ||
-           !stylesheet.is_effective_for_device(stylist.device(), guard) {
-            debug!(" > Stylesheet was not effective");
-            return; // Nothing to do here.
-        }
-
-        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>)
-        where E: TElement,
-    {
-        if let Some(e) = document_element {
-            self.process_invalidations_in_subtree(e);
-        }
-        self.invalid_scopes.clear();
-        self.fully_invalid = false;
-    }
-
-    /// Process style invalidations in a given subtree, that is, look for all
-    /// the relevant scopes in the subtree, and mark as dirty only the relevant
-    /// ones.
-    ///
-    /// Returns whether it invalidated at least one element's style.
-    #[allow(unsafe_code)]
-    fn process_invalidations_in_subtree<E>(&self, element: E) -> bool
-        where E: TElement,
-    {
-        let mut data = match element.mutate_data() {
-            Some(data) => data,
-            None => return false,
-        };
-
-        if !data.has_styles() {
-            return false;
-        }
-
-        if let Some(ref r) = data.get_restyle() {
-            if r.hint.contains_subtree() {
-                debug!("process_invalidations_in_subtree: {:?} was already invalid",
-                       element);
-                return false;
-            }
-        }
+//! Different bits of code related to invalidating style.
 
-        if self.fully_invalid {
-            debug!("process_invalidations_in_subtree: fully_invalid({:?})",
-                   element);
-            data.ensure_restyle().hint.insert(StoredRestyleHint::subtree());
-            return true;
-        }
-
-        for scope in &self.invalid_scopes {
-            if scope.matches(element) {
-                debug!("process_invalidations_in_subtree: {:?} matched {:?}",
-                       element, scope);
-                data.ensure_restyle().hint.insert(StoredRestyleHint::subtree());
-                return true;
-            }
-        }
-
-
-        let mut any_children_invalid = false;
-
-        for child in element.as_node().children() {
-            let child = match child.as_element() {
-                Some(e) => e,
-                None => continue,
-            };
-
-            any_children_invalid |= self.process_invalidations_in_subtree(child);
-        }
-
-        if any_children_invalid {
-            debug!("Children of {:?} changed, setting dirty descendants",
-                   element);
-            unsafe { element.set_dirty_descendants() }
-        }
-
-        return any_children_invalid
-    }
-
-    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()));
-                }
-            }
-            Component::ID(ref id) => {
-                if scope.is_none() {
-                    *scope = Some(InvalidationScope::ID(id.clone()));
-                }
-            }
-            _ => {
-                // Ignore everything else, at least for now.
-            }
-        }
-    }
-
-    /// Collect a style scopes for a given selector.
-    ///
-    /// We look at the outermost class or id selector to the left of an ancestor
-    /// combinator, in order to restyle only a given subtree.
-    ///
-    /// We prefer id scopes to class scopes, and outermost scopes to innermost
-    /// scopes (to reduce the amount of traversal we need to do).
-    fn collect_scopes(&mut self, selector: &Selector<SelectorImpl>) {
-        debug!("StylesheetInvalidationSet::collect_scopes({:?})", selector);
-
-        let mut scope: Option<InvalidationScope> = None;
-
-        let mut scan = true;
-        let mut iter = selector.inner.complex.iter();
-
-        loop {
-            for component in &mut iter {
-                if scan {
-                    Self::scan_component(component, &mut scope);
-                }
-            }
-            match iter.next_sequence() {
-                None => break,
-                Some(combinator) => {
-                    scan = combinator.is_ancestor();
-                }
-            }
-        }
-
-        match scope {
-            Some(s) => {
-                debug!(" > Found scope: {:?}", s);
-                self.invalid_scopes.insert(s);
-            }
-            None => {
-                debug!(" > Scope not found");
-
-                // If we didn't find a scope, any element could match this, so
-                // let's just bail out.
-                self.fully_invalid = true;
-            }
-        }
-    }
-
-    /// Collects invalidations for a given CSS rule.
-    fn collect_invalidations_for_rule(
-        &mut self,
-        rule: &CssRule,
-        guard: &SharedRwLockReadGuard)
-    {
-        use stylesheets::CssRule::*;
-        debug!("StylesheetInvalidationSet::collect_invalidations_for_rule");
-        debug_assert!(!self.fully_invalid, "Not worth to be here!");
-
-        match *rule {
-            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;
-                    }
-                }
-            }
-            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;
-            }
-        }
-    }
-}
+pub mod media_queries;
+pub mod stylesheets;
copy from servo/components/style/invalidation/mod.rs
copy to servo/components/style/invalidation/stylesheets.rs
--- a/servo/components/style/stylesheet_set.rs
+++ b/servo/components/style/stylesheet_set.rs
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! A centralized set of stylesheets for a document.
 
 use dom::TElement;
-use invalidation::StylesheetInvalidationSet;
+use invalidation::stylesheets::StylesheetInvalidationSet;
 use shared_lock::SharedRwLockReadGuard;
 use std::slice;
 use stylearc::Arc;
 use stylesheets::Stylesheet;
 use stylist::Stylist;
 
 /// Entry for a StylesheetSet. We don't bother creating a constructor, because
 /// there's no sensible defaults for the member variables.
@@ -170,16 +170,21 @@ impl StylesheetSet {
         where E: TElement,
     {
         debug!("StylesheetSet::flush");
         debug_assert!(self.dirty);
 
         self.dirty = false;
         self.invalidations.flush(document_element);
 
+        self.iter()
+    }
+
+    /// Returns an iterator over the current list of stylesheets.
+    pub fn iter(&self) -> StylesheetIterator {
         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) {
--- a/servo/components/style/stylesheets.rs
+++ b/servo/components/style/stylesheets.rs
@@ -1043,39 +1043,36 @@ impl NestedRuleIterationCondition for Al
         _: &Device,
         _: QuirksMode,
         _: &ImportRule)
         -> bool
     {
         true
     }
 
-    /// Whether we should process the nested rules in a given `@media` rule.
     fn process_media(
         _: &SharedRwLockReadGuard,
         _: &Device,
         _: QuirksMode,
         _: &MediaRule)
         -> bool
     {
         true
     }
 
-    /// Whether we should process the nested rules in a given `@-moz-document` rule.
     fn process_document(
         _: &SharedRwLockReadGuard,
         _: &Device,
         _: QuirksMode,
         _: &DocumentRule)
         -> bool
     {
         true
     }
 
-    /// Whether we should process the nested rules in a given `@supports` rule.
     fn process_supports(
         _: &SharedRwLockReadGuard,
         _: &Device,
         _: QuirksMode,
         _: &SupportsRule)
         -> bool
     {
         true
@@ -1153,46 +1150,41 @@ impl<'a, 'b, C> Iterator for RulesIterat
                         nested_iter_finished = true;
                         continue
                     }
                 };
 
                 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
+                        if !C::process_import(self.guard, self.device, self.quirks_mode, import_rule) {
+                            continue;
                         }
+                        Some(import_rule.stylesheet.rules.read_with(self.guard).0.iter())
                     }
                     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
+                        if !C::process_document(self.guard, self.device, self.quirks_mode, doc_rule) {
+                            continue;
                         }
+                        Some(doc_rule.rules.read_with(self.guard).0.iter())
                     }
                     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
+                        if !C::process_media(self.guard, self.device, self.quirks_mode, media_rule) {
+                            continue;
                         }
+                        Some(media_rule.rules.read_with(self.guard).0.iter())
                     }
                     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
+                        if !C::process_supports(self.guard, self.device, self.quirks_mode, supports_rule) {
+                            continue;
                         }
+                        Some(supports_rule.rules.read_with(self.guard).0.iter())
                     }
                     CssRule::Namespace(_) |
                     CssRule::Style(_) |
                     CssRule::FontFace(_) |
                     CssRule::CounterStyle(_) |
                     CssRule::Viewport(_) |
                     CssRule::Keyframes(_) |
                     CssRule::Page(_) => None,
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -9,16 +9,17 @@ use bit_vec::BitVec;
 use context::{QuirksMode, SharedStyleContext};
 use data::ComputedStyle;
 use dom::TElement;
 use element_state::ElementState;
 use error_reporting::RustLogReporter;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")]
 use gecko_bindings::structs::{nsIAtom, StyleRuleInclusion};
+use invalidation::media_queries::EffectiveMediaQueryResults;
 use keyframes::KeyframesAnimation;
 use media_queries::Device;
 use properties::{self, CascadeFlags, ComputedValues};
 use properties::{AnimationRules, PropertyDeclarationBlock};
 #[cfg(feature = "servo")]
 use properties::INHERIT_ALL;
 use restyle_hints::{HintComputationContext, DependencySet, RestyleHint};
 use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
@@ -34,19 +35,18 @@ use shared_lock::{Locked, SharedRwLockRe
 use sink::Push;
 use smallvec::{SmallVec, VecLike};
 #[cfg(feature = "servo")]
 use std::marker::PhantomData;
 use style_traits::viewport::ViewportConstraints;
 use stylearc::Arc;
 #[cfg(feature = "gecko")]
 use stylesheets::{CounterStyleRule, FontFaceRule};
-use stylesheets::{CssRule, DocumentRule, ImportRule, MediaRule, StyleRule, SupportsRule};
+use stylesheets::{CssRule, StyleRule};
 use stylesheets::{Stylesheet, Origin, UserAgentStylesheets};
-use stylesheets::NestedRuleIterationCondition;
 use thread_state;
 use viewport::{self, MaybeNew, ViewportRule};
 
 pub use ::fnv::FnvHashMap;
 
 /// List of applicable declaration. This is a transient structure that shuttles
 /// declarations between selector matching and inserting into the rule tree, and
 /// therefore we want to avoid heap-allocation where possible.
@@ -78,16 +78,19 @@ pub struct Stylist {
     /// 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`.
     device: Device,
 
     /// Viewport constraints based on the current device.
     viewport_constraints: Option<ViewportConstraints>,
 
+    /// Effective media query results cached from the last rebuild.
+    effective_media_query_results: EffectiveMediaQueryResults,
+
     /// If true, the quirks-mode stylesheet is applied.
     quirks_mode: QuirksMode,
 
     /// If true, the device has changed, and the stylist needs to be updated.
     is_device_dirty: bool,
 
     /// If true, the stylist is in a cleared state (e.g. just-constructed, or
     /// had clear() called on it with no following rebuild()).
@@ -217,79 +220,29 @@ impl From<StyleRuleInclusion> for RuleIn
     fn from(value: StyleRuleInclusion) -> Self {
         match value {
             StyleRuleInclusion::All => RuleInclusion::All,
             StyleRuleInclusion::DefaultOnly => RuleInclusion::DefaultOnly,
         }
     }
 }
 
-/// A filter that filters over effective rules, but allowing all potentially
-/// effective `@media` rules.
-pub struct PotentiallyEffectiveMediaRules;
-
-impl NestedRuleIterationCondition for PotentiallyEffectiveMediaRules {
-    fn process_import(
-        _: &SharedRwLockReadGuard,
-        _: &Device,
-        _: QuirksMode,
-        _: &ImportRule)
-        -> bool
-    {
-        true
-    }
-
-    fn process_media(
-        _: &SharedRwLockReadGuard,
-        _: &Device,
-        _: QuirksMode,
-        _: &MediaRule)
-        -> bool
-    {
-        true
-    }
-
-    /// 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
-    {
-        use stylesheets::EffectiveRules;
-        EffectiveRules::process_document(guard, device, quirks_mode, rule)
-    }
-
-    /// 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
-    {
-        use stylesheets::EffectiveRules;
-        EffectiveRules::process_supports(guard, device, quirks_mode, rule)
-    }
-}
-
-
 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: device,
             is_device_dirty: true,
             is_cleared: true,
             quirks_mode: quirks_mode,
+            effective_media_query_results: EffectiveMediaQueryResults::new(),
 
             element_map: PerPseudoElementSelectorMap::new(),
             pseudos_map: Default::default(),
             animations: Default::default(),
             precomputed_pseudo_element_decls: Default::default(),
             rules_source_order: 0,
             rule_tree: RuleTree::new(),
             dependencies: DependencySet::new(),
@@ -350,16 +303,17 @@ impl Stylist {
     /// Stylist::new.
     pub fn clear(&mut self) {
         if self.is_cleared {
             return
         }
 
         self.is_cleared = true;
 
+        self.effective_media_query_results.clear();
         self.viewport_constraints = None;
         // preserve current device
         self.is_device_dirty = true;
         // preserve current quirks_mode value
         self.element_map = PerPseudoElementSelectorMap::new();
         self.pseudos_map = Default::default();
         self.animations.clear(); // Or set to Default::default()?
         self.precomputed_pseudo_element_decls = Default::default();
@@ -477,16 +431,18 @@ 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;
         }
 
+        self.effective_media_query_results.saw_effective(stylesheet);
+
         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;
 
@@ -510,20 +466,27 @@ impl Stylist {
                         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(..) => {
-                    // effective_rules visits the inner stylesheet if
+                CssRule::Import(ref lock) => {
+                    let import_rule = lock.read_with(guard);
+                    self.effective_media_query_results.saw_effective(import_rule);
+
+                    // NOTE: effective_rules visits the inner stylesheet if
                     // appropriate.
                 }
+                CssRule::Media(ref lock) => {
+                    let media_rule = lock.read_with(guard);
+                    self.effective_media_query_results.saw_effective(media_rule);
+                }
                 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|
                             rule.vendor_prefix.is_some());
@@ -811,26 +774,60 @@ impl Stylist {
 
         self.viewport_constraints =
             ViewportConstraints::maybe_new(&device, &cascaded_rule, self.quirks_mode);
 
         if let Some(ref constraints) = self.viewport_constraints {
             device.account_for_viewport_rule(constraints);
         }
 
-        self.is_device_dirty |= stylesheets.iter().any(|stylesheet| {
-            let mq = stylesheet.media.read_with(guard);
-            if mq.evaluate(&self.device, self.quirks_mode) != mq.evaluate(&device, self.quirks_mode) {
+        self.device = device;
+        let features_changed = self.media_features_change_changed_style(
+            stylesheets.iter(),
+            guard
+        );
+        self.is_device_dirty |= features_changed;
+    }
+
+    /// Returns whether, given a media feature change, any previously-applicable
+    /// style has become non-applicable, or vice-versa.
+    pub fn media_features_change_changed_style<'a, I>(
+        &self,
+        stylesheets: I,
+        guard: &SharedRwLockReadGuard,
+    ) -> bool
+        where I: Iterator<Item = &'a Arc<Stylesheet>>
+    {
+        use invalidation::media_queries::PotentiallyEffectiveMediaRules;
+
+        debug!("Stylist::media_features_change_changed_style");
+
+        for stylesheet in stylesheets {
+            let effective_now =
+                stylesheet.media.read_with(guard)
+                    .evaluate(&self.device, self.quirks_mode);
+
+            let effective_then =
+                self.effective_media_query_results.was_effective(&**stylesheet);
+
+            if effective_now != effective_then {
+                debug!(" > Stylesheet changed -> {}, {}",
+                       effective_then, effective_now);
                 return true
             }
 
+            if !effective_now {
+                continue;
+            }
+
             let mut iter =
                 stylesheet.iter_rules::<PotentiallyEffectiveMediaRules>(
                     &self.device,
-                    guard);
+                    guard
+                );
 
             while let Some(rule) = iter.next() {
                 match *rule {
                     CssRule::Style(..) |
                     CssRule::Namespace(..) |
                     CssRule::FontFace(..) |
                     CssRule::CounterStyle(..) |
                     CssRule::Supports(..) |
@@ -839,44 +836,52 @@ impl Stylist {
                     CssRule::Viewport(..) |
                     CssRule::Document(..) => {
                         // Not affected by device changes.
                         continue;
                     }
                     CssRule::Import(ref lock) => {
                         let import_rule = lock.read_with(guard);
                         let mq = import_rule.stylesheet.media.read_with(guard);
-                        let effective_now = mq.evaluate(&self.device, self.quirks_mode);
-                        if effective_now != mq.evaluate(&device, self.quirks_mode) {
+                        let effective_now =
+                            mq.evaluate(&self.device, self.quirks_mode);
+                        let effective_then =
+                            self.effective_media_query_results.was_effective(import_rule);
+                        if effective_now != effective_then {
+                            debug!(" > @import rule changed {} -> {}",
+                                   effective_then, effective_now);
                             return true;
                         }
 
                         if !effective_now {
                             iter.skip_children();
                         }
                     }
                     CssRule::Media(ref lock) => {
                         let media_rule = lock.read_with(guard);
                         let mq = media_rule.media_queries.read_with(guard);
-                        let effective_now = mq.evaluate(&self.device, self.quirks_mode);
-                        if effective_now != mq.evaluate(&device, self.quirks_mode) {
+                        let effective_now =
+                            mq.evaluate(&self.device, self.quirks_mode);
+                        let effective_then =
+                            self.effective_media_query_results.was_effective(media_rule);
+                        if effective_now != effective_then {
+                            debug!(" > @media rule changed {} -> {}",
+                                   effective_then, effective_now);
                             return true;
                         }
 
                         if !effective_now {
                             iter.skip_children();
                         }
                     }
                 }
             }
+        }
 
-            return false;
-        });
-
-        self.device = device;
+        return false;
     }
 
     /// 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()
     }
 
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -743,16 +743,42 @@ pub extern "C" fn Servo_StyleSet_AppendS
         &data.stylist,
         sheet,
         unique_id,
         &guard);
     data.clear_stylist();
 }
 
 #[no_mangle]
+pub extern "C" fn Servo_StyleSet_MediumFeaturesChanged(
+    raw_data: RawServoStyleSetBorrowed,
+) -> bool {
+    let global_style_data = &*GLOBAL_STYLE_DATA;
+    let guard = global_style_data.shared_lock.read();
+
+    // NOTE(emilio): We don't actually need to flush the stylist here and ensure
+    // it's up to date.
+    //
+    // In case it isn't we would trigger a rebuild + restyle as needed too.
+    //
+    // We need to ensure the default computed values are up to date though,
+    // because those can influence the result of media query evaluation.
+    //
+    // FIXME(emilio, bug 1369984): do the computation conditionally, to do it
+    // less often.
+    let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
+
+    data.stylist.device_mut().reset_computed_values();
+    data.stylist.media_features_change_changed_style(
+        data.stylesheets.iter(),
+        &guard,
+    )
+}
+
+#[no_mangle]
 pub extern "C" fn Servo_StyleSet_PrependStyleSheet(raw_data: RawServoStyleSetBorrowed,
                                                    raw_sheet: RawServoStyleSheetBorrowed,
                                                    unique_id: u64) {
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
     let mut data = &mut *data;
     let sheet = HasArcFFI::as_arc(&raw_sheet);
     let guard = global_style_data.shared_lock.read();
@@ -1403,18 +1429,21 @@ pub extern "C" fn Servo_StyleSet_Init(pr
     let data = Box::new(PerDocumentStyleData::new(pres_context));
     data.into_ffi()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSet_RebuildData(raw_data: RawServoStyleSetBorrowed) {
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let guard = global_style_data.shared_lock.read();
+
     let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
-    data.reset_device(&guard);
+    data.stylist.device_mut().reset();
+    data.stylesheets.force_dirty();
+    data.flush_stylesheets::<GeckoElement>(&guard, None);
 }
 
 #[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();
 }