servo: Merge #17154 - style: Split stylesheets.rs (from emilio:split); r=Manishearth
authorEmilio Cobos Álvarez <emilio@crisal.io>
Sun, 04 Jun 2017 14:26:30 -0700
changeset 410339 0dd3473a3d40b5f915f89cf46c3fae6ef8e27db0
parent 410338 520b32ed6ba822d40687a2d9859697b8c0bbae36
child 410340 e5c037e4dbcc90b968edb6261a223817c8909a64
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersManishearth
bugs17154
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 #17154 - style: Split stylesheets.rs (from emilio:split); r=Manishearth This file has become quite bloated lately. This commit deletes that file in favor of a set of submodules. The only noticeable change apart from code move, is converting deep_clone_foo methods into a trait. It also unifies logic related to different style rules in the same place. There's some missing work, specially related to font-face and counter-style, but I think this is worth landing in the meantime. Source-Repo: https://github.com/servo/servo Source-Revision: c260c6ede0988f331011f33485964775e1f3a7f9
servo/components/script/dom/bindings/trace.rs
servo/components/script/dom/css.rs
servo/components/script/dom/csskeyframerule.rs
servo/components/script/dom/csskeyframesrule.rs
servo/components/script/dom/csssupportsrule.rs
servo/components/script/dom/cssviewportrule.rs
servo/components/script/dom/htmlmetaelement.rs
servo/components/style/animation.rs
servo/components/style/document_condition.rs
servo/components/style/gecko/arc_types.rs
servo/components/style/keyframes.rs
servo/components/style/lib.rs
servo/components/style/shared_lock.rs
servo/components/style/stylesheets.rs
servo/components/style/stylesheets/counter_style_rule.rs
servo/components/style/stylesheets/document_rule.rs
servo/components/style/stylesheets/font_face_rule.rs
servo/components/style/stylesheets/import_rule.rs
servo/components/style/stylesheets/keyframes_rule.rs
servo/components/style/stylesheets/loader.rs
servo/components/style/stylesheets/media_rule.rs
servo/components/style/stylesheets/memory.rs
servo/components/style/stylesheets/mod.rs
servo/components/style/stylesheets/namespace_rule.rs
servo/components/style/stylesheets/page_rule.rs
servo/components/style/stylesheets/rule_list.rs
servo/components/style/stylesheets/rule_parser.rs
servo/components/style/stylesheets/rules_iterator.rs
servo/components/style/stylesheets/style_rule.rs
servo/components/style/stylesheets/stylesheet.rs
servo/components/style/stylesheets/supports_rule.rs
servo/components/style/stylesheets/viewport_rule.rs
servo/components/style/stylist.rs
servo/components/style/supports.rs
servo/components/style/viewport.rs
servo/ports/geckolib/glue.rs
servo/tests/unit/style/keyframes.rs
servo/tests/unit/style/parsing/supports.rs
servo/tests/unit/style/stylesheets.rs
servo/tests/unit/style/viewport.rs
--- a/servo/components/script/dom/bindings/trace.rs
+++ b/servo/components/script/dom/bindings/trace.rs
@@ -90,26 +90,25 @@ use std::path::PathBuf;
 use std::rc::Rc;
 use std::sync::{Arc, Mutex};
 use std::sync::atomic::{AtomicBool, AtomicUsize};
 use std::sync::mpsc::{Receiver, Sender};
 use std::time::{SystemTime, Instant};
 use style::attr::{AttrIdentifier, AttrValue, LengthOrPercentageOrAuto};
 use style::context::QuirksMode;
 use style::element_state::*;
-use style::keyframes::Keyframe;
 use style::media_queries::MediaList;
 use style::properties::PropertyDeclarationBlock;
 use style::selector_parser::{PseudoElement, Snapshot};
 use style::shared_lock::{SharedRwLock as StyleSharedRwLock, Locked as StyleLocked};
 use style::stylearc::Arc as StyleArc;
 use style::stylesheets::{CssRules, FontFaceRule, KeyframesRule, MediaRule};
-use style::stylesheets::{NamespaceRule, StyleRule, ImportRule, SupportsRule};
+use style::stylesheets::{NamespaceRule, StyleRule, ImportRule, SupportsRule, ViewportRule};
+use style::stylesheets::keyframes_rule::Keyframe;
 use style::values::specified::Length;
-use style::viewport::ViewportRule;
 use time::Duration;
 use uuid::Uuid;
 use webrender_traits::{WebGLBufferId, WebGLError, WebGLFramebufferId, WebGLProgramId};
 use webrender_traits::{WebGLRenderbufferId, WebGLShaderId, WebGLTextureId, WebGLVertexArrayId};
 use webvr_traits::WebVRGamepadHand;
 
 /// A trait to allow tracing (only) DOM objects.
 pub unsafe trait JSTraceable {
--- a/servo/components/script/dom/css.rs
+++ b/servo/components/script/dom/css.rs
@@ -7,17 +7,17 @@ use dom::bindings::codegen::Bindings::Wi
 use dom::bindings::error::Fallible;
 use dom::bindings::reflector::Reflector;
 use dom::bindings::str::DOMString;
 use dom::window::Window;
 use dom_struct::dom_struct;
 use style::context::QuirksMode;
 use style::parser::{PARSING_MODE_DEFAULT, ParserContext};
 use style::stylesheets::CssRuleType;
-use style::supports::{Declaration, parse_condition_or_declaration};
+use style::stylesheets::supports_rule::{Declaration, parse_condition_or_declaration};
 
 #[dom_struct]
 pub struct CSS {
     reflector_: Reflector,
 }
 
 impl CSS {
     /// http://dev.w3.org/csswg/cssom/#serialize-an-identifier
--- a/servo/components/script/dom/csskeyframerule.rs
+++ b/servo/components/script/dom/csskeyframerule.rs
@@ -7,19 +7,19 @@ use dom::bindings::inheritance::Castable
 use dom::bindings::js::{JS, MutNullableJS, Root};
 use dom::bindings::reflector::{DomObject, reflect_dom_object};
 use dom::bindings::str::DOMString;
 use dom::cssrule::{CSSRule, SpecificCSSRule};
 use dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner};
 use dom::cssstylesheet::CSSStyleSheet;
 use dom::window::Window;
 use dom_struct::dom_struct;
-use style::keyframes::Keyframe;
 use style::shared_lock::{Locked, ToCssWithGuard};
 use style::stylearc::Arc;
+use style::stylesheets::keyframes_rule::Keyframe;
 
 #[dom_struct]
 pub struct CSSKeyframeRule {
     cssrule: CSSRule,
     #[ignore_heap_size_of = "Arc"]
     keyframerule: Arc<Locked<Keyframe>>,
     style_decl: MutNullableJS<CSSStyleDeclaration>,
 }
--- a/servo/components/script/dom/csskeyframesrule.rs
+++ b/servo/components/script/dom/csskeyframesrule.rs
@@ -11,20 +11,19 @@ use dom::bindings::js::{MutNullableJS, R
 use dom::bindings::reflector::{DomObject, reflect_dom_object};
 use dom::bindings::str::DOMString;
 use dom::csskeyframerule::CSSKeyframeRule;
 use dom::cssrule::{CSSRule, SpecificCSSRule};
 use dom::cssrulelist::{CSSRuleList, RulesSource};
 use dom::cssstylesheet::CSSStyleSheet;
 use dom::window::Window;
 use dom_struct::dom_struct;
-use style::keyframes::{Keyframe, KeyframeSelector};
 use style::shared_lock::{Locked, ToCssWithGuard};
 use style::stylearc::Arc;
-use style::stylesheets::KeyframesRule;
+use style::stylesheets::keyframes_rule::{KeyframesRule, Keyframe, KeyframeSelector};
 use style::values::KeyframesName;
 
 #[dom_struct]
 pub struct CSSKeyframesRule {
     cssrule: CSSRule,
     #[ignore_heap_size_of = "Arc"]
     keyframesrule: Arc<Locked<KeyframesRule>>,
     rulelist: MutNullableJS<CSSRuleList>,
--- a/servo/components/script/dom/csssupportsrule.rs
+++ b/servo/components/script/dom/csssupportsrule.rs
@@ -12,17 +12,17 @@ use dom::cssconditionrule::CSSConditionR
 use dom::cssrule::SpecificCSSRule;
 use dom::cssstylesheet::CSSStyleSheet;
 use dom::window::Window;
 use dom_struct::dom_struct;
 use style::parser::{PARSING_MODE_DEFAULT, ParserContext};
 use style::shared_lock::{Locked, ToCssWithGuard};
 use style::stylearc::Arc;
 use style::stylesheets::{CssRuleType, SupportsRule};
-use style::supports::SupportsCondition;
+use style::stylesheets::supports_rule::SupportsCondition;
 use style_traits::ToCss;
 
 #[dom_struct]
 pub struct CSSSupportsRule {
     cssconditionrule: CSSConditionRule,
     #[ignore_heap_size_of = "Arc"]
     supportsrule: Arc<Locked<SupportsRule>>,
 }
--- a/servo/components/script/dom/cssviewportrule.rs
+++ b/servo/components/script/dom/cssviewportrule.rs
@@ -7,17 +7,17 @@ use dom::bindings::js::Root;
 use dom::bindings::reflector::reflect_dom_object;
 use dom::bindings::str::DOMString;
 use dom::cssrule::{CSSRule, SpecificCSSRule};
 use dom::cssstylesheet::CSSStyleSheet;
 use dom::window::Window;
 use dom_struct::dom_struct;
 use style::shared_lock::{Locked, ToCssWithGuard};
 use style::stylearc::Arc;
-use style::viewport::ViewportRule;
+use style::stylesheets::ViewportRule;
 
 #[dom_struct]
 pub struct CSSViewportRule {
     cssrule: CSSRule,
     #[ignore_heap_size_of = "Arc"]
     viewportrule: Arc<Locked<ViewportRule>>,
 }
 
--- a/servo/components/script/dom/htmlmetaelement.rs
+++ b/servo/components/script/dom/htmlmetaelement.rs
@@ -21,18 +21,17 @@ use dom_struct::dom_struct;
 use html5ever::{LocalName, Prefix};
 use servo_config::prefs::PREFS;
 use std::ascii::AsciiExt;
 use std::sync::atomic::AtomicBool;
 use style::attr::AttrValue;
 use style::media_queries::MediaList;
 use style::str::HTML_SPACE_CHARACTERS;
 use style::stylearc::Arc;
-use style::stylesheets::{Stylesheet, CssRule, CssRules, Origin};
-use style::viewport::ViewportRule;
+use style::stylesheets::{Stylesheet, CssRule, CssRules, Origin, ViewportRule};
 
 #[dom_struct]
 pub struct HTMLMetaElement {
     htmlelement: HTMLElement,
     #[ignore_heap_size_of = "Arc"]
     stylesheet: DOMRefCell<Option<Arc<Stylesheet>>>,
     cssom_stylesheet: MutNullableJS<CSSStyleSheet>,
 }
--- a/servo/components/style/animation.rs
+++ b/servo/components/style/animation.rs
@@ -6,27 +6,27 @@
 #![deny(missing_docs)]
 
 use Atom;
 use bezier::Bezier;
 use context::SharedStyleContext;
 use dom::OpaqueNode;
 use euclid::point::Point2D;
 use font_metrics::FontMetricsProvider;
-use keyframes::{KeyframesStep, KeyframesStepValue};
 use properties::{self, CascadeFlags, ComputedValues, Importance};
 use properties::animated_properties::{AnimatedProperty, TransitionProperty};
 use properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection;
 use properties::longhands::animation_iteration_count::single_value::computed_value::T as AnimationIterationCount;
 use properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState;
 use properties::longhands::transition_timing_function::single_value::computed_value::StartEnd;
 use properties::longhands::transition_timing_function::single_value::computed_value::T as TransitionTimingFunction;
 use rule_tree::CascadeLevel;
 use std::sync::mpsc::Sender;
 use stylearc::Arc;
+use stylesheets::keyframes_rule::{KeyframesStep, KeyframesStepValue};
 use timer::Timer;
 use values::computed::Time;
 
 /// This structure represents a keyframes animation current iteration state.
 ///
 /// If the iteration count is infinite, there's no other state, otherwise we
 /// have to keep track the current iteration and the max iteration count.
 #[derive(Debug, Clone)]
--- a/servo/components/style/gecko/arc_types.rs
+++ b/servo/components/style/gecko/arc_types.rs
@@ -12,25 +12,25 @@ use gecko_bindings::bindings::{RawServoK
 use gecko_bindings::bindings::{RawServoMediaList, RawServoMediaRule};
 use gecko_bindings::bindings::{RawServoNamespaceRule, RawServoPageRule};
 use gecko_bindings::bindings::{RawServoRuleNode, RawServoRuleNodeStrong, RawServoDocumentRule};
 use gecko_bindings::bindings::{RawServoStyleSheet, RawServoImportRule, RawServoSupportsRule};
 use gecko_bindings::bindings::{ServoComputedValues, ServoCssRules};
 use gecko_bindings::structs::{RawServoDeclarationBlock, RawServoStyleRule};
 use gecko_bindings::structs::RawServoAnimationValue;
 use gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI};
-use keyframes::Keyframe;
 use media_queries::MediaList;
 use properties::{ComputedValues, PropertyDeclarationBlock};
 use properties::animated_properties::AnimationValue;
 use rule_tree::StrongRuleNode;
 use shared_lock::Locked;
 use std::{mem, ptr};
 use stylesheets::{CssRules, Stylesheet, StyleRule, ImportRule, KeyframesRule, MediaRule};
 use stylesheets::{NamespaceRule, PageRule, SupportsRule, DocumentRule};
+use stylesheets::keyframes_rule::Keyframe;
 
 macro_rules! impl_arc_ffi {
     ($servo_type:ty => $gecko_type:ty [$addref:ident, $release:ident]) => {
         unsafe impl HasFFI for $servo_type {
             type FFIType = $gecko_type;
         }
         unsafe impl HasArcFFI for $servo_type {}
 
--- a/servo/components/style/lib.rs
+++ b/servo/components/style/lib.rs
@@ -96,27 +96,25 @@ pub mod animation;
 pub mod bezier;
 pub mod bloom;
 pub mod cache;
 pub mod cascade_info;
 pub mod context;
 pub mod counter_style;
 pub mod custom_properties;
 pub mod data;
-pub mod document_condition;
 pub mod dom;
 pub mod element_state;
 #[cfg(feature = "servo")] mod encoding_support;
 pub mod error_reporting;
 pub mod font_face;
 pub mod font_metrics;
 #[cfg(feature = "gecko")] #[allow(unsafe_code)] pub mod gecko;
 #[cfg(feature = "gecko")] #[allow(unsafe_code)] pub mod gecko_bindings;
 pub mod invalidation;
-pub mod keyframes;
 #[allow(missing_docs)] // TODO.
 pub mod logical_geometry;
 pub mod matching;
 pub mod media_queries;
 pub mod parallel;
 pub mod parser;
 pub mod restyle_hints;
 pub mod rule_tree;
@@ -129,24 +127,22 @@ pub mod stylist;
 #[cfg(feature = "servo")] #[allow(unsafe_code)] pub mod servo;
 pub mod sequential;
 pub mod sink;
 pub mod str;
 pub mod style_adjuster;
 pub mod stylearc;
 pub mod stylesheet_set;
 pub mod stylesheets;
-pub mod supports;
 pub mod thread_state;
 pub mod timer;
 pub mod traversal;
 #[macro_use]
 #[allow(non_camel_case_types)]
 pub mod values;
-pub mod viewport;
 
 use std::fmt;
 use style_traits::ToCss;
 
 #[cfg(feature = "gecko")] pub use gecko_string_cache as string_cache;
 #[cfg(feature = "gecko")] pub use gecko_string_cache::Atom;
 #[cfg(feature = "gecko")] pub use gecko_string_cache::Namespace;
 #[cfg(feature = "gecko")] pub use gecko_string_cache::Atom as Prefix;
--- a/servo/components/style/shared_lock.rs
+++ b/servo/components/style/shared_lock.rs
@@ -221,16 +221,27 @@ pub trait ToCssWithGuard {
     #[inline]
     fn to_css_string(&self, guard: &SharedRwLockReadGuard) -> String {
         let mut s = String::new();
         self.to_css(guard, &mut s).unwrap();
         s
     }
 }
 
+/// A trait to do a deep clone of a given CSS type. Gets a lock and a read
+/// guard, in order to be able to read and clone nested structures.
+pub trait DeepCloneWithLock : Sized {
+    /// Deep clones this object.
+    fn deep_clone_with_lock(
+        &self,
+        lock: &SharedRwLock,
+        guard: &SharedRwLockReadGuard
+    ) -> Self;
+}
+
 /// Guards for a document
 #[derive(Clone)]
 pub struct StylesheetGuards<'a> {
     /// For author-origin stylesheets
     pub author: &'a SharedRwLockReadGuard<'a>,
 
     /// For user-agent-origin and user-origin stylesheets
     pub ua_or_user: &'a SharedRwLockReadGuard<'a>,
deleted file mode 100644
--- a/servo/components/style/stylesheets.rs
+++ /dev/null
@@ -1,1943 +0,0 @@
-/* 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/. */
-
-//! Style sheets and their CSS rules.
-
-#![deny(missing_docs)]
-
-use {Atom, Prefix, Namespace};
-use context::QuirksMode;
-use counter_style::{parse_counter_style_name, parse_counter_style_body};
-#[cfg(feature = "servo")]
-use counter_style::CounterStyleRuleData;
-use cssparser::{AtRuleParser, Parser, QualifiedRuleParser};
-use cssparser::{AtRuleType, RuleListParser, parse_one_rule, SourceLocation};
-use cssparser::ToCss as ParserToCss;
-use document_condition::DocumentCondition;
-use error_reporting::{ParseErrorReporter, NullReporter};
-#[cfg(feature = "servo")]
-use font_face::FontFaceRuleData;
-use font_face::parse_font_face_block;
-#[cfg(feature = "gecko")]
-pub use gecko::rules::{CounterStyleRule, FontFaceRule};
-#[cfg(feature = "gecko")]
-use gecko_bindings::structs::URLExtraData;
-#[cfg(feature = "gecko")]
-use gecko_bindings::sugar::refptr::RefPtr;
-use keyframes::{Keyframe, KeyframeSelector, parse_keyframe_list};
-use media_queries::{Device, MediaList, parse_media_query_list};
-use parking_lot::RwLock;
-use parser::{PARSING_MODE_DEFAULT, Parse, ParserContext, log_css_error};
-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::{fmt, mem};
-use std::borrow::Borrow;
-use std::mem::align_of;
-use std::os::raw::c_void;
-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::NamespaceId;
-use values::specified::url::SpecifiedUrl;
-use viewport::ViewportRule;
-
-
-/// Extra data that the backend may need to resolve url values.
-#[cfg(not(feature = "gecko"))]
-pub type UrlExtraData = ServoUrl;
-
-/// Extra data that the backend may need to resolve url values.
-#[cfg(feature = "gecko")]
-pub type UrlExtraData = RefPtr<URLExtraData>;
-
-#[cfg(feature = "gecko")]
-impl UrlExtraData {
-    /// Returns a string for the url.
-    ///
-    /// Unimplemented currently.
-    pub fn as_str(&self) -> &str {
-        // TODO
-        "(stylo: not supported)"
-    }
-}
-
-// XXX We probably need to figure out whether we should mark Eq here.
-// It is currently marked so because properties::UnparsedValue wants Eq.
-#[cfg(feature = "gecko")]
-impl Eq for UrlExtraData {}
-
-/// Each style rule has an origin, which determines where it enters the cascade.
-///
-/// http://dev.w3.org/csswg/css-cascade/#cascading-origins
-#[derive(Clone, PartialEq, Eq, Copy, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub enum Origin {
-    /// http://dev.w3.org/csswg/css-cascade/#cascade-origin-ua
-    UserAgent,
-
-    /// http://dev.w3.org/csswg/css-cascade/#cascade-origin-author
-    Author,
-
-    /// http://dev.w3.org/csswg/css-cascade/#cascade-origin-user
-    User,
-}
-
-/// A set of namespaces applying to a given stylesheet.
-///
-/// The namespace id is used in gecko
-#[derive(Clone, Default, Debug)]
-#[allow(missing_docs)]
-pub struct Namespaces {
-    pub default: Option<(Namespace, NamespaceId)>,
-    pub prefixes: FnvHashMap<Prefix, (Namespace, NamespaceId)>,
-}
-
-/// Like gecko_bindings::structs::MallocSizeOf, but without the Option<> wrapper. Note that
-/// functions of this type should not be called via do_malloc_size_of(), rather than directly.
-pub type MallocSizeOfFn = unsafe extern "C" fn(ptr: *const c_void) -> usize;
-
-/// Call malloc_size_of on ptr, first checking that the allocation isn't empty.
-pub unsafe fn do_malloc_size_of<T>(malloc_size_of: MallocSizeOfFn, ptr: *const T) -> usize {
-    if ptr as usize <= align_of::<T>() {
-        0
-    } else {
-        malloc_size_of(ptr as *const c_void)
-    }
-}
-
-/// Trait for measuring the size of heap data structures.
-pub trait MallocSizeOf {
-    /// Measure the size of any heap-allocated structures that hang off this value, but not the
-    /// space taken up by the value itself.
-    fn malloc_size_of_children(&self, malloc_size_of: MallocSizeOfFn) -> usize;
-}
-
-/// Like MallocSizeOf, but operates with the global SharedRwLockReadGuard locked.
-pub trait MallocSizeOfWithGuard {
-    /// Like MallocSizeOf::malloc_size_of_children, but with a |guard| argument.
-    fn malloc_size_of_children(&self, guard: &SharedRwLockReadGuard,
-                               malloc_size_of: MallocSizeOfFn) -> usize;
-}
-
-impl<A: MallocSizeOf, B: MallocSizeOf> MallocSizeOf for (A, B) {
-    fn malloc_size_of_children(&self, malloc_size_of: MallocSizeOfFn) -> usize {
-        self.0.malloc_size_of_children(malloc_size_of) +
-            self.1.malloc_size_of_children(malloc_size_of)
-    }
-}
-
-impl<T: MallocSizeOf> MallocSizeOf for Vec<T> {
-    fn malloc_size_of_children(&self, malloc_size_of: MallocSizeOfFn) -> usize {
-        self.iter().fold(
-            unsafe { do_malloc_size_of(malloc_size_of, self.as_ptr()) },
-            |n, elem| n + elem.malloc_size_of_children(malloc_size_of))
-    }
-}
-
-impl<T: MallocSizeOfWithGuard> MallocSizeOfWithGuard for Vec<T> {
-    fn malloc_size_of_children(&self, guard: &SharedRwLockReadGuard,
-                               malloc_size_of: MallocSizeOfFn) -> usize {
-        self.iter().fold(
-            unsafe { do_malloc_size_of(malloc_size_of, self.as_ptr()) },
-            |n, elem| n + elem.malloc_size_of_children(guard, malloc_size_of))
-    }
-}
-
-/// A list of CSS rules.
-#[derive(Debug)]
-pub struct CssRules(pub Vec<CssRule>);
-
-impl CssRules {
-    /// Whether this CSS rules is empty.
-    pub fn is_empty(&self) -> bool {
-        self.0.is_empty()
-    }
-
-    /// Creates a deep clone where each CssRule has also been cloned with
-    /// the provided lock.
-    fn deep_clone_with_lock(&self,
-                            lock: &SharedRwLock) -> CssRules {
-        CssRules(
-            self.0.iter().map(|ref x| x.deep_clone_with_lock(lock)).collect()
-        )
-    }
-}
-
-impl MallocSizeOfWithGuard for CssRules {
-    fn malloc_size_of_children(&self, guard: &SharedRwLockReadGuard,
-                               malloc_size_of: MallocSizeOfFn) -> usize {
-        self.0.malloc_size_of_children(guard, malloc_size_of)
-    }
-}
-
-#[allow(missing_docs)]
-pub enum RulesMutateError {
-    Syntax,
-    IndexSize,
-    HierarchyRequest,
-    InvalidState,
-}
-
-impl From<SingleRuleParseError> for RulesMutateError {
-    fn from(other: SingleRuleParseError) -> Self {
-        match other {
-            SingleRuleParseError::Syntax => RulesMutateError::Syntax,
-            SingleRuleParseError::Hierarchy => RulesMutateError::HierarchyRequest,
-        }
-    }
-}
-
-impl CssRules {
-    #[allow(missing_docs)]
-    pub fn new(rules: Vec<CssRule>, shared_lock: &SharedRwLock) -> Arc<Locked<CssRules>> {
-        Arc::new(shared_lock.wrap(CssRules(rules)))
-    }
-
-    fn only_ns_or_import(&self) -> bool {
-        self.0.iter().all(|r| {
-            match *r {
-                CssRule::Namespace(..) |
-                CssRule::Import(..) => true,
-                _ => false
-            }
-        })
-    }
-
-    /// https://drafts.csswg.org/cssom/#remove-a-css-rule
-    pub fn remove_rule(&mut self, index: usize) -> Result<(), RulesMutateError> {
-        // Step 1, 2
-        if index >= self.0.len() {
-            return Err(RulesMutateError::IndexSize);
-        }
-
-        {
-            // Step 3
-            let ref rule = self.0[index];
-
-            // Step 4
-            if let CssRule::Namespace(..) = *rule {
-                if !self.only_ns_or_import() {
-                    return Err(RulesMutateError::InvalidState);
-                }
-            }
-        }
-
-        // Step 5, 6
-        self.0.remove(index);
-        Ok(())
-    }
-}
-
-/// A trait to implement helpers for `Arc<Locked<CssRules>>`.
-pub trait CssRulesHelpers {
-    /// https://drafts.csswg.org/cssom/#insert-a-css-rule
-    ///
-    /// Written in this funky way because parsing an @import rule may cause us
-    /// to clone a stylesheet from the same document due to caching in the CSS
-    /// loader.
-    ///
-    /// TODO(emilio): We could also pass the write guard down into the loader
-    /// instead, but that seems overkill.
-    fn insert_rule(&self,
-                   lock: &SharedRwLock,
-                   rule: &str,
-                   parent_stylesheet: &Stylesheet,
-                   index: usize,
-                   nested: bool,
-                   loader: Option<&StylesheetLoader>)
-                   -> Result<CssRule, RulesMutateError>;
-}
-
-impl CssRulesHelpers for Arc<Locked<CssRules>> {
-    fn insert_rule(&self,
-                   lock: &SharedRwLock,
-                   rule: &str,
-                   parent_stylesheet: &Stylesheet,
-                   index: usize,
-                   nested: bool,
-                   loader: Option<&StylesheetLoader>)
-                   -> Result<CssRule, RulesMutateError> {
-        let state = {
-            let read_guard = lock.read();
-            let rules = self.read_with(&read_guard);
-
-            // Step 1, 2
-            if index > rules.0.len() {
-                return Err(RulesMutateError::IndexSize);
-            }
-
-            // Computes the parser state at the given index
-            if nested {
-                None
-            } else if index == 0 {
-                Some(State::Start)
-            } else {
-                rules.0.get(index - 1).map(CssRule::rule_state)
-            }
-        };
-
-        // Step 3, 4
-        // XXXManishearth should we also store the namespace map?
-        let (new_rule, new_state) =
-            try!(CssRule::parse(&rule, parent_stylesheet, state, loader));
-
-        {
-            let mut write_guard = lock.write();
-            let mut rules = self.write_with(&mut write_guard);
-            // Step 5
-            // Computes the maximum allowed parser state at a given index.
-            let rev_state = rules.0.get(index).map_or(State::Body, CssRule::rule_state);
-            if new_state > rev_state {
-                // We inserted a rule too early, e.g. inserting
-                // a regular style rule before @namespace rules
-                return Err(RulesMutateError::HierarchyRequest);
-            }
-
-            // Step 6
-            if let CssRule::Namespace(..) = new_rule {
-                if !rules.only_ns_or_import() {
-                    return Err(RulesMutateError::InvalidState);
-                }
-            }
-
-            rules.0.insert(index, new_rule.clone());
-        }
-
-        Ok(new_rule)
-    }
-}
-
-
-/// The structure servo uses to represent a stylesheet.
-#[derive(Debug)]
-pub struct Stylesheet {
-    /// List of rules in the order they were found (important for
-    /// cascading order)
-    pub rules: Arc<Locked<CssRules>>,
-    /// List of media associated with the Stylesheet.
-    pub media: Arc<Locked<MediaList>>,
-    /// The origin of this stylesheet.
-    pub origin: Origin,
-    /// The url data this stylesheet should use.
-    pub url_data: UrlExtraData,
-    /// The lock used for objects inside this stylesheet
-    pub shared_lock: SharedRwLock,
-    /// The namespaces that apply to this stylesheet.
-    pub namespaces: RwLock<Namespaces>,
-    /// Whether this stylesheet would be dirty when the viewport size changes.
-    pub dirty_on_viewport_size_change: AtomicBool,
-    /// Whether this stylesheet should be disabled.
-    pub disabled: AtomicBool,
-    /// The quirks mode of this stylesheet.
-    pub quirks_mode: QuirksMode,
-}
-
-impl MallocSizeOfWithGuard for Stylesheet {
-    fn malloc_size_of_children(&self, guard: &SharedRwLockReadGuard,
-                               malloc_size_of: MallocSizeOfFn) -> usize {
-        // Measurement of other fields may be added later.
-        self.rules.read_with(guard).malloc_size_of_children(guard, malloc_size_of)
-    }
-}
-
-/// This structure holds the user-agent and user stylesheets.
-pub struct UserAgentStylesheets {
-    /// The lock used for user-agent stylesheets.
-    pub shared_lock: SharedRwLock,
-    /// The user or user agent stylesheets.
-    pub user_or_user_agent_stylesheets: Vec<Stylesheet>,
-    /// The quirks mode stylesheet.
-    pub quirks_mode_stylesheet: Stylesheet,
-}
-
-
-/// A CSS rule.
-///
-/// TODO(emilio): Lots of spec links should be around.
-#[derive(Debug, Clone)]
-#[allow(missing_docs)]
-pub enum CssRule {
-    // No Charset here, CSSCharsetRule has been removed from CSSOM
-    // https://drafts.csswg.org/cssom/#changes-from-5-december-2013
-
-    Namespace(Arc<Locked<NamespaceRule>>),
-    Import(Arc<Locked<ImportRule>>),
-    Style(Arc<Locked<StyleRule>>),
-    Media(Arc<Locked<MediaRule>>),
-    FontFace(Arc<Locked<FontFaceRule>>),
-    CounterStyle(Arc<Locked<CounterStyleRule>>),
-    Viewport(Arc<Locked<ViewportRule>>),
-    Keyframes(Arc<Locked<KeyframesRule>>),
-    Supports(Arc<Locked<SupportsRule>>),
-    Page(Arc<Locked<PageRule>>),
-    Document(Arc<Locked<DocumentRule>>),
-}
-
-impl MallocSizeOfWithGuard for CssRule {
-    fn malloc_size_of_children(&self, guard: &SharedRwLockReadGuard,
-                               malloc_size_of: MallocSizeOfFn) -> usize {
-        match *self {
-            CssRule::Style(ref lock) => {
-                lock.read_with(guard).malloc_size_of_children(guard, malloc_size_of)
-            },
-            // Measurement of these fields may be added later.
-            CssRule::Import(_) => 0,
-            CssRule::Media(_) => 0,
-            CssRule::FontFace(_) => 0,
-            CssRule::CounterStyle(_) => 0,
-            CssRule::Keyframes(_) => 0,
-            CssRule::Namespace(_) => 0,
-            CssRule::Viewport(_) => 0,
-            CssRule::Supports(_) => 0,
-            CssRule::Page(_) => 0,
-            CssRule::Document(_)  => 0,
-        }
-    }
-}
-
-#[allow(missing_docs)]
-#[derive(PartialEq, Eq, Copy, Clone)]
-pub enum CssRuleType {
-    // https://drafts.csswg.org/cssom/#the-cssrule-interface
-    Style               = 1,
-    Charset             = 2,
-    Import              = 3,
-    Media               = 4,
-    FontFace            = 5,
-    Page                = 6,
-    // https://drafts.csswg.org/css-animations-1/#interface-cssrule-idl
-    Keyframes           = 7,
-    Keyframe            = 8,
-    // https://drafts.csswg.org/cssom/#the-cssrule-interface
-    Margin              = 9,
-    Namespace           = 10,
-    // https://drafts.csswg.org/css-counter-styles-3/#extentions-to-cssrule-interface
-    CounterStyle        = 11,
-    // https://drafts.csswg.org/css-conditional-3/#extentions-to-cssrule-interface
-    Supports            = 12,
-    // 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,
-}
-
-#[allow(missing_docs)]
-pub enum SingleRuleParseError {
-    Syntax,
-    Hierarchy,
-}
-
-impl CssRule {
-    #[allow(missing_docs)]
-    pub fn rule_type(&self) -> CssRuleType {
-        match *self {
-            CssRule::Style(_) => CssRuleType::Style,
-            CssRule::Import(_) => CssRuleType::Import,
-            CssRule::Media(_) => CssRuleType::Media,
-            CssRule::FontFace(_) => CssRuleType::FontFace,
-            CssRule::CounterStyle(_) => CssRuleType::CounterStyle,
-            CssRule::Keyframes(_) => CssRuleType::Keyframes,
-            CssRule::Namespace(_) => CssRuleType::Namespace,
-            CssRule::Viewport(_) => CssRuleType::Viewport,
-            CssRule::Supports(_) => CssRuleType::Supports,
-            CssRule::Page(_) => CssRuleType::Page,
-            CssRule::Document(_)  => CssRuleType::Document,
-        }
-    }
-
-    fn rule_state(&self) -> State {
-        match *self {
-            // CssRule::Charset(..) => State::Start,
-            CssRule::Import(..) => State::Imports,
-            CssRule::Namespace(..) => State::Namespaces,
-            _ => State::Body,
-        }
-    }
-
-    // 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> {
-        let error_reporter = NullReporter;
-        let context = ParserContext::new(
-            parent_stylesheet.origin,
-            &parent_stylesheet.url_data,
-            &error_reporter,
-            None,
-            PARSING_MODE_DEFAULT,
-            parent_stylesheet.quirks_mode
-        );
-
-        let mut input = Parser::new(css);
-
-        let mut guard = parent_stylesheet.namespaces.write();
-
-        // nested rules are in the body state
-        let state = state.unwrap_or(State::Body);
-        let mut rule_parser = TopLevelRuleParser {
-            stylesheet_origin: parent_stylesheet.origin,
-            context: context,
-            shared_lock: &parent_stylesheet.shared_lock,
-            loader: loader,
-            state: state,
-            namespaces: Some(&mut *guard),
-        };
-        match parse_one_rule(&mut input, &mut rule_parser) {
-            Ok(result) => Ok((result, rule_parser.state)),
-            Err(_) => {
-                if let State::Invalid = rule_parser.state {
-                    Err(SingleRuleParseError::Hierarchy)
-                } else {
-                    Err(SingleRuleParseError::Syntax)
-                }
-            }
-        }
-    }
-
-    /// Deep clones this CssRule.
-    fn deep_clone_with_lock(
-        &self,
-        lock: &SharedRwLock
-    ) -> CssRule {
-        let guard = lock.read();
-        match *self {
-            CssRule::Namespace(ref arc) => {
-                let rule = arc.read_with(&guard);
-                CssRule::Namespace(Arc::new(lock.wrap(rule.clone())))
-            },
-            CssRule::Import(ref arc) => {
-                let rule = arc.read_with(&guard);
-                CssRule::Import(Arc::new(lock.wrap(rule.clone())))
-            },
-            CssRule::Style(ref arc) => {
-                let rule = arc.read_with(&guard);
-                CssRule::Style(Arc::new(
-                    lock.wrap(rule.deep_clone_with_lock(lock))))
-            },
-            CssRule::Media(ref arc) => {
-                let rule = arc.read_with(&guard);
-                CssRule::Media(Arc::new(
-                    lock.wrap(rule.deep_clone_with_lock(lock))))
-            },
-            CssRule::FontFace(ref arc) => {
-                let rule = arc.read_with(&guard);
-                CssRule::FontFace(Arc::new(lock.wrap(rule.clone())))
-            },
-            CssRule::CounterStyle(ref arc) => {
-                let rule = arc.read_with(&guard);
-                CssRule::CounterStyle(Arc::new(lock.wrap(rule.clone())))
-            },
-            CssRule::Viewport(ref arc) => {
-                let rule = arc.read_with(&guard);
-                CssRule::Viewport(Arc::new(lock.wrap(rule.clone())))
-            },
-            CssRule::Keyframes(ref arc) => {
-                let rule = arc.read_with(&guard);
-                CssRule::Keyframes(Arc::new(
-                    lock.wrap(rule.deep_clone_with_lock(lock))))
-            },
-            CssRule::Supports(ref arc) => {
-                let rule = arc.read_with(&guard);
-                CssRule::Supports(Arc::new(
-                    lock.wrap(rule.deep_clone_with_lock(lock))))
-            },
-            CssRule::Page(ref arc) => {
-                let rule = arc.read_with(&guard);
-                CssRule::Page(Arc::new(
-                    lock.wrap(rule.deep_clone_with_lock(lock))))
-            },
-            CssRule::Document(ref arc) => {
-                let rule = arc.read_with(&guard);
-                CssRule::Document(Arc::new(
-                    lock.wrap(rule.deep_clone_with_lock(lock))))
-            },
-        }
-    }
-}
-
-impl ToCssWithGuard for CssRule {
-    // https://drafts.csswg.org/cssom/#serialize-a-css-rule
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
-        match *self {
-            CssRule::Namespace(ref lock) => lock.read_with(guard).to_css(guard, dest),
-            CssRule::Import(ref lock) => lock.read_with(guard).to_css(guard, dest),
-            CssRule::Style(ref lock) => lock.read_with(guard).to_css(guard, dest),
-            CssRule::FontFace(ref lock) => lock.read_with(guard).to_css(guard, dest),
-            CssRule::CounterStyle(ref lock) => lock.read_with(guard).to_css(guard, dest),
-            CssRule::Viewport(ref lock) => lock.read_with(guard).to_css(guard, dest),
-            CssRule::Keyframes(ref lock) => lock.read_with(guard).to_css(guard, dest),
-            CssRule::Media(ref lock) => lock.read_with(guard).to_css(guard, dest),
-            CssRule::Supports(ref lock) => lock.read_with(guard).to_css(guard, dest),
-            CssRule::Page(ref lock) => lock.read_with(guard).to_css(guard, dest),
-            CssRule::Document(ref lock) => lock.read_with(guard).to_css(guard, dest),
-        }
-    }
-}
-
-/// Calculates the location of a rule's source given an offset.
-fn get_location_with_offset(location: SourceLocation, offset: u64)
-    -> SourceLocation {
-    SourceLocation {
-        line: location.line + offset as usize - 1,
-        column: location.column,
-    }
-}
-
-#[derive(Clone, Debug, PartialEq)]
-#[allow(missing_docs)]
-pub struct NamespaceRule {
-    /// `None` for the default Namespace
-    pub prefix: Option<Prefix>,
-    pub url: Namespace,
-    pub source_location: SourceLocation,
-}
-
-impl ToCssWithGuard for NamespaceRule {
-    // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSNamespaceRule
-    fn to_css<W>(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
-        try!(dest.write_str("@namespace "));
-        if let Some(ref prefix) = self.prefix {
-            try!(dest.write_str(&*prefix.to_string()));
-            try!(dest.write_str(" "));
-        }
-        try!(dest.write_str("url(\""));
-        try!(dest.write_str(&*self.url.to_string()));
-        dest.write_str("\");")
-    }
-}
-
-
-/// The [`@import`][import] at-rule.
-///
-/// [import]: https://drafts.csswg.org/css-cascade-3/#at-import
-#[derive(Debug)]
-pub struct ImportRule {
-    /// The `<url>` this `@import` rule is loading.
-    pub url: SpecifiedUrl,
-
-    /// The stylesheet is always present.
-    ///
-    /// It contains an empty list of rules and namespace set that is updated
-    /// when it loads.
-    pub stylesheet: Arc<Stylesheet>,
-
-    /// The line and column of the rule's source code.
-    pub source_location: SourceLocation,
-}
-
-impl Clone for ImportRule {
-    fn clone(&self) -> ImportRule {
-        let stylesheet: &Stylesheet = self.stylesheet.borrow();
-        ImportRule {
-            url: self.url.clone(),
-            stylesheet: Arc::new(stylesheet.clone()),
-            source_location: self.source_location.clone(),
-        }
-    }
-}
-
-impl ToCssWithGuard for ImportRule {
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
-        try!(dest.write_str("@import "));
-        try!(self.url.to_css(dest));
-        let media = self.stylesheet.media.read_with(guard);
-        if !media.is_empty() {
-            try!(dest.write_str(" "));
-            try!(media.to_css(dest));
-        }
-        dest.write_str(";")
-    }
-}
-
-/// A [`@keyframes`][keyframes] rule.
-///
-/// [keyframes]: https://drafts.csswg.org/css-animations/#keyframes
-#[derive(Debug)]
-pub struct KeyframesRule {
-    /// The name of the current animation.
-    pub name: KeyframesName,
-    /// The keyframes specified for this CSS rule.
-    pub keyframes: Vec<Arc<Locked<Keyframe>>>,
-    /// Vendor prefix type the @keyframes has.
-    pub vendor_prefix: Option<VendorPrefix>,
-    /// The line and column of the rule's source code.
-    pub source_location: SourceLocation,
-}
-
-impl ToCssWithGuard for KeyframesRule {
-    // Serialization of KeyframesRule is not specced.
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
-        try!(dest.write_str("@keyframes "));
-        try!(self.name.to_css(dest));
-        try!(dest.write_str(" {"));
-        let iter = self.keyframes.iter();
-        for lock in iter {
-            try!(dest.write_str("\n"));
-            let keyframe = lock.read_with(&guard);
-            try!(keyframe.to_css(guard, dest));
-        }
-        dest.write_str("\n}")
-    }
-}
-
-impl KeyframesRule {
-    /// Returns the index of the last keyframe that matches the given selector.
-    /// If the selector is not valid, or no keyframe is found, returns None.
-    ///
-    /// Related spec:
-    /// https://drafts.csswg.org/css-animations-1/#interface-csskeyframesrule-findrule
-    pub fn find_rule(&self, guard: &SharedRwLockReadGuard, selector: &str) -> Option<usize> {
-        if let Ok(selector) = Parser::new(selector).parse_entirely(KeyframeSelector::parse) {
-            for (i, keyframe) in self.keyframes.iter().enumerate().rev() {
-                if keyframe.read_with(guard).selector == selector {
-                    return Some(i);
-                }
-            }
-        }
-        None
-    }
-}
-
-impl KeyframesRule {
-    /// Deep clones this KeyframesRule.
-    fn deep_clone_with_lock(&self,
-                            lock: &SharedRwLock) -> KeyframesRule {
-        let guard = lock.read();
-        KeyframesRule {
-            name: self.name.clone(),
-            keyframes: self.keyframes.iter()
-                .map(|ref x| Arc::new(lock.wrap(
-                    x.read_with(&guard).deep_clone_with_lock(lock))))
-                .collect(),
-            vendor_prefix: self.vendor_prefix.clone(),
-            source_location: self.source_location.clone(),
-        }
-    }
-}
-
-#[allow(missing_docs)]
-#[derive(Debug)]
-pub struct MediaRule {
-    pub media_queries: Arc<Locked<MediaList>>,
-    pub rules: Arc<Locked<CssRules>>,
-    pub source_location: SourceLocation,
-}
-
-impl ToCssWithGuard for MediaRule {
-    // Serialization of MediaRule is not specced.
-    // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSMediaRule
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
-        try!(dest.write_str("@media "));
-        try!(self.media_queries.read_with(guard).to_css(dest));
-        try!(dest.write_str(" {"));
-        for rule in self.rules.read_with(guard).0.iter() {
-            try!(dest.write_str(" "));
-            try!(rule.to_css(guard, dest));
-        }
-        dest.write_str(" }")
-    }
-}
-
-impl MediaRule {
-    /// Deep clones this MediaRule.
-    fn deep_clone_with_lock(&self,
-                            lock: &SharedRwLock) -> MediaRule {
-        let guard = lock.read();
-        let media_queries = self.media_queries.read_with(&guard);
-        let rules = self.rules.read_with(&guard);
-        MediaRule {
-            media_queries: Arc::new(lock.wrap(media_queries.clone())),
-            rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock))),
-            source_location: self.source_location.clone(),
-        }
-    }
-}
-
-
-#[derive(Debug)]
-/// An @supports rule
-pub struct SupportsRule {
-    /// The parsed condition
-    pub condition: SupportsCondition,
-    /// Child rules
-    pub rules: Arc<Locked<CssRules>>,
-    /// The result of evaluating the condition
-    pub enabled: bool,
-    /// The line and column of the rule's source code.
-    pub source_location: SourceLocation,
-}
-
-impl ToCssWithGuard for SupportsRule {
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
-        try!(dest.write_str("@supports "));
-        try!(self.condition.to_css(dest));
-        try!(dest.write_str(" {"));
-        for rule in self.rules.read_with(guard).0.iter() {
-            try!(dest.write_str(" "));
-            try!(rule.to_css(guard, dest));
-        }
-        dest.write_str(" }")
-    }
-}
-
-impl SupportsRule {
-    fn deep_clone_with_lock(&self,
-                            lock: &SharedRwLock) -> SupportsRule {
-        let guard = lock.read();
-        let rules = self.rules.read_with(&guard);
-        SupportsRule {
-            condition: self.condition.clone(),
-            rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock))),
-            enabled: self.enabled,
-            source_location: self.source_location.clone(),
-        }
-    }
-}
-
-/// A [`@page`][page] rule.  This implements only a limited subset of the CSS 2.2 syntax.  In this
-/// subset, [page selectors][page-selectors] are not implemented.
-///
-/// [page]: https://drafts.csswg.org/css2/page.html#page-box
-/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors
-#[allow(missing_docs)]
-#[derive(Debug)]
-pub struct PageRule {
-    pub block: Arc<Locked<PropertyDeclarationBlock>>,
-    pub source_location: SourceLocation,
-}
-
-impl ToCssWithGuard for PageRule {
-    // Serialization of PageRule is not specced, adapted from steps for StyleRule.
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
-        dest.write_str("@page { ")?;
-        let declaration_block = self.block.read_with(guard);
-        declaration_block.to_css(dest)?;
-        if declaration_block.declarations().len() > 0 {
-            write!(dest, " ")?;
-        }
-        dest.write_str("}")
-    }
-}
-
-impl PageRule {
-    fn deep_clone_with_lock(&self,
-                            lock: &SharedRwLock) -> PageRule {
-        let guard = lock.read();
-        PageRule {
-            block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())),
-            source_location: self.source_location.clone(),
-        }
-    }
-}
-
-#[allow(missing_docs)]
-#[derive(Debug)]
-pub struct StyleRule {
-    pub selectors: SelectorList<SelectorImpl>,
-    pub block: Arc<Locked<PropertyDeclarationBlock>>,
-    pub source_location: SourceLocation,
-}
-
-impl MallocSizeOfWithGuard for StyleRule {
-    fn malloc_size_of_children(&self, guard: &SharedRwLockReadGuard,
-                               malloc_size_of: MallocSizeOfFn) -> usize {
-        // Measurement of other fields may be added later.
-        self.block.read_with(guard).malloc_size_of_children(malloc_size_of)
-    }
-}
-
-impl ToCssWithGuard for StyleRule {
-    // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSStyleRule
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
-        // Step 1
-        try!(self.selectors.to_css(dest));
-        // Step 2
-        try!(dest.write_str(" { "));
-        // Step 3
-        let declaration_block = self.block.read_with(guard);
-        try!(declaration_block.to_css(dest));
-        // Step 4
-        if declaration_block.declarations().len() > 0 {
-            try!(write!(dest, " "));
-        }
-        // Step 5
-        try!(dest.write_str("}"));
-        Ok(())
-    }
-}
-
-impl StyleRule {
-    /// Deep clones this StyleRule.
-    fn deep_clone_with_lock(&self,
-                            lock: &SharedRwLock) -> StyleRule {
-        let guard = lock.read();
-        StyleRule {
-            selectors: self.selectors.clone(),
-            block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())),
-            source_location: self.source_location.clone(),
-        }
-    }
-}
-
-/// A @font-face rule
-#[cfg(feature = "servo")]
-pub type FontFaceRule = FontFaceRuleData;
-
-/// A @counter-style rule
-#[cfg(feature = "servo")]
-pub type CounterStyleRule = CounterStyleRuleData;
-
-#[derive(Debug)]
-/// A @-moz-document rule
-pub struct DocumentRule {
-    /// The parsed condition
-    pub condition: DocumentCondition,
-    /// Child rules
-    pub rules: Arc<Locked<CssRules>>,
-    /// The line and column of the rule's source code.
-    pub source_location: SourceLocation,
-}
-
-impl ToCssWithGuard for DocumentRule {
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
-        try!(dest.write_str("@-moz-document "));
-        try!(self.condition.to_css(dest));
-        try!(dest.write_str(" {"));
-        for rule in self.rules.read_with(guard).0.iter() {
-            try!(dest.write_str(" "));
-            try!(rule.to_css(guard, dest));
-        }
-        dest.write_str(" }")
-    }
-}
-
-impl DocumentRule {
-    /// Deep clones this DocumentRule.
-    fn deep_clone_with_lock(&self,
-                            lock: &SharedRwLock) -> DocumentRule {
-        let guard = lock.read();
-        let rules = self.rules.read_with(&guard);
-        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 {
-    fn process_import(
-        guard: &SharedRwLockReadGuard,
-        device: &Device,
-        quirks_mode: QuirksMode,
-        rule: &ImportRule)
-        -> bool
-    {
-        rule.stylesheet.media.read_with(guard).evaluate(device, quirks_mode)
-    }
-
-    fn process_media(
-        guard: &SharedRwLockReadGuard,
-        device: &Device,
-        quirks_mode: QuirksMode,
-        rule: &MediaRule)
-        -> bool
-    {
-        rule.media_queries.read_with(guard).evaluate(device, quirks_mode)
-    }
-
-    fn process_document(
-        _: &SharedRwLockReadGuard,
-        device: &Device,
-        _: QuirksMode,
-        rule: &DocumentRule)
-        -> bool
-    {
-        rule.condition.evaluate(device)
-    }
-
-    fn process_supports(
-        _: &SharedRwLockReadGuard,
-        _: &Device,
-        _: QuirksMode,
-        rule: &SupportsRule)
-        -> bool
-    {
-        rule.enabled
-    }
-}
-
-/// A filter that processes all the rules in a rule list.
-pub struct AllRules;
-
-impl NestedRuleIterationCondition for AllRules {
-    fn process_import(
-        _: &SharedRwLockReadGuard,
-        _: &Device,
-        _: QuirksMode,
-        _: &ImportRule)
-        -> bool
-    {
-        true
-    }
-
-    fn process_media(
-        _: &SharedRwLockReadGuard,
-        _: &Device,
-        _: QuirksMode,
-        _: &MediaRule)
-        -> bool
-    {
-        true
-    }
-
-    fn process_document(
-        _: &SharedRwLockReadGuard,
-        _: &Device,
-        _: QuirksMode,
-        _: &DocumentRule)
-        -> bool
-    {
-        true
-    }
-
-    fn process_supports(
-        _: &SharedRwLockReadGuard,
-        _: &Device,
-        _: QuirksMode,
-        _: &SupportsRule)
-        -> bool
-    {
-        true
-    }
-}
-
-/// 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,
-        }
-    }
-
-    /// Skips all the remaining children of the last nested rule processed.
-    pub fn skip_children(&mut self) {
-        self.stack.pop();
-    }
-}
-
-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> {
-        let mut nested_iter_finished = false;
-        while !self.stack.is_empty() {
-            if nested_iter_finished {
-                self.stack.pop();
-                nested_iter_finished = false;
-                continue;
-            }
-
-            let rule;
-            let sub_iter;
-            {
-                let mut nested_iter = self.stack.last_mut().unwrap();
-                rule = match nested_iter.next() {
-                    Some(r) => r,
-                    None => {
-                        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) {
-                            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) {
-                            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) {
-                            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) {
-                            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,
-                };
-            }
-
-            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) {
-        let namespaces = RwLock::new(Namespaces::default());
-        // FIXME: we really should update existing.url_data with the given url_data,
-        // otherwise newly inserted rule may not have the right base url.
-        let (rules, dirty_on_viewport_size_change) =
-            Stylesheet::parse_rules(
-                css,
-                url_data,
-                existing.origin,
-                &mut *namespaces.write(),
-                &existing.shared_lock,
-                stylesheet_loader,
-                error_reporter,
-                existing.quirks_mode,
-                line_number_offset
-            );
-
-        mem::swap(&mut *existing.namespaces.write(), &mut *namespaces.write());
-        existing.dirty_on_viewport_size_change
-            .store(dirty_on_viewport_size_change, Ordering::Release);
-
-        // Acquire the lock *after* parsing, to minimize the exclusive section.
-        let mut guard = existing.shared_lock.write();
-        *existing.rules.write_with(&mut guard) = CssRules(rules);
-    }
-
-    fn parse_rules(
-        css: &str,
-        url_data: &UrlExtraData,
-        origin: Origin,
-        namespaces: &mut Namespaces,
-        shared_lock: &SharedRwLock,
-        stylesheet_loader: Option<&StylesheetLoader>,
-        error_reporter: &ParseErrorReporter,
-        quirks_mode: QuirksMode,
-        line_number_offset: u64
-    ) -> (Vec<CssRule>, bool) {
-        let mut rules = Vec::new();
-        let mut input = Parser::new(css);
-
-        let context =
-            ParserContext::new_with_line_number_offset(
-                origin,
-                url_data,
-                error_reporter,
-                line_number_offset,
-                PARSING_MODE_DEFAULT,
-                quirks_mode
-            );
-
-        let rule_parser = TopLevelRuleParser {
-            stylesheet_origin: origin,
-            shared_lock: shared_lock,
-            loader: stylesheet_loader,
-            context: context,
-            state: State::Start,
-            namespaces: Some(namespaces),
-        };
-
-        input.look_for_viewport_percentages();
-
-        {
-            let mut iter =
-                RuleListParser::new_for_stylesheet(&mut input, rule_parser);
-
-            while let Some(result) = iter.next() {
-                match result {
-                    Ok(rule) => rules.push(rule),
-                    Err(range) => {
-                        let pos = range.start;
-                        let message = format!("Invalid rule: '{}'", iter.input.slice(range));
-                        log_css_error(iter.input, pos, &*message, &iter.parser.context);
-                    }
-                }
-            }
-        }
-
-        (rules, input.seen_viewport_percentages())
-    }
-
-    /// Creates an empty stylesheet and parses it with a given base url, origin
-    /// and media.
-    ///
-    /// Effectively creates a new stylesheet and forwards the hard work to
-    /// `Stylesheet::update_from_str`.
-    pub fn from_str(css: &str,
-                    url_data: UrlExtraData,
-                    origin: Origin,
-                    media: Arc<Locked<MediaList>>,
-                    shared_lock: SharedRwLock,
-                    stylesheet_loader: Option<&StylesheetLoader>,
-                    error_reporter: &ParseErrorReporter,
-                    quirks_mode: QuirksMode,
-                    line_number_offset: u64)
-                    -> Stylesheet {
-        let namespaces = RwLock::new(Namespaces::default());
-        let (rules, dirty_on_viewport_size_change) = Stylesheet::parse_rules(
-            css,
-            &url_data,
-            origin,
-            &mut *namespaces.write(),
-            &shared_lock,
-            stylesheet_loader,
-            error_reporter,
-            quirks_mode,
-            line_number_offset,
-        );
-
-        Stylesheet {
-            origin: origin,
-            url_data: url_data,
-            namespaces: namespaces,
-            rules: CssRules::new(rules, &shared_lock),
-            media: media,
-            shared_lock: shared_lock,
-            dirty_on_viewport_size_change: AtomicBool::new(dirty_on_viewport_size_change),
-            disabled: AtomicBool::new(false),
-            quirks_mode: quirks_mode,
-        }
-    }
-
-    /// Whether this stylesheet can be dirty on viewport size change.
-    pub fn dirty_on_viewport_size_change(&self) -> bool {
-        self.dirty_on_viewport_size_change.load(Ordering::SeqCst)
-    }
-
-    /// When CSSOM inserts a rule or declaration into this stylesheet, it needs to call this method
-    /// with the return value of `cssparser::Parser::seen_viewport_percentages`.
-    ///
-    /// FIXME: actually make these calls
-    ///
-    /// Note: when *removing* a rule or declaration that contains a viewport percentage,
-    /// to keep the flag accurate we’d need to iterator through the rest of the stylesheet to
-    /// check for *other* such values.
-    ///
-    /// Instead, we conservatively assume there might be some.
-    /// Restyling will some some more work than necessary, but give correct results.
-    pub fn inserted_has_viewport_percentages(&self, has_viewport_percentages: bool) {
-        self.dirty_on_viewport_size_change.fetch_or(has_viewport_percentages, Ordering::SeqCst);
-    }
-
-    /// Returns whether the style-sheet applies for the current device depending
-    /// on the associated MediaList.
-    ///
-    /// 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`.
-    #[inline]
-    pub fn effective_rules<'a, 'b>(
-        &'a self,
-        device: &'a Device,
-        guard: &'a SharedRwLockReadGuard<'b>)
-        -> EffectiveRulesIterator<'a, 'b>
-    {
-        self.iter_rules::<'a, 'b, EffectiveRules>(device, guard)
-    }
-
-    /// Return an iterator using the condition `C`.
-    #[inline]
-    pub fn iter_rules<'a, 'b, C>(
-        &'a self,
-        device: &'a Device,
-        guard: &'a SharedRwLockReadGuard<'b>)
-        -> RulesIterator<'a, 'b, C>
-        where C: NestedRuleIterationCondition,
-    {
-        RulesIterator::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)
-    }
-
-    /// Records that the stylesheet has been explicitly disabled through the
-    /// CSSOM.
-    ///
-    /// Returns whether the the call resulted in a change in disabled state.
-    ///
-    /// Disabled stylesheets remain in the document, but their rules are not
-    /// added to the Stylist.
-    pub fn set_disabled(&self, disabled: bool) -> bool {
-        self.disabled.swap(disabled, Ordering::SeqCst) != disabled
-    }
-}
-
-impl Clone for Stylesheet {
-    fn clone(&self) -> Stylesheet {
-        // Create a new lock for our clone.
-        let lock = self.shared_lock.clone();
-        let guard = self.shared_lock.read();
-
-        // Make a deep clone of the rules, using the new lock.
-        let rules = self.rules.read_with(&guard);
-        let cloned_rules = rules.deep_clone_with_lock(&lock);
-
-        // Make a deep clone of the media, using the new lock.
-        let media = self.media.read_with(&guard);
-        let cloned_media = media.clone();
-
-        Stylesheet {
-            rules: Arc::new(lock.wrap(cloned_rules)),
-            media: Arc::new(lock.wrap(cloned_media)),
-            origin: self.origin,
-            url_data: self.url_data.clone(),
-            shared_lock: lock,
-            namespaces: RwLock::new((*self.namespaces.read()).clone()),
-            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,
-        }
-    }
-}
-
-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),
-                {
-                    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),
-    effective_media_rules(Media => MediaRule),
-    effective_font_face_rules(FontFace => FontFaceRule),
-    effective_counter_style_rules(CounterStyle => CounterStyleRule),
-    effective_viewport_rules(Viewport => ViewportRule),
-    effective_keyframes_rules(Keyframes => KeyframesRule),
-    effective_supports_rules(Supports => SupportsRule),
-    effective_page_rules(Page => PageRule),
-    effective_document_rules(Document => DocumentRule),
-}
-
-/// The stylesheet loader is the abstraction used to trigger network requests
-/// for `@import` rules.
-pub trait StylesheetLoader {
-    /// Request a stylesheet after parsing a given `@import` rule.
-    ///
-    /// The called code is responsible to update the `stylesheet` rules field
-    /// when the sheet is done loading.
-    ///
-    /// The convoluted signature allows impls to look at MediaList and ImportRule
-    /// before they’re locked, while keeping the trait object-safe.
-    fn request_stylesheet(
-        &self,
-        media: Arc<Locked<MediaList>>,
-        make_import: &mut FnMut(Arc<Locked<MediaList>>) -> ImportRule,
-        make_arc: &mut FnMut(ImportRule) -> Arc<Locked<ImportRule>>,
-    ) -> Arc<Locked<ImportRule>>;
-}
-
-struct NoOpLoader;
-
-impl StylesheetLoader for NoOpLoader {
-    fn request_stylesheet(
-        &self,
-        media: Arc<Locked<MediaList>>,
-        make_import: &mut FnMut(Arc<Locked<MediaList>>) -> ImportRule,
-        make_arc: &mut FnMut(ImportRule) -> Arc<Locked<ImportRule>>,
-    ) -> Arc<Locked<ImportRule>> {
-        make_arc(make_import(media))
-    }
-}
-
-
-struct TopLevelRuleParser<'a> {
-    stylesheet_origin: Origin,
-    shared_lock: &'a SharedRwLock,
-    loader: Option<&'a StylesheetLoader>,
-    context: ParserContext<'a>,
-    state: State,
-    namespaces: Option<&'a mut Namespaces>,
-}
-
-impl<'b> TopLevelRuleParser<'b> {
-    fn nested<'a: 'b>(&'a self) -> NestedRuleParser<'a, 'b> {
-        NestedRuleParser {
-            stylesheet_origin: self.stylesheet_origin,
-            shared_lock: self.shared_lock,
-            context: &self.context,
-        }
-    }
-}
-
-#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
-#[allow(missing_docs)]
-pub enum State {
-    Start = 1,
-    Imports = 2,
-    Namespaces = 3,
-    Body = 4,
-    Invalid = 5,
-}
-
-#[derive(Clone, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-/// Vendor prefix.
-pub enum VendorPrefix {
-    /// -moz prefix.
-    Moz,
-    /// -webkit prefix.
-    WebKit,
-}
-
-enum AtRulePrelude {
-    /// A @font-face rule prelude.
-    FontFace(SourceLocation),
-    /// A @counter-style rule prelude, with its counter style name.
-    CounterStyle(CustomIdent),
-    /// A @media rule prelude, with its media queries.
-    Media(Arc<Locked<MediaList>>, SourceLocation),
-    /// An @supports rule, with its conditional
-    Supports(SupportsCondition, SourceLocation),
-    /// A @viewport rule prelude.
-    Viewport,
-    /// A @keyframes rule, with its animation name and vendor prefix if exists.
-    Keyframes(KeyframesName, Option<VendorPrefix>, SourceLocation),
-    /// A @page rule prelude.
-    Page(SourceLocation),
-    /// A @document rule, with its conditional.
-    Document(DocumentCondition, SourceLocation),
-}
-
-
-#[cfg(feature = "gecko")]
-fn register_namespace(ns: &Namespace) -> Result<i32, ()> {
-    let id = unsafe { ::gecko_bindings::bindings::Gecko_RegisterNamespace(ns.0.as_ptr()) };
-    if id == -1 {
-        Err(())
-    } else {
-        Ok(id)
-    }
-}
-
-#[cfg(feature = "servo")]
-fn register_namespace(_: &Namespace) -> Result<(), ()> {
-    Ok(()) // servo doesn't use namespace ids
-}
-
-impl<'a> AtRuleParser for TopLevelRuleParser<'a> {
-    type Prelude = AtRulePrelude;
-    type AtRule = CssRule;
-
-    fn parse_prelude(
-        &mut self,
-        name: &str,
-        input: &mut Parser
-    ) -> Result<AtRuleType<AtRulePrelude, CssRule>, ()> {
-        let location = get_location_with_offset(input.current_source_location(),
-                                                self.context.line_number_offset);
-        match_ignore_ascii_case! { name,
-            "import" => {
-                if self.state > State::Imports {
-                    self.state = State::Invalid;
-                    return Err(())  // "@import must be before any rule but @charset"
-                }
-
-                self.state = State::Imports;
-                let url_string = input.expect_url_or_string()?;
-                let specified_url = SpecifiedUrl::parse_from_string(url_string, &self.context)?;
-
-                let media = parse_media_query_list(&self.context, input);
-                let media = Arc::new(self.shared_lock.wrap(media));
-
-                let noop_loader = NoOpLoader;
-                let loader = if !specified_url.is_invalid() {
-                    self.loader.expect("Expected a stylesheet loader for @import")
-                } else {
-                    &noop_loader
-                };
-
-                let mut specified_url = Some(specified_url);
-                let arc = loader.request_stylesheet(media, &mut |media| {
-                    ImportRule {
-                        url: specified_url.take().unwrap(),
-                        stylesheet: Arc::new(Stylesheet {
-                            rules: CssRules::new(Vec::new(), self.shared_lock),
-                            media: media,
-                            shared_lock: self.shared_lock.clone(),
-                            origin: self.context.stylesheet_origin,
-                            url_data: self.context.url_data.clone(),
-                            namespaces: RwLock::new(Namespaces::default()),
-                            dirty_on_viewport_size_change: AtomicBool::new(false),
-                            disabled: AtomicBool::new(false),
-                            quirks_mode: self.context.quirks_mode,
-                        }),
-                        source_location: location,
-                    }
-                }, &mut |import_rule| {
-                    Arc::new(self.shared_lock.wrap(import_rule))
-                });
-
-                return Ok(AtRuleType::WithoutBlock(CssRule::Import(arc)))
-            },
-            "namespace" => {
-                if self.state > State::Namespaces {
-                    self.state = State::Invalid;
-                    return Err(())  // "@namespace must be before any rule but @charset and @import"
-                }
-                self.state = State::Namespaces;
-
-                let prefix_result = input.try(|input| input.expect_ident());
-                let url = Namespace::from(try!(input.expect_url_or_string()));
-
-                let id = register_namespace(&url)?;
-
-                let mut namespaces = self.namespaces.as_mut().unwrap();
-
-                let opt_prefix = if let Ok(prefix) = prefix_result {
-                    let prefix = Prefix::from(prefix);
-                    namespaces
-                        .prefixes
-                        .insert(prefix.clone(), (url.clone(), id));
-                    Some(prefix)
-                } else {
-                    namespaces.default = Some((url.clone(), id));
-                    None
-                };
-
-                return Ok(AtRuleType::WithoutBlock(CssRule::Namespace(Arc::new(
-                    self.shared_lock.wrap(NamespaceRule {
-                        prefix: opt_prefix,
-                        url: url,
-                        source_location: location,
-                    })
-                ))))
-            },
-            // @charset is removed by rust-cssparser if it’s the first rule in the stylesheet
-            // anything left is invalid.
-            "charset" => return Err(()), // (insert appropriate error message)
-            _ => {}
-        }
-        // Don't allow starting with an invalid state
-        if self.state > State::Body {
-            self.state = State::Invalid;
-            return Err(());
-        }
-        self.state = State::Body;
-
-        // "Freeze" the namespace map (no more namespace rules can be parsed
-        // after this point), and stick it in the context.
-        if self.namespaces.is_some() {
-            let namespaces = &*self.namespaces.take().unwrap();
-            self.context.namespaces = Some(namespaces);
-        }
-        AtRuleParser::parse_prelude(&mut self.nested(), name, input)
-    }
-
-    #[inline]
-    fn parse_block(&mut self, prelude: AtRulePrelude, input: &mut Parser) -> Result<CssRule, ()> {
-        AtRuleParser::parse_block(&mut self.nested(), prelude, input)
-    }
-}
-
-
-impl<'a> QualifiedRuleParser for TopLevelRuleParser<'a> {
-    type Prelude = SelectorList<SelectorImpl>;
-    type QualifiedRule = CssRule;
-
-    #[inline]
-    fn parse_prelude(&mut self, input: &mut Parser) -> Result<SelectorList<SelectorImpl>, ()> {
-        self.state = State::Body;
-
-        // "Freeze" the namespace map (no more namespace rules can be parsed
-        // after this point), and stick it in the context.
-        if self.namespaces.is_some() {
-            let namespaces = &*self.namespaces.take().unwrap();
-            self.context.namespaces = Some(namespaces);
-        }
-
-        QualifiedRuleParser::parse_prelude(&mut self.nested(), input)
-    }
-
-    #[inline]
-    fn parse_block(
-        &mut self,
-        prelude: SelectorList<SelectorImpl>,
-        input: &mut Parser
-    ) -> Result<CssRule, ()> {
-        QualifiedRuleParser::parse_block(&mut self.nested(), prelude, input)
-    }
-}
-
-#[derive(Clone)]  // shallow, relatively cheap .clone
-struct NestedRuleParser<'a, 'b: 'a> {
-    stylesheet_origin: Origin,
-    shared_lock: &'a SharedRwLock,
-    context: &'a ParserContext<'b>,
-}
-
-impl<'a, 'b> NestedRuleParser<'a, 'b> {
-    fn parse_nested_rules(
-        &mut self,
-        input: &mut Parser,
-        rule_type: CssRuleType
-    ) -> Arc<Locked<CssRules>> {
-        let context = ParserContext::new_with_rule_type(self.context, Some(rule_type));
-
-        let nested_parser = NestedRuleParser {
-            stylesheet_origin: self.stylesheet_origin,
-            shared_lock: self.shared_lock,
-            context: &context,
-        };
-
-        let mut iter = RuleListParser::new_for_nested_rule(input, nested_parser);
-        let mut rules = Vec::new();
-        while let Some(result) = iter.next() {
-            match result {
-                Ok(rule) => rules.push(rule),
-                Err(range) => {
-                    let pos = range.start;
-                    let message = format!("Unsupported rule: '{}'", iter.input.slice(range));
-                    log_css_error(iter.input, pos, &*message, self.context);
-                }
-            }
-        }
-        CssRules::new(rules, self.shared_lock)
-    }
-}
-
-#[cfg(feature = "servo")]
-fn is_viewport_enabled() -> bool {
-    PREFS.get("layout.viewport.enabled").as_boolean().unwrap_or(false)
-}
-
-#[cfg(not(feature = "servo"))]
-fn is_viewport_enabled() -> bool {
-    false // Gecko doesn't support @viewport
-}
-
-impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> {
-    type Prelude = AtRulePrelude;
-    type AtRule = CssRule;
-
-    fn parse_prelude(
-        &mut self,
-        name: &str,
-        input: &mut Parser
-    ) -> Result<AtRuleType<AtRulePrelude, CssRule>, ()> {
-        let location =
-            get_location_with_offset(
-                input.current_source_location(),
-                self.context.line_number_offset
-            );
-
-        match_ignore_ascii_case! { name,
-            "media" => {
-                let media_queries = parse_media_query_list(self.context, input);
-                let arc = Arc::new(self.shared_lock.wrap(media_queries));
-                Ok(AtRuleType::WithBlock(AtRulePrelude::Media(arc, location)))
-            },
-            "supports" => {
-                let cond = SupportsCondition::parse(input)?;
-                Ok(AtRuleType::WithBlock(AtRulePrelude::Supports(cond, location)))
-            },
-            "font-face" => {
-                Ok(AtRuleType::WithBlock(AtRulePrelude::FontFace(location)))
-            },
-            "counter-style" => {
-                if !cfg!(feature = "gecko") {
-                    // Support for this rule is not fully implemented in Servo yet.
-                    return Err(())
-                }
-                let name = parse_counter_style_name(input)?;
-                // ASCII-case-insensitive matches for "decimal" are already lower-cased
-                // by `parse_counter_style_name`, so we can use == here.
-                // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1359323 use atom!("decimal")
-                if name.0 == Atom::from("decimal") {
-                    return Err(())
-                }
-                Ok(AtRuleType::WithBlock(AtRulePrelude::CounterStyle(name)))
-            },
-            "viewport" => {
-                if is_viewport_enabled() {
-                    Ok(AtRuleType::WithBlock(AtRulePrelude::Viewport))
-                } else {
-                    Err(())
-                }
-            },
-            "keyframes" | "-webkit-keyframes" | "-moz-keyframes" => {
-                let prefix = if starts_with_ignore_ascii_case(name, "-webkit-") {
-                    Some(VendorPrefix::WebKit)
-                } else if starts_with_ignore_ascii_case(name, "-moz-") {
-                    Some(VendorPrefix::Moz)
-                } else {
-                    None
-                };
-                if cfg!(feature = "servo") &&
-                   prefix.as_ref().map_or(false, |p| matches!(*p, VendorPrefix::Moz)) {
-                    // Servo should not support @-moz-keyframes.
-                    return Err(())
-                }
-                let name = KeyframesName::parse(self.context, input)?;
-
-                Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(name, prefix, location)))
-            },
-            "page" => {
-                if cfg!(feature = "gecko") {
-                    Ok(AtRuleType::WithBlock(AtRulePrelude::Page(location)))
-                } else {
-                    Err(())
-                }
-            },
-            "-moz-document" => {
-                if cfg!(feature = "gecko") {
-                    let cond = DocumentCondition::parse(self.context, input)?;
-                    Ok(AtRuleType::WithBlock(AtRulePrelude::Document(cond, location)))
-                } else {
-                    Err(())
-                }
-            },
-            _ => Err(())
-        }
-    }
-
-    fn parse_block(&mut self, prelude: AtRulePrelude, input: &mut Parser) -> Result<CssRule, ()> {
-        match prelude {
-            AtRulePrelude::FontFace(location) => {
-                let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::FontFace));
-                Ok(CssRule::FontFace(Arc::new(self.shared_lock.wrap(
-                   parse_font_face_block(&context, input, location).into()))))
-            }
-            AtRulePrelude::CounterStyle(name) => {
-                let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::CounterStyle));
-                Ok(CssRule::CounterStyle(Arc::new(self.shared_lock.wrap(
-                   parse_counter_style_body(name, &context, input)?.into()))))
-            }
-            AtRulePrelude::Media(media_queries, location) => {
-                Ok(CssRule::Media(Arc::new(self.shared_lock.wrap(MediaRule {
-                    media_queries: media_queries,
-                    rules: self.parse_nested_rules(input, CssRuleType::Media),
-                    source_location: location,
-                }))))
-            }
-            AtRulePrelude::Supports(cond, location) => {
-                let enabled = cond.eval(self.context);
-                Ok(CssRule::Supports(Arc::new(self.shared_lock.wrap(SupportsRule {
-                    condition: cond,
-                    rules: self.parse_nested_rules(input, CssRuleType::Supports),
-                    enabled: enabled,
-                    source_location: location,
-                }))))
-            }
-            AtRulePrelude::Viewport => {
-                let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Viewport));
-                Ok(CssRule::Viewport(Arc::new(self.shared_lock.wrap(
-                   try!(ViewportRule::parse(&context, input))))))
-            }
-            AtRulePrelude::Keyframes(name, prefix, location) => {
-                let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Keyframes));
-                Ok(CssRule::Keyframes(Arc::new(self.shared_lock.wrap(KeyframesRule {
-                    name: name,
-                    keyframes: parse_keyframe_list(&context, input, self.shared_lock),
-                    vendor_prefix: prefix,
-                    source_location: location,
-                }))))
-            }
-            AtRulePrelude::Page(location) => {
-                let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Page));
-                let declarations = parse_property_declaration_list(&context, input);
-                Ok(CssRule::Page(Arc::new(self.shared_lock.wrap(PageRule {
-                    block: Arc::new(self.shared_lock.wrap(declarations)),
-                    source_location: location,
-                }))))
-            }
-            AtRulePrelude::Document(cond, location) => {
-                if cfg!(feature = "gecko") {
-                    Ok(CssRule::Document(Arc::new(self.shared_lock.wrap(DocumentRule {
-                        condition: cond,
-                        rules: self.parse_nested_rules(input, CssRuleType::Document),
-                        source_location: location,
-                    }))))
-                } else {
-                    unreachable!()
-                }
-            }
-        }
-    }
-}
-
-impl<'a, 'b> QualifiedRuleParser for NestedRuleParser<'a, 'b> {
-    type Prelude = SelectorList<SelectorImpl>;
-    type QualifiedRule = CssRule;
-
-    fn parse_prelude(&mut self, input: &mut Parser) -> Result<SelectorList<SelectorImpl>, ()> {
-        let selector_parser = SelectorParser {
-            stylesheet_origin: self.stylesheet_origin,
-            namespaces: self.context.namespaces.unwrap(),
-        };
-
-        SelectorList::parse(&selector_parser, input)
-    }
-
-    fn parse_block(
-        &mut self,
-        prelude: SelectorList<SelectorImpl>,
-        input: &mut Parser
-    ) -> Result<CssRule, ()> {
-        let location = get_location_with_offset(input.current_source_location(),
-                                                self.context.line_number_offset);
-        let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Style));
-        let declarations = parse_property_declaration_list(&context, input);
-        Ok(CssRule::Style(Arc::new(self.shared_lock.wrap(StyleRule {
-            selectors: prelude,
-            block: Arc::new(self.shared_lock.wrap(declarations)),
-            source_location: location,
-        }))))
-    }
-}
new file mode 100644
--- /dev/null
+++ b/servo/components/style/stylesheets/counter_style_rule.rs
@@ -0,0 +1,12 @@
+/* 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/. */
+
+// TODO(emilio): unify this, components/style/counter_style.rs, and
+// components/style/gecko/rules.rs
+#![allow(missing_docs)]
+
+#[cfg(feature = "servo")]
+pub use counter_style::CounterStyleRuleData as CounterStyleRule;
+#[cfg(feature = "gecko")]
+pub use gecko::rules::CounterStyleRule;
rename from servo/components/style/document_condition.rs
rename to servo/components/style/stylesheets/document_rule.rs
--- a/servo/components/style/document_condition.rs
+++ b/servo/components/style/stylesheets/document_rule.rs
@@ -1,29 +1,67 @@
 /* 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/. */
 
 //! [@document rules](https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document)
 //! initially in CSS Conditional Rules Module Level 3, @document has been postponed to the level 4.
 //! We implement the prefixed `@-moz-document`.
 
-use cssparser::{Parser, Token, serialize_string};
-#[cfg(feature = "gecko")]
-use gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation;
-#[cfg(feature = "gecko")]
-use gecko_bindings::structs::URLMatchingFunction as GeckoUrlMatchingFunction;
+use cssparser::{Parser, Token, SourceLocation, serialize_string};
 use media_queries::Device;
-#[cfg(feature = "gecko")]
-use nsstring::nsCString;
 use parser::{Parse, ParserContext};
+use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
 use std::fmt;
 use style_traits::ToCss;
+use stylearc::Arc;
+use stylesheets::CssRules;
 use values::specified::url::SpecifiedUrl;
 
+#[derive(Debug)]
+/// A @-moz-document rule
+pub struct DocumentRule {
+    /// The parsed condition
+    pub condition: DocumentCondition,
+    /// Child rules
+    pub rules: Arc<Locked<CssRules>>,
+    /// The line and column of the rule's source code.
+    pub source_location: SourceLocation,
+}
+
+impl ToCssWithGuard for DocumentRule {
+    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
+    where W: fmt::Write {
+        try!(dest.write_str("@-moz-document "));
+        try!(self.condition.to_css(dest));
+        try!(dest.write_str(" {"));
+        for rule in self.rules.read_with(guard).0.iter() {
+            try!(dest.write_str(" "));
+            try!(rule.to_css(guard, dest));
+        }
+        dest.write_str(" }")
+    }
+}
+
+impl DeepCloneWithLock for DocumentRule {
+    /// Deep clones this DocumentRule.
+    fn deep_clone_with_lock(
+        &self,
+        lock: &SharedRwLock,
+        guard: &SharedRwLockReadGuard,
+    ) -> Self {
+        let rules = self.rules.read_with(guard);
+        DocumentRule {
+            condition: self.condition.clone(),
+            rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
+            source_location: self.source_location.clone(),
+        }
+    }
+}
+
 /// A URL matching function for a `@document` rule's condition.
 #[derive(Clone, Debug)]
 pub enum UrlMatchingFunction {
     /// Exact URL matching function. It evaluates to true whenever the
     /// URL of the document being styled is exactly the URL given.
     Url(SpecifiedUrl),
     /// URL prefix matching function. It evaluates to true whenever the
     /// URL of the document being styled has the argument to the
@@ -79,22 +117,27 @@ impl UrlMatchingFunction {
         } else {
             Err(())
         }
     }
 
     #[cfg(feature = "gecko")]
     /// Evaluate a URL matching function.
     pub fn evaluate(&self, device: &Device) -> bool {
+        use gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation;
+        use gecko_bindings::structs::URLMatchingFunction as GeckoUrlMatchingFunction;
+        use nsstring::nsCString;
+
         let func = match *self {
             UrlMatchingFunction::Url(_) => GeckoUrlMatchingFunction::eURL,
             UrlMatchingFunction::UrlPrefix(_) => GeckoUrlMatchingFunction::eURLPrefix,
             UrlMatchingFunction::Domain(_) => GeckoUrlMatchingFunction::eDomain,
             UrlMatchingFunction::RegExp(_) => GeckoUrlMatchingFunction::eRegExp,
         };
+
         let pattern = nsCString::from(match *self {
             UrlMatchingFunction::Url(ref url) => url.as_str(),
             UrlMatchingFunction::UrlPrefix(ref pat) |
             UrlMatchingFunction::Domain(ref pat) |
             UrlMatchingFunction::RegExp(ref pat) => pat,
         });
         unsafe {
             Gecko_DocumentRule_UseForPresentation(&*device.pres_context, &*pattern, func)
new file mode 100644
--- /dev/null
+++ b/servo/components/style/stylesheets/font_face_rule.rs
@@ -0,0 +1,12 @@
+/* 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/. */
+
+// TODO(emilio): unify this, components/style/font_face.rs, and
+// components/style/gecko/rules.rs
+#![allow(missing_docs)]
+
+#[cfg(feature = "servo")]
+pub use font_face::FontFaceRuleData as FontFaceRule;
+#[cfg(feature = "gecko")]
+pub use gecko::rules::FontFaceRule;
new file mode 100644
--- /dev/null
+++ b/servo/components/style/stylesheets/import_rule.rs
@@ -0,0 +1,59 @@
+/* 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/. */
+
+//! The [`@import`][import] at-rule.
+//!
+//! [import]: https://drafts.csswg.org/css-cascade-3/#at-import
+
+use cssparser::SourceLocation;
+use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
+use std::fmt;
+use style_traits::ToCss;
+use stylearc::Arc;
+use stylesheets::stylesheet::Stylesheet;
+use values::specified::url::SpecifiedUrl;
+
+/// The [`@import`][import] at-rule.
+///
+/// [import]: https://drafts.csswg.org/css-cascade-3/#at-import
+#[derive(Debug)]
+pub struct ImportRule {
+    /// The `<url>` this `@import` rule is loading.
+    pub url: SpecifiedUrl,
+
+    /// The stylesheet is always present.
+    ///
+    /// It contains an empty list of rules and namespace set that is updated
+    /// when it loads.
+    pub stylesheet: Arc<Stylesheet>,
+
+    /// The line and column of the rule's source code.
+    pub source_location: SourceLocation,
+}
+
+impl Clone for ImportRule {
+    fn clone(&self) -> ImportRule {
+        let stylesheet: &Stylesheet = &*self.stylesheet;
+        ImportRule {
+            url: self.url.clone(),
+            stylesheet: Arc::new(stylesheet.clone()),
+            source_location: self.source_location.clone(),
+        }
+    }
+}
+
+impl ToCssWithGuard for ImportRule {
+    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
+        where W: fmt::Write,
+    {
+        dest.write_str("@import ")?;
+        self.url.to_css(dest)?;
+        let media = self.stylesheet.media.read_with(guard);
+        if !media.is_empty() {
+            dest.write_str(" ")?;
+            media.to_css(dest)?;
+        }
+        dest.write_str(";")
+    }
+}
rename from servo/components/style/keyframes.rs
rename to servo/components/style/stylesheets/keyframes_rule.rs
--- a/servo/components/style/keyframes.rs
+++ b/servo/components/style/stylesheets/keyframes_rule.rs
@@ -1,30 +1,99 @@
 /* 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/. */
 
 //! Keyframes: https://drafts.csswg.org/css-animations/#keyframes
 
-#![deny(missing_docs)]
-
 use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, RuleListParser};
-use cssparser::{DeclarationListParser, DeclarationParser, parse_one_rule};
+use cssparser::{DeclarationListParser, DeclarationParser, parse_one_rule, SourceLocation};
 use error_reporting::NullReporter;
 use parser::{PARSING_MODE_DEFAULT, ParserContext, log_css_error};
 use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock, PropertyId};
 use properties::{PropertyDeclarationId, LonghandId, SourcePropertyDeclaration};
 use properties::LonghandIdSet;
 use properties::animated_properties::TransitionProperty;
 use properties::longhands::transition_timing_function::single_value::SpecifiedValue as SpecifiedTimingFunction;
-use shared_lock::{SharedRwLock, SharedRwLockReadGuard, Locked, ToCssWithGuard};
+use shared_lock::{DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard, Locked, ToCssWithGuard};
 use std::fmt;
 use style_traits::ToCss;
 use stylearc::Arc;
-use stylesheets::{CssRuleType, Stylesheet, VendorPrefix};
+use stylesheets::{CssRuleType, Stylesheet};
+use stylesheets::rule_parser::VendorPrefix;
+use values::KeyframesName;
+
+/// A [`@keyframes`][keyframes] rule.
+///
+/// [keyframes]: https://drafts.csswg.org/css-animations/#keyframes
+#[derive(Debug)]
+pub struct KeyframesRule {
+    /// The name of the current animation.
+    pub name: KeyframesName,
+    /// The keyframes specified for this CSS rule.
+    pub keyframes: Vec<Arc<Locked<Keyframe>>>,
+    /// Vendor prefix type the @keyframes has.
+    pub vendor_prefix: Option<VendorPrefix>,
+    /// The line and column of the rule's source code.
+    pub source_location: SourceLocation,
+}
+
+impl ToCssWithGuard for KeyframesRule {
+    // Serialization of KeyframesRule is not specced.
+    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
+        where W: fmt::Write,
+    {
+        dest.write_str("@keyframes ")?;
+        self.name.to_css(dest)?;
+        dest.write_str(" {")?;
+        let iter = self.keyframes.iter();
+        for lock in iter {
+            dest.write_str("\n")?;
+            let keyframe = lock.read_with(&guard);
+            keyframe.to_css(guard, dest)?;
+        }
+        dest.write_str("\n}")
+    }
+}
+
+impl KeyframesRule {
+    /// Returns the index of the last keyframe that matches the given selector.
+    /// If the selector is not valid, or no keyframe is found, returns None.
+    ///
+    /// Related spec:
+    /// https://drafts.csswg.org/css-animations-1/#interface-csskeyframesrule-findrule
+    pub fn find_rule(&self, guard: &SharedRwLockReadGuard, selector: &str) -> Option<usize> {
+        if let Ok(selector) = Parser::new(selector).parse_entirely(KeyframeSelector::parse) {
+            for (i, keyframe) in self.keyframes.iter().enumerate().rev() {
+                if keyframe.read_with(guard).selector == selector {
+                    return Some(i);
+                }
+            }
+        }
+        None
+    }
+}
+
+impl DeepCloneWithLock for KeyframesRule {
+    fn deep_clone_with_lock(
+        &self,
+        lock: &SharedRwLock,
+        guard: &SharedRwLockReadGuard
+    ) -> Self {
+        KeyframesRule {
+            name: self.name.clone(),
+            keyframes: self.keyframes.iter()
+                .map(|ref x| Arc::new(lock.wrap(
+                    x.read_with(guard).deep_clone_with_lock(lock, guard))))
+                .collect(),
+            vendor_prefix: self.vendor_prefix.clone(),
+            source_location: self.source_location.clone(),
+        }
+    }
+}
 
 /// A number from 0 to 1, indicating the percentage of the animation when this
 /// keyframe should run.
 #[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct KeyframePercentage(pub f32);
 
 impl ::std::cmp::Ord for KeyframePercentage {
@@ -145,24 +214,28 @@ impl Keyframe {
         let mut declarations = SourcePropertyDeclaration::new();
         let mut rule_parser = KeyframeListParser {
             context: &context,
             shared_lock: &parent_stylesheet.shared_lock,
             declarations: &mut declarations,
         };
         parse_one_rule(&mut input, &mut rule_parser)
     }
+}
 
+impl DeepCloneWithLock for Keyframe {
     /// Deep clones this Keyframe.
-    pub fn deep_clone_with_lock(&self,
-                                lock: &SharedRwLock) -> Keyframe {
-        let guard = lock.read();
+    fn deep_clone_with_lock(
+        &self,
+        lock: &SharedRwLock,
+        guard: &SharedRwLockReadGuard
+    ) -> Keyframe {
         Keyframe {
             selector: self.selector.clone(),
-            block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())),
+            block: Arc::new(lock.wrap(self.block.read_with(guard).clone())),
         }
     }
 }
 
 /// A keyframes step value. This can be a synthetised keyframes animation, that
 /// is, one autogenerated from the current computed values, or a list of
 /// declarations to apply.
 ///
new file mode 100644
--- /dev/null
+++ b/servo/components/style/stylesheets/loader.rs
@@ -0,0 +1,43 @@
+/* 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/. */
+
+//! The stylesheet loader is the abstraction used to trigger network requests
+//! for `@import` rules.
+
+use media_queries::MediaList;
+use shared_lock::Locked;
+use stylearc::Arc;
+use stylesheets::ImportRule;
+
+/// The stylesheet loader is the abstraction used to trigger network requests
+/// for `@import` rules.
+pub trait StylesheetLoader {
+    /// Request a stylesheet after parsing a given `@import` rule.
+    ///
+    /// The called code is responsible to update the `stylesheet` rules field
+    /// when the sheet is done loading.
+    ///
+    /// The convoluted signature allows impls to look at MediaList and
+    /// ImportRule before they’re locked, while keeping the trait object-safe.
+    fn request_stylesheet(
+        &self,
+        media: Arc<Locked<MediaList>>,
+        make_import: &mut FnMut(Arc<Locked<MediaList>>) -> ImportRule,
+        make_arc: &mut FnMut(ImportRule) -> Arc<Locked<ImportRule>>,
+    ) -> Arc<Locked<ImportRule>>;
+}
+
+/// A dummy loader that just creates the import rule with the empty stylesheet.
+pub struct NoOpLoader;
+
+impl StylesheetLoader for NoOpLoader {
+    fn request_stylesheet(
+        &self,
+        media: Arc<Locked<MediaList>>,
+        make_import: &mut FnMut(Arc<Locked<MediaList>>) -> ImportRule,
+        make_arc: &mut FnMut(ImportRule) -> Arc<Locked<ImportRule>>,
+    ) -> Arc<Locked<ImportRule>> {
+        make_arc(make_import(media))
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/components/style/stylesheets/media_rule.rs
@@ -0,0 +1,60 @@
+/* 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/. */
+
+//! An [`@media`][media] urle.
+//!
+//! [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
+
+use cssparser::SourceLocation;
+use media_queries::MediaList;
+use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
+use std::fmt;
+use style_traits::ToCss;
+use stylearc::Arc;
+use stylesheets::CssRules;
+
+/// An [`@media`][media] urle.
+///
+/// [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
+#[derive(Debug)]
+pub struct MediaRule {
+    /// The list of media queries used by this media rule.
+    pub media_queries: Arc<Locked<MediaList>>,
+    /// The nested rules to this media rule.
+    pub rules: Arc<Locked<CssRules>>,
+    /// The source position where this media rule was found.
+    pub source_location: SourceLocation,
+}
+
+impl ToCssWithGuard for MediaRule {
+    // Serialization of MediaRule is not specced.
+    // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSMediaRule
+    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
+    where W: fmt::Write {
+        dest.write_str("@media ")?;
+        self.media_queries.read_with(guard).to_css(dest)?;
+        dest.write_str(" {")?;
+        for rule in self.rules.read_with(guard).0.iter() {
+            dest.write_str(" ")?;
+            rule.to_css(guard, dest)?;
+        }
+        dest.write_str(" }")
+    }
+}
+
+impl DeepCloneWithLock for MediaRule {
+    fn deep_clone_with_lock(
+        &self,
+        lock: &SharedRwLock,
+        guard: &SharedRwLockReadGuard
+    ) -> Self {
+        let media_queries = self.media_queries.read_with(guard);
+        let rules = self.rules.read_with(guard);
+        MediaRule {
+            media_queries: Arc::new(lock.wrap(media_queries.clone())),
+            rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
+            source_location: self.source_location.clone(),
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/components/style/stylesheets/memory.rs
@@ -0,0 +1,71 @@
+/* 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/. */
+
+//! Memory reporting for the style system when running inside of Gecko.
+
+use shared_lock::SharedRwLockReadGuard;
+use std::os::raw::c_void;
+
+/// Like gecko_bindings::structs::MallocSizeOf, but without the Option<>
+/// wrapper.
+///
+/// Note that functions of this type should not be called via
+/// do_malloc_size_of(), rather than directly.
+pub type MallocSizeOfFn = unsafe extern "C" fn(ptr: *const c_void) -> usize;
+
+/// Call malloc_size_of on ptr, first checking that the allocation isn't empty.
+pub unsafe fn do_malloc_size_of<T>(malloc_size_of: MallocSizeOfFn, ptr: *const T) -> usize {
+    use std::mem::align_of;
+
+    if ptr as usize <= align_of::<T>() {
+        0
+    } else {
+        malloc_size_of(ptr as *const c_void)
+    }
+}
+
+/// Trait for measuring the size of heap data structures.
+pub trait MallocSizeOf {
+    /// Measure the size of any heap-allocated structures that hang off this
+    /// value, but not the space taken up by the value itself.
+    fn malloc_size_of_children(&self, malloc_size_of: MallocSizeOfFn) -> usize;
+}
+
+/// Like MallocSizeOf, but operates with the global SharedRwLockReadGuard
+/// locked.
+pub trait MallocSizeOfWithGuard {
+    /// Like MallocSizeOf::malloc_size_of_children, but with a |guard| argument.
+    fn malloc_size_of_children(
+        &self,
+        guard: &SharedRwLockReadGuard,
+        malloc_size_of: MallocSizeOfFn
+    ) -> usize;
+}
+
+impl<A: MallocSizeOf, B: MallocSizeOf> MallocSizeOf for (A, B) {
+    fn malloc_size_of_children(&self, malloc_size_of: MallocSizeOfFn) -> usize {
+        self.0.malloc_size_of_children(malloc_size_of) +
+            self.1.malloc_size_of_children(malloc_size_of)
+    }
+}
+
+impl<T: MallocSizeOf> MallocSizeOf for Vec<T> {
+    fn malloc_size_of_children(&self, malloc_size_of: MallocSizeOfFn) -> usize {
+        self.iter().fold(
+            unsafe { do_malloc_size_of(malloc_size_of, self.as_ptr()) },
+            |n, elem| n + elem.malloc_size_of_children(malloc_size_of))
+    }
+}
+
+impl<T: MallocSizeOfWithGuard> MallocSizeOfWithGuard for Vec<T> {
+    fn malloc_size_of_children(
+        &self,
+        guard: &SharedRwLockReadGuard,
+        malloc_size_of: MallocSizeOfFn,
+    ) -> usize {
+        self.iter().fold(
+            unsafe { do_malloc_size_of(malloc_size_of, self.as_ptr()) },
+            |n, elem| n + elem.malloc_size_of_children(guard, malloc_size_of))
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/components/style/stylesheets/mod.rs
@@ -0,0 +1,344 @@
+/* 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/. */
+
+//! Style sheets and their CSS rules.
+
+mod counter_style_rule;
+mod document_rule;
+mod font_face_rule;
+mod import_rule;
+pub mod keyframes_rule;
+mod loader;
+mod media_rule;
+mod memory;
+mod namespace_rule;
+mod page_rule;
+mod rule_list;
+mod rule_parser;
+mod rules_iterator;
+mod style_rule;
+mod stylesheet;
+pub mod supports_rule;
+pub mod viewport_rule;
+
+use cssparser::{parse_one_rule, Parser};
+use error_reporting::NullReporter;
+use parser::{ParserContext, PARSING_MODE_DEFAULT};
+use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
+use std::fmt;
+use stylearc::Arc;
+
+pub use self::counter_style_rule::CounterStyleRule;
+pub use self::document_rule::DocumentRule;
+pub use self::font_face_rule::FontFaceRule;
+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};
+pub use self::namespace_rule::NamespaceRule;
+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, UserAgentStylesheets};
+pub use self::style_rule::StyleRule;
+pub use self::supports_rule::SupportsRule;
+pub use self::viewport_rule::ViewportRule;
+
+/// Extra data that the backend may need to resolve url values.
+#[cfg(not(feature = "gecko"))]
+pub type UrlExtraData = ::servo_url::ServoUrl;
+
+/// Extra data that the backend may need to resolve url values.
+#[cfg(feature = "gecko")]
+pub type UrlExtraData =
+    ::gecko_bindings::sugar::refptr::RefPtr<::gecko_bindings::structs::URLExtraData>;
+
+#[cfg(feature = "gecko")]
+impl UrlExtraData {
+    /// Returns a string for the url.
+    ///
+    /// Unimplemented currently.
+    pub fn as_str(&self) -> &str {
+        // TODO
+        "(stylo: not supported)"
+    }
+}
+
+// XXX We probably need to figure out whether we should mark Eq here.
+// It is currently marked so because properties::UnparsedValue wants Eq.
+#[cfg(feature = "gecko")]
+impl Eq for UrlExtraData {}
+
+/// Each style rule has an origin, which determines where it enters the cascade.
+///
+/// http://dev.w3.org/csswg/css-cascade/#cascading-origins
+#[derive(Clone, PartialEq, Eq, Copy, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub enum Origin {
+    /// http://dev.w3.org/csswg/css-cascade/#cascade-origin-ua
+    UserAgent,
+
+    /// http://dev.w3.org/csswg/css-cascade/#cascade-origin-author
+    Author,
+
+    /// http://dev.w3.org/csswg/css-cascade/#cascade-origin-user
+    User,
+}
+
+/// A CSS rule.
+///
+/// TODO(emilio): Lots of spec links should be around.
+#[derive(Debug, Clone)]
+#[allow(missing_docs)]
+pub enum CssRule {
+    // No Charset here, CSSCharsetRule has been removed from CSSOM
+    // https://drafts.csswg.org/cssom/#changes-from-5-december-2013
+
+    Namespace(Arc<Locked<NamespaceRule>>),
+    Import(Arc<Locked<ImportRule>>),
+    Style(Arc<Locked<StyleRule>>),
+    Media(Arc<Locked<MediaRule>>),
+    FontFace(Arc<Locked<FontFaceRule>>),
+    CounterStyle(Arc<Locked<CounterStyleRule>>),
+    Viewport(Arc<Locked<ViewportRule>>),
+    Keyframes(Arc<Locked<KeyframesRule>>),
+    Supports(Arc<Locked<SupportsRule>>),
+    Page(Arc<Locked<PageRule>>),
+    Document(Arc<Locked<DocumentRule>>),
+}
+
+impl MallocSizeOfWithGuard for CssRule {
+    fn malloc_size_of_children(
+        &self,
+        guard: &SharedRwLockReadGuard,
+        malloc_size_of: MallocSizeOfFn
+    ) -> usize {
+        match *self {
+            CssRule::Style(ref lock) => {
+                lock.read_with(guard).malloc_size_of_children(guard, malloc_size_of)
+            },
+            // Measurement of these fields may be added later.
+            CssRule::Import(_) => 0,
+            CssRule::Media(_) => 0,
+            CssRule::FontFace(_) => 0,
+            CssRule::CounterStyle(_) => 0,
+            CssRule::Keyframes(_) => 0,
+            CssRule::Namespace(_) => 0,
+            CssRule::Viewport(_) => 0,
+            CssRule::Supports(_) => 0,
+            CssRule::Page(_) => 0,
+            CssRule::Document(_)  => 0,
+        }
+    }
+}
+
+#[allow(missing_docs)]
+#[derive(PartialEq, Eq, Copy, Clone)]
+pub enum CssRuleType {
+    // https://drafts.csswg.org/cssom/#the-cssrule-interface
+    Style               = 1,
+    Charset             = 2,
+    Import              = 3,
+    Media               = 4,
+    FontFace            = 5,
+    Page                = 6,
+    // https://drafts.csswg.org/css-animations-1/#interface-cssrule-idl
+    Keyframes           = 7,
+    Keyframe            = 8,
+    // https://drafts.csswg.org/cssom/#the-cssrule-interface
+    Margin              = 9,
+    Namespace           = 10,
+    // https://drafts.csswg.org/css-counter-styles-3/#extentions-to-cssrule-interface
+    CounterStyle        = 11,
+    // https://drafts.csswg.org/css-conditional-3/#extentions-to-cssrule-interface
+    Supports            = 12,
+    // 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,
+}
+
+#[allow(missing_docs)]
+pub enum SingleRuleParseError {
+    Syntax,
+    Hierarchy,
+}
+
+#[allow(missing_docs)]
+pub enum RulesMutateError {
+    Syntax,
+    IndexSize,
+    HierarchyRequest,
+    InvalidState,
+}
+
+impl From<SingleRuleParseError> for RulesMutateError {
+    fn from(other: SingleRuleParseError) -> Self {
+        match other {
+            SingleRuleParseError::Syntax => RulesMutateError::Syntax,
+            SingleRuleParseError::Hierarchy => RulesMutateError::HierarchyRequest,
+        }
+    }
+}
+
+impl CssRule {
+    /// Returns the CSSOM rule type of this rule.
+    pub fn rule_type(&self) -> CssRuleType {
+        match *self {
+            CssRule::Style(_) => CssRuleType::Style,
+            CssRule::Import(_) => CssRuleType::Import,
+            CssRule::Media(_) => CssRuleType::Media,
+            CssRule::FontFace(_) => CssRuleType::FontFace,
+            CssRule::CounterStyle(_) => CssRuleType::CounterStyle,
+            CssRule::Keyframes(_) => CssRuleType::Keyframes,
+            CssRule::Namespace(_) => CssRuleType::Namespace,
+            CssRule::Viewport(_) => CssRuleType::Viewport,
+            CssRule::Supports(_) => CssRuleType::Supports,
+            CssRule::Page(_) => CssRuleType::Page,
+            CssRule::Document(_)  => CssRuleType::Document,
+        }
+    }
+
+    fn rule_state(&self) -> State {
+        match *self {
+            // CssRule::Charset(..) => State::Start,
+            CssRule::Import(..) => State::Imports,
+            CssRule::Namespace(..) => State::Namespaces,
+            _ => State::Body,
+        }
+    }
+
+    /// Parse a CSS rule.
+    ///
+    /// Returns a parsed CSS rule and the final state of the parser.
+    ///
+    /// Input state is None for a nested rule
+    pub fn parse(
+        css: &str,
+        parent_stylesheet: &Stylesheet,
+        state: Option<State>,
+        loader: Option<&StylesheetLoader>
+    ) -> Result<(Self, State), SingleRuleParseError> {
+        let error_reporter = NullReporter;
+        let context = ParserContext::new(
+            parent_stylesheet.origin,
+            &parent_stylesheet.url_data,
+            &error_reporter,
+            None,
+            PARSING_MODE_DEFAULT,
+            parent_stylesheet.quirks_mode
+        );
+
+        let mut input = Parser::new(css);
+
+        let mut guard = parent_stylesheet.namespaces.write();
+
+        // nested rules are in the body state
+        let state = state.unwrap_or(State::Body);
+        let mut rule_parser = TopLevelRuleParser {
+            stylesheet_origin: parent_stylesheet.origin,
+            context: context,
+            shared_lock: &parent_stylesheet.shared_lock,
+            loader: loader,
+            state: state,
+            namespaces: Some(&mut *guard),
+        };
+
+        match parse_one_rule(&mut input, &mut rule_parser) {
+            Ok(result) => Ok((result, rule_parser.state)),
+            Err(_) => {
+                Err(match rule_parser.state {
+                    State::Invalid => SingleRuleParseError::Hierarchy,
+                    _ => SingleRuleParseError::Syntax,
+                })
+            }
+        }
+    }
+}
+
+impl DeepCloneWithLock for CssRule {
+    /// Deep clones this CssRule.
+    fn deep_clone_with_lock(
+        &self,
+        lock: &SharedRwLock,
+        guard: &SharedRwLockReadGuard,
+    ) -> CssRule {
+        match *self {
+            CssRule::Namespace(ref arc) => {
+                let rule = arc.read_with(guard);
+                CssRule::Namespace(Arc::new(lock.wrap(rule.clone())))
+            },
+            CssRule::Import(ref arc) => {
+                let rule = arc.read_with(guard);
+                CssRule::Import(Arc::new(lock.wrap(rule.clone())))
+            },
+            CssRule::Style(ref arc) => {
+                let rule = arc.read_with(guard);
+                CssRule::Style(Arc::new(
+                    lock.wrap(rule.deep_clone_with_lock(lock, guard))))
+            },
+            CssRule::Media(ref arc) => {
+                let rule = arc.read_with(guard);
+                CssRule::Media(Arc::new(
+                    lock.wrap(rule.deep_clone_with_lock(lock, guard))))
+            },
+            CssRule::FontFace(ref arc) => {
+                let rule = arc.read_with(guard);
+                CssRule::FontFace(Arc::new(lock.wrap(rule.clone())))
+            },
+            CssRule::CounterStyle(ref arc) => {
+                let rule = arc.read_with(guard);
+                CssRule::CounterStyle(Arc::new(lock.wrap(rule.clone())))
+            },
+            CssRule::Viewport(ref arc) => {
+                let rule = arc.read_with(guard);
+                CssRule::Viewport(Arc::new(lock.wrap(rule.clone())))
+            },
+            CssRule::Keyframes(ref arc) => {
+                let rule = arc.read_with(guard);
+                CssRule::Keyframes(Arc::new(
+                    lock.wrap(rule.deep_clone_with_lock(lock, guard))))
+            },
+            CssRule::Supports(ref arc) => {
+                let rule = arc.read_with(guard);
+                CssRule::Supports(Arc::new(
+                    lock.wrap(rule.deep_clone_with_lock(lock, guard))))
+            },
+            CssRule::Page(ref arc) => {
+                let rule = arc.read_with(guard);
+                CssRule::Page(Arc::new(
+                    lock.wrap(rule.deep_clone_with_lock(lock, guard))))
+            },
+            CssRule::Document(ref arc) => {
+                let rule = arc.read_with(guard);
+                CssRule::Document(Arc::new(
+                    lock.wrap(rule.deep_clone_with_lock(lock, guard))))
+            },
+        }
+    }
+}
+
+impl ToCssWithGuard for CssRule {
+    // https://drafts.csswg.org/cssom/#serialize-a-css-rule
+    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
+    where W: fmt::Write {
+        match *self {
+            CssRule::Namespace(ref lock) => lock.read_with(guard).to_css(guard, dest),
+            CssRule::Import(ref lock) => lock.read_with(guard).to_css(guard, dest),
+            CssRule::Style(ref lock) => lock.read_with(guard).to_css(guard, dest),
+            CssRule::FontFace(ref lock) => lock.read_with(guard).to_css(guard, dest),
+            CssRule::CounterStyle(ref lock) => lock.read_with(guard).to_css(guard, dest),
+            CssRule::Viewport(ref lock) => lock.read_with(guard).to_css(guard, dest),
+            CssRule::Keyframes(ref lock) => lock.read_with(guard).to_css(guard, dest),
+            CssRule::Media(ref lock) => lock.read_with(guard).to_css(guard, dest),
+            CssRule::Supports(ref lock) => lock.read_with(guard).to_css(guard, dest),
+            CssRule::Page(ref lock) => lock.read_with(guard).to_css(guard, dest),
+            CssRule::Document(ref lock) => lock.read_with(guard).to_css(guard, dest),
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/components/style/stylesheets/namespace_rule.rs
@@ -0,0 +1,39 @@
+/* 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/. */
+
+//! The `@namespace` at-rule.
+
+use {Namespace, Prefix};
+use cssparser::SourceLocation;
+use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
+use std::fmt;
+
+/// A `@namespace` rule.
+#[derive(Clone, Debug, PartialEq)]
+#[allow(missing_docs)]
+pub struct NamespaceRule {
+    /// The namespace prefix, and `None` if it's the default Namespace
+    pub prefix: Option<Prefix>,
+    /// The actual namespace url.
+    pub url: Namespace,
+    /// The source location this rule was found at.
+    pub source_location: SourceLocation,
+}
+
+impl ToCssWithGuard for NamespaceRule {
+    // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSNamespaceRule
+    fn to_css<W>(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
+    where W: fmt::Write {
+        dest.write_str("@namespace ")?;
+        if let Some(ref prefix) = self.prefix {
+            dest.write_str(&*prefix.to_string())?;
+            dest.write_str(" ")?;
+        }
+
+        // FIXME(emilio): Pretty sure this needs some escaping, or something?
+        dest.write_str("url(\"")?;
+        dest.write_str(&*self.url.to_string())?;
+        dest.write_str("\");")
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/components/style/stylesheets/page_rule.rs
@@ -0,0 +1,60 @@
+/* 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 [`@page`][page] rule.
+//!
+//! [page]: https://drafts.csswg.org/css2/page.html#page-box
+
+use cssparser::SourceLocation;
+use properties::PropertyDeclarationBlock;
+use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
+use std::fmt;
+use style_traits::ToCss;
+use stylearc::Arc;
+
+/// A [`@page`][page] rule.
+///
+/// This implements only a limited subset of the CSS
+/// 2.2 syntax.
+///
+/// In this subset, [page selectors][page-selectors] are not implemented.
+///
+/// [page]: https://drafts.csswg.org/css2/page.html#page-box
+/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors
+#[derive(Debug)]
+pub struct PageRule {
+    /// The declaration block this page rule contains.
+    pub block: Arc<Locked<PropertyDeclarationBlock>>,
+    /// The source position this rule was found at.
+    pub source_location: SourceLocation,
+}
+
+impl ToCssWithGuard for PageRule {
+    /// Serialization of PageRule is not specced, adapted from steps for
+    /// StyleRule.
+    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
+        where W: fmt::Write,
+    {
+        dest.write_str("@page { ")?;
+        let declaration_block = self.block.read_with(guard);
+        declaration_block.to_css(dest)?;
+        if !declaration_block.declarations().is_empty() {
+            dest.write_str(" ")?;
+        }
+        dest.write_str("}")
+    }
+}
+
+impl DeepCloneWithLock for PageRule {
+    fn deep_clone_with_lock(
+        &self,
+        lock: &SharedRwLock,
+        guard: &SharedRwLockReadGuard,
+    ) -> Self {
+        PageRule {
+            block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())),
+            source_location: self.source_location.clone(),
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/components/style/stylesheets/rule_list.rs
@@ -0,0 +1,168 @@
+/* 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 list of CSS rules.
+
+use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard};
+use stylearc::Arc;
+use stylesheets::{CssRule, RulesMutateError};
+use stylesheets::loader::StylesheetLoader;
+use stylesheets::memory::{MallocSizeOfFn, MallocSizeOfWithGuard};
+use stylesheets::rule_parser::State;
+use stylesheets::stylesheet::Stylesheet;
+
+/// A list of CSS rules.
+#[derive(Debug)]
+pub struct CssRules(pub Vec<CssRule>);
+
+impl CssRules {
+    /// Whether this CSS rules is empty.
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+}
+
+impl DeepCloneWithLock for CssRules {
+    fn deep_clone_with_lock(
+        &self,
+        lock: &SharedRwLock,
+        guard: &SharedRwLockReadGuard
+    ) -> Self {
+        CssRules(
+            self.0.iter().map(|ref x| x.deep_clone_with_lock(lock, guard)).collect()
+        )
+    }
+}
+
+impl MallocSizeOfWithGuard for CssRules {
+    fn malloc_size_of_children(
+        &self,
+        guard: &SharedRwLockReadGuard,
+        malloc_size_of: MallocSizeOfFn
+    ) -> usize {
+        self.0.malloc_size_of_children(guard, malloc_size_of)
+    }
+}
+
+impl CssRules {
+    /// Trivially construct a new set of CSS rules.
+    pub fn new(rules: Vec<CssRule>, shared_lock: &SharedRwLock) -> Arc<Locked<CssRules>> {
+        Arc::new(shared_lock.wrap(CssRules(rules)))
+    }
+
+    /// Returns whether all the rules in this list are namespace or import
+    /// rules.
+    fn only_ns_or_import(&self) -> bool {
+        self.0.iter().all(|r| {
+            match *r {
+                CssRule::Namespace(..) |
+                CssRule::Import(..) => true,
+                _ => false
+            }
+        })
+    }
+
+    /// https://drafts.csswg.org/cssom/#remove-a-css-rule
+    pub fn remove_rule(&mut self, index: usize) -> Result<(), RulesMutateError> {
+        // Step 1, 2
+        if index >= self.0.len() {
+            return Err(RulesMutateError::IndexSize);
+        }
+
+        {
+            // Step 3
+            let ref rule = self.0[index];
+
+            // Step 4
+            if let CssRule::Namespace(..) = *rule {
+                if !self.only_ns_or_import() {
+                    return Err(RulesMutateError::InvalidState);
+                }
+            }
+        }
+
+        // Step 5, 6
+        self.0.remove(index);
+        Ok(())
+    }
+}
+
+/// A trait to implement helpers for `Arc<Locked<CssRules>>`.
+pub trait CssRulesHelpers {
+    /// https://drafts.csswg.org/cssom/#insert-a-css-rule
+    ///
+    /// Written in this funky way because parsing an @import rule may cause us
+    /// to clone a stylesheet from the same document due to caching in the CSS
+    /// loader.
+    ///
+    /// TODO(emilio): We could also pass the write guard down into the loader
+    /// instead, but that seems overkill.
+    fn insert_rule(&self,
+                   lock: &SharedRwLock,
+                   rule: &str,
+                   parent_stylesheet: &Stylesheet,
+                   index: usize,
+                   nested: bool,
+                   loader: Option<&StylesheetLoader>)
+                   -> Result<CssRule, RulesMutateError>;
+}
+
+impl CssRulesHelpers for Arc<Locked<CssRules>> {
+    fn insert_rule(&self,
+                   lock: &SharedRwLock,
+                   rule: &str,
+                   parent_stylesheet: &Stylesheet,
+                   index: usize,
+                   nested: bool,
+                   loader: Option<&StylesheetLoader>)
+                   -> Result<CssRule, RulesMutateError> {
+        let state = {
+            let read_guard = lock.read();
+            let rules = self.read_with(&read_guard);
+
+            // Step 1, 2
+            if index > rules.0.len() {
+                return Err(RulesMutateError::IndexSize);
+            }
+
+            // Computes the parser state at the given index
+            if nested {
+                None
+            } else if index == 0 {
+                Some(State::Start)
+            } else {
+                rules.0.get(index - 1).map(CssRule::rule_state)
+            }
+        };
+
+        // Step 3, 4
+        // XXXManishearth should we also store the namespace map?
+        let (new_rule, new_state) =
+            CssRule::parse(&rule, parent_stylesheet, state, loader)?;
+
+        {
+            let mut write_guard = lock.write();
+            let mut rules = self.write_with(&mut write_guard);
+            // Step 5
+            // Computes the maximum allowed parser state at a given index.
+            let rev_state = rules.0.get(index).map_or(State::Body, CssRule::rule_state);
+            if new_state > rev_state {
+                // We inserted a rule too early, e.g. inserting
+                // a regular style rule before @namespace rules
+                return Err(RulesMutateError::HierarchyRequest);
+            }
+
+            // Step 6
+            if let CssRule::Namespace(..) = new_rule {
+                if !rules.only_ns_or_import() {
+                    return Err(RulesMutateError::InvalidState);
+                }
+            }
+
+            rules.0.insert(index, new_rule.clone());
+        }
+
+        Ok(new_rule)
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/components/style/stylesheets/rule_parser.rs
@@ -0,0 +1,520 @@
+/* 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/. */
+
+//! Parsing of the stylesheet contents.
+
+use {Namespace, Prefix};
+use counter_style::{parse_counter_style_body, parse_counter_style_name};
+use cssparser::{AtRuleParser, AtRuleType, Parser, QualifiedRuleParser, RuleListParser, SourceLocation};
+use font_face::parse_font_face_block;
+use media_queries::{parse_media_query_list, MediaList};
+use parking_lot::RwLock;
+use parser::{Parse, ParserContext, log_css_error};
+use properties::parse_property_declaration_list;
+use selector_parser::{SelectorImpl, SelectorParser};
+use selectors::SelectorList;
+use shared_lock::{Locked, SharedRwLock};
+use std::sync::atomic::AtomicBool;
+use str::starts_with_ignore_ascii_case;
+use stylearc::Arc;
+use stylesheets::{CssRule, CssRules, CssRuleType, Origin, StylesheetLoader};
+use stylesheets::{DocumentRule, ImportRule, KeyframesRule, MediaRule, NamespaceRule, PageRule};
+use stylesheets::{StyleRule, SupportsRule, ViewportRule};
+use stylesheets::document_rule::DocumentCondition;
+use stylesheets::keyframes_rule::parse_keyframe_list;
+use stylesheets::loader::NoOpLoader;
+use stylesheets::stylesheet::{Namespaces, Stylesheet};
+use stylesheets::supports_rule::SupportsCondition;
+use values::CustomIdent;
+use values::KeyframesName;
+use values::specified::url::SpecifiedUrl;
+
+/// The parser for the top-level rules in a stylesheet.
+pub struct TopLevelRuleParser<'a> {
+    /// The origin of the stylesheet we're parsing.
+    pub stylesheet_origin: Origin,
+    /// A reference to the lock we need to use to create rules.
+    pub shared_lock: &'a SharedRwLock,
+    /// A reference to a stylesheet loader if applicable, for `@import` rules.
+    pub loader: Option<&'a StylesheetLoader>,
+    /// The parser context. This initially won't contain any namespaces, but
+    /// will be populated after parsing namespace rules, if any.
+    pub context: ParserContext<'a>,
+    /// The current state of the parser.
+    pub state: State,
+    /// The namespace map we use for parsing. Needs to start as `Some()`, and
+    /// will be taken out after parsing namespace rules, and that reference will
+    /// be moved to `ParserContext`.
+    pub namespaces: Option<&'a mut Namespaces>,
+}
+
+impl<'b> TopLevelRuleParser<'b> {
+    fn nested<'a: 'b>(&'a self) -> NestedRuleParser<'a, 'b> {
+        NestedRuleParser {
+            stylesheet_origin: self.stylesheet_origin,
+            shared_lock: self.shared_lock,
+            context: &self.context,
+        }
+    }
+
+    /// Returns the associated parser context with this rule parser.
+    pub fn context(&self) -> &ParserContext {
+        &self.context
+    }
+
+    /// Returns the current state of the parser.
+    pub fn state(&self) -> State {
+        self.state
+    }
+}
+
+/// The current state of the parser.
+#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
+pub enum State {
+    /// We haven't started parsing rules.
+    Start = 1,
+    /// We're parsing `@import` rules.
+    Imports = 2,
+    /// We're parsing `@namespace` rules.
+    Namespaces = 3,
+    /// We're parsing the main body of the stylesheet.
+    Body = 4,
+    /// We've found an invalid state (as, a namespace rule after style rules),
+    /// and the rest of the stylesheet should be ignored.
+    Invalid = 5,
+}
+
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+/// Vendor prefix.
+pub enum VendorPrefix {
+    /// -moz prefix.
+    Moz,
+    /// -webkit prefix.
+    WebKit,
+}
+
+/// A rule prelude for a given at-rule.
+pub enum AtRulePrelude {
+    /// A @font-face rule prelude.
+    FontFace(SourceLocation),
+    /// A @counter-style rule prelude, with its counter style name.
+    CounterStyle(CustomIdent),
+    /// A @media rule prelude, with its media queries.
+    Media(Arc<Locked<MediaList>>, SourceLocation),
+    /// An @supports rule, with its conditional
+    Supports(SupportsCondition, SourceLocation),
+    /// A @viewport rule prelude.
+    Viewport,
+    /// A @keyframes rule, with its animation name and vendor prefix if exists.
+    Keyframes(KeyframesName, Option<VendorPrefix>, SourceLocation),
+    /// A @page rule prelude.
+    Page(SourceLocation),
+    /// A @document rule, with its conditional.
+    Document(DocumentCondition, SourceLocation),
+}
+
+
+#[cfg(feature = "gecko")]
+fn register_namespace(ns: &Namespace) -> Result<i32, ()> {
+    use gecko_bindings::bindings;
+    let id = unsafe { bindings::Gecko_RegisterNamespace(ns.0.as_ptr()) };
+    if id == -1 {
+        Err(())
+    } else {
+        Ok(id)
+    }
+}
+
+#[cfg(feature = "servo")]
+fn register_namespace(_: &Namespace) -> Result<(), ()> {
+    Ok(()) // servo doesn't use namespace ids
+}
+
+impl<'a> AtRuleParser for TopLevelRuleParser<'a> {
+    type Prelude = AtRulePrelude;
+    type AtRule = CssRule;
+
+    fn parse_prelude(
+        &mut self,
+        name: &str,
+        input: &mut Parser
+    ) -> Result<AtRuleType<AtRulePrelude, CssRule>, ()> {
+        let location = get_location_with_offset(input.current_source_location(),
+                                                self.context.line_number_offset);
+        match_ignore_ascii_case! { name,
+            "import" => {
+                if self.state > State::Imports {
+                    self.state = State::Invalid;
+                    return Err(())  // "@import must be before any rule but @charset"
+                }
+
+                self.state = State::Imports;
+                let url_string = input.expect_url_or_string()?;
+                let specified_url = SpecifiedUrl::parse_from_string(url_string, &self.context)?;
+
+                let media = parse_media_query_list(&self.context, input);
+                let media = Arc::new(self.shared_lock.wrap(media));
+
+                let noop_loader = NoOpLoader;
+                let loader = if !specified_url.is_invalid() {
+                    self.loader.expect("Expected a stylesheet loader for @import")
+                } else {
+                    &noop_loader
+                };
+
+                let mut specified_url = Some(specified_url);
+                let arc = loader.request_stylesheet(media, &mut |media| {
+                    ImportRule {
+                        url: specified_url.take().unwrap(),
+                        stylesheet: Arc::new(Stylesheet {
+                            rules: CssRules::new(Vec::new(), self.shared_lock),
+                            media: media,
+                            shared_lock: self.shared_lock.clone(),
+                            origin: self.context.stylesheet_origin,
+                            url_data: self.context.url_data.clone(),
+                            namespaces: RwLock::new(Namespaces::default()),
+                            dirty_on_viewport_size_change: AtomicBool::new(false),
+                            disabled: AtomicBool::new(false),
+                            quirks_mode: self.context.quirks_mode,
+                        }),
+                        source_location: location,
+                    }
+                }, &mut |import_rule| {
+                    Arc::new(self.shared_lock.wrap(import_rule))
+                });
+
+                return Ok(AtRuleType::WithoutBlock(CssRule::Import(arc)))
+            },
+            "namespace" => {
+                if self.state > State::Namespaces {
+                    self.state = State::Invalid;
+                    return Err(())  // "@namespace must be before any rule but @charset and @import"
+                }
+                self.state = State::Namespaces;
+
+                let prefix_result = input.try(|input| input.expect_ident());
+                let url = Namespace::from(try!(input.expect_url_or_string()));
+
+                let id = register_namespace(&url)?;
+
+                let mut namespaces = self.namespaces.as_mut().unwrap();
+
+                let opt_prefix = if let Ok(prefix) = prefix_result {
+                    let prefix = Prefix::from(prefix);
+                    namespaces
+                        .prefixes
+                        .insert(prefix.clone(), (url.clone(), id));
+                    Some(prefix)
+                } else {
+                    namespaces.default = Some((url.clone(), id));
+                    None
+                };
+
+                return Ok(AtRuleType::WithoutBlock(CssRule::Namespace(Arc::new(
+                    self.shared_lock.wrap(NamespaceRule {
+                        prefix: opt_prefix,
+                        url: url,
+                        source_location: location,
+                    })
+                ))))
+            },
+            // @charset is removed by rust-cssparser if it’s the first rule in the stylesheet
+            // anything left is invalid.
+            "charset" => return Err(()), // (insert appropriate error message)
+            _ => {}
+        }
+        // Don't allow starting with an invalid state
+        if self.state > State::Body {
+            self.state = State::Invalid;
+            return Err(());
+        }
+        self.state = State::Body;
+
+        // "Freeze" the namespace map (no more namespace rules can be parsed
+        // after this point), and stick it in the context.
+        if self.namespaces.is_some() {
+            let namespaces = &*self.namespaces.take().unwrap();
+            self.context.namespaces = Some(namespaces);
+        }
+        AtRuleParser::parse_prelude(&mut self.nested(), name, input)
+    }
+
+    #[inline]
+    fn parse_block(&mut self, prelude: AtRulePrelude, input: &mut Parser) -> Result<CssRule, ()> {
+        AtRuleParser::parse_block(&mut self.nested(), prelude, input)
+    }
+}
+
+
+impl<'a> QualifiedRuleParser for TopLevelRuleParser<'a> {
+    type Prelude = SelectorList<SelectorImpl>;
+    type QualifiedRule = CssRule;
+
+    #[inline]
+    fn parse_prelude(&mut self, input: &mut Parser) -> Result<SelectorList<SelectorImpl>, ()> {
+        self.state = State::Body;
+
+        // "Freeze" the namespace map (no more namespace rules can be parsed
+        // after this point), and stick it in the context.
+        if self.namespaces.is_some() {
+            let namespaces = &*self.namespaces.take().unwrap();
+            self.context.namespaces = Some(namespaces);
+        }
+
+        QualifiedRuleParser::parse_prelude(&mut self.nested(), input)
+    }
+
+    #[inline]
+    fn parse_block(
+        &mut self,
+        prelude: SelectorList<SelectorImpl>,
+        input: &mut Parser
+    ) -> Result<CssRule, ()> {
+        QualifiedRuleParser::parse_block(&mut self.nested(), prelude, input)
+    }
+}
+
+#[derive(Clone)]  // shallow, relatively cheap .clone
+struct NestedRuleParser<'a, 'b: 'a> {
+    stylesheet_origin: Origin,
+    shared_lock: &'a SharedRwLock,
+    context: &'a ParserContext<'b>,
+}
+
+impl<'a, 'b> NestedRuleParser<'a, 'b> {
+    fn parse_nested_rules(
+        &mut self,
+        input: &mut Parser,
+        rule_type: CssRuleType
+    ) -> Arc<Locked<CssRules>> {
+        let context = ParserContext::new_with_rule_type(self.context, Some(rule_type));
+
+        let nested_parser = NestedRuleParser {
+            stylesheet_origin: self.stylesheet_origin,
+            shared_lock: self.shared_lock,
+            context: &context,
+        };
+
+        let mut iter = RuleListParser::new_for_nested_rule(input, nested_parser);
+        let mut rules = Vec::new();
+        while let Some(result) = iter.next() {
+            match result {
+                Ok(rule) => rules.push(rule),
+                Err(range) => {
+                    let pos = range.start;
+                    let message = format!("Unsupported rule: '{}'", iter.input.slice(range));
+                    log_css_error(iter.input, pos, &*message, self.context);
+                }
+            }
+        }
+        CssRules::new(rules, self.shared_lock)
+    }
+}
+
+#[cfg(feature = "servo")]
+fn is_viewport_enabled() -> bool {
+    use servo_config::prefs::PREFS;
+    PREFS.get("layout.viewport.enabled").as_boolean().unwrap_or(false)
+}
+
+#[cfg(not(feature = "servo"))]
+fn is_viewport_enabled() -> bool {
+    false // Gecko doesn't support @viewport.
+}
+
+impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> {
+    type Prelude = AtRulePrelude;
+    type AtRule = CssRule;
+
+    fn parse_prelude(
+        &mut self,
+        name: &str,
+        input: &mut Parser
+    ) -> Result<AtRuleType<AtRulePrelude, CssRule>, ()> {
+        let location =
+            get_location_with_offset(
+                input.current_source_location(),
+                self.context.line_number_offset
+            );
+
+        match_ignore_ascii_case! { name,
+            "media" => {
+                let media_queries = parse_media_query_list(self.context, input);
+                let arc = Arc::new(self.shared_lock.wrap(media_queries));
+                Ok(AtRuleType::WithBlock(AtRulePrelude::Media(arc, location)))
+            },
+            "supports" => {
+                let cond = SupportsCondition::parse(input)?;
+                Ok(AtRuleType::WithBlock(AtRulePrelude::Supports(cond, location)))
+            },
+            "font-face" => {
+                Ok(AtRuleType::WithBlock(AtRulePrelude::FontFace(location)))
+            },
+            "counter-style" => {
+                if !cfg!(feature = "gecko") {
+                    // Support for this rule is not fully implemented in Servo yet.
+                    return Err(())
+                }
+                let name = parse_counter_style_name(input)?;
+                // ASCII-case-insensitive matches for "decimal" are already
+                // lower-cased by `parse_counter_style_name`, so we can use ==
+                // here.
+                if name.0 == atom!("decimal") {
+                    return Err(())
+                }
+                Ok(AtRuleType::WithBlock(AtRulePrelude::CounterStyle(name)))
+            },
+            "viewport" => {
+                if is_viewport_enabled() {
+                    Ok(AtRuleType::WithBlock(AtRulePrelude::Viewport))
+                } else {
+                    Err(())
+                }
+            },
+            "keyframes" | "-webkit-keyframes" | "-moz-keyframes" => {
+                let prefix = if starts_with_ignore_ascii_case(name, "-webkit-") {
+                    Some(VendorPrefix::WebKit)
+                } else if starts_with_ignore_ascii_case(name, "-moz-") {
+                    Some(VendorPrefix::Moz)
+                } else {
+                    None
+                };
+                if cfg!(feature = "servo") &&
+                   prefix.as_ref().map_or(false, |p| matches!(*p, VendorPrefix::Moz)) {
+                    // Servo should not support @-moz-keyframes.
+                    return Err(())
+                }
+                let name = KeyframesName::parse(self.context, input)?;
+
+                Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(name, prefix, location)))
+            },
+            "page" => {
+                if cfg!(feature = "gecko") {
+                    Ok(AtRuleType::WithBlock(AtRulePrelude::Page(location)))
+                } else {
+                    Err(())
+                }
+            },
+            "-moz-document" => {
+                if cfg!(feature = "gecko") {
+                    let cond = DocumentCondition::parse(self.context, input)?;
+                    Ok(AtRuleType::WithBlock(AtRulePrelude::Document(cond, location)))
+                } else {
+                    Err(())
+                }
+            },
+            _ => Err(())
+        }
+    }
+
+    fn parse_block(
+        &mut self,
+        prelude: AtRulePrelude,
+        input: &mut Parser
+    ) -> Result<CssRule, ()> {
+        match prelude {
+            AtRulePrelude::FontFace(location) => {
+                let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::FontFace));
+                Ok(CssRule::FontFace(Arc::new(self.shared_lock.wrap(
+                   parse_font_face_block(&context, input, location).into()))))
+            }
+            AtRulePrelude::CounterStyle(name) => {
+                let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::CounterStyle));
+                Ok(CssRule::CounterStyle(Arc::new(self.shared_lock.wrap(
+                   parse_counter_style_body(name, &context, input)?.into()))))
+            }
+            AtRulePrelude::Media(media_queries, location) => {
+                Ok(CssRule::Media(Arc::new(self.shared_lock.wrap(MediaRule {
+                    media_queries: media_queries,
+                    rules: self.parse_nested_rules(input, CssRuleType::Media),
+                    source_location: location,
+                }))))
+            }
+            AtRulePrelude::Supports(cond, location) => {
+                let enabled = cond.eval(self.context);
+                Ok(CssRule::Supports(Arc::new(self.shared_lock.wrap(SupportsRule {
+                    condition: cond,
+                    rules: self.parse_nested_rules(input, CssRuleType::Supports),
+                    enabled: enabled,
+                    source_location: location,
+                }))))
+            }
+            AtRulePrelude::Viewport => {
+                let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Viewport));
+                Ok(CssRule::Viewport(Arc::new(self.shared_lock.wrap(
+                   try!(ViewportRule::parse(&context, input))))))
+            }
+            AtRulePrelude::Keyframes(name, prefix, location) => {
+                let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Keyframes));
+                Ok(CssRule::Keyframes(Arc::new(self.shared_lock.wrap(KeyframesRule {
+                    name: name,
+                    keyframes: parse_keyframe_list(&context, input, self.shared_lock),
+                    vendor_prefix: prefix,
+                    source_location: location,
+                }))))
+            }
+            AtRulePrelude::Page(location) => {
+                let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Page));
+                let declarations = parse_property_declaration_list(&context, input);
+                Ok(CssRule::Page(Arc::new(self.shared_lock.wrap(PageRule {
+                    block: Arc::new(self.shared_lock.wrap(declarations)),
+                    source_location: location,
+                }))))
+            }
+            AtRulePrelude::Document(cond, location) => {
+                if cfg!(feature = "gecko") {
+                    Ok(CssRule::Document(Arc::new(self.shared_lock.wrap(DocumentRule {
+                        condition: cond,
+                        rules: self.parse_nested_rules(input, CssRuleType::Document),
+                        source_location: location,
+                    }))))
+                } else {
+                    unreachable!()
+                }
+            }
+        }
+    }
+}
+
+impl<'a, 'b> QualifiedRuleParser for NestedRuleParser<'a, 'b> {
+    type Prelude = SelectorList<SelectorImpl>;
+    type QualifiedRule = CssRule;
+
+    fn parse_prelude(&mut self, input: &mut Parser) -> Result<SelectorList<SelectorImpl>, ()> {
+        let selector_parser = SelectorParser {
+            stylesheet_origin: self.stylesheet_origin,
+            namespaces: self.context.namespaces.unwrap(),
+        };
+
+        SelectorList::parse(&selector_parser, input)
+    }
+
+    fn parse_block(
+        &mut self,
+        prelude: SelectorList<SelectorImpl>,
+        input: &mut Parser
+    ) -> Result<CssRule, ()> {
+        let location = get_location_with_offset(input.current_source_location(),
+                                                self.context.line_number_offset);
+        let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Style));
+        let declarations = parse_property_declaration_list(&context, input);
+        Ok(CssRule::Style(Arc::new(self.shared_lock.wrap(StyleRule {
+            selectors: prelude,
+            block: Arc::new(self.shared_lock.wrap(declarations)),
+            source_location: location,
+        }))))
+    }
+}
+
+/// Calculates the location of a rule's source given an offset.
+fn get_location_with_offset(
+    location: SourceLocation,
+    offset: u64
+) -> SourceLocation {
+    SourceLocation {
+        line: location.line + offset as usize - 1,
+        column: location.column,
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/components/style/stylesheets/rules_iterator.rs
@@ -0,0 +1,273 @@
+/* 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/. */
+
+//! An iterator over a list of rules.
+
+use context::QuirksMode;
+use media_queries::Device;
+use shared_lock::SharedRwLockReadGuard;
+use smallvec::SmallVec;
+use std::slice;
+use stylesheets::{CssRule, CssRules, DocumentRule, ImportRule, MediaRule, SupportsRule};
+
+/// 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,
+        }
+    }
+
+    /// Skips all the remaining children of the last nested rule processed.
+    pub fn skip_children(&mut self) {
+        self.stack.pop();
+    }
+}
+
+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> {
+        let mut nested_iter_finished = false;
+        while !self.stack.is_empty() {
+            if nested_iter_finished {
+                self.stack.pop();
+                nested_iter_finished = false;
+                continue;
+            }
+
+            let rule;
+            let sub_iter;
+            {
+                let mut nested_iter = self.stack.last_mut().unwrap();
+                rule = match nested_iter.next() {
+                    Some(r) => r,
+                    None => {
+                        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) {
+                            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) {
+                            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) {
+                            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) {
+                            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,
+                };
+            }
+
+            if let Some(sub_iter) = sub_iter {
+                self.stack.push(sub_iter);
+            }
+
+            return Some(rule);
+        }
+
+        None
+    }
+}
+
+/// 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 {
+    fn process_import(
+        guard: &SharedRwLockReadGuard,
+        device: &Device,
+        quirks_mode: QuirksMode,
+        rule: &ImportRule)
+        -> bool
+    {
+        rule.stylesheet.media.read_with(guard).evaluate(device, quirks_mode)
+    }
+
+    fn process_media(
+        guard: &SharedRwLockReadGuard,
+        device: &Device,
+        quirks_mode: QuirksMode,
+        rule: &MediaRule)
+        -> bool
+    {
+        rule.media_queries.read_with(guard).evaluate(device, quirks_mode)
+    }
+
+    fn process_document(
+        _: &SharedRwLockReadGuard,
+        device: &Device,
+        _: QuirksMode,
+        rule: &DocumentRule)
+        -> bool
+    {
+        rule.condition.evaluate(device)
+    }
+
+    fn process_supports(
+        _: &SharedRwLockReadGuard,
+        _: &Device,
+        _: QuirksMode,
+        rule: &SupportsRule)
+        -> bool
+    {
+        rule.enabled
+    }
+}
+
+/// A filter that processes all the rules in a rule list.
+pub struct AllRules;
+
+impl NestedRuleIterationCondition for AllRules {
+    fn process_import(
+        _: &SharedRwLockReadGuard,
+        _: &Device,
+        _: QuirksMode,
+        _: &ImportRule)
+        -> bool
+    {
+        true
+    }
+
+    fn process_media(
+        _: &SharedRwLockReadGuard,
+        _: &Device,
+        _: QuirksMode,
+        _: &MediaRule)
+        -> bool
+    {
+        true
+    }
+
+    fn process_document(
+        _: &SharedRwLockReadGuard,
+        _: &Device,
+        _: QuirksMode,
+        _: &DocumentRule)
+        -> bool
+    {
+        true
+    }
+
+    fn process_supports(
+        _: &SharedRwLockReadGuard,
+        _: &Device,
+        _: QuirksMode,
+        _: &SupportsRule)
+        -> bool
+    {
+        true
+    }
+}
+
+/// 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>;
new file mode 100644
--- /dev/null
+++ b/servo/components/style/stylesheets/style_rule.rs
@@ -0,0 +1,76 @@
+/* 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 style rule.
+
+use cssparser::SourceLocation;
+use properties::PropertyDeclarationBlock;
+use selector_parser::SelectorImpl;
+use selectors::SelectorList;
+use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
+use std::fmt;
+use style_traits::ToCss;
+use stylearc::Arc;
+use stylesheets::{MallocSizeOf, MallocSizeOfFn, MallocSizeOfWithGuard};
+
+/// A style rule, with selectors and declarations.
+#[derive(Debug)]
+pub struct StyleRule {
+    /// The list of selectors in this rule.
+    pub selectors: SelectorList<SelectorImpl>,
+    /// The declaration block with the properties it contains.
+    pub block: Arc<Locked<PropertyDeclarationBlock>>,
+    /// The location in the sheet where it was found.
+    pub source_location: SourceLocation,
+}
+
+impl DeepCloneWithLock for StyleRule {
+    /// Deep clones this StyleRule.
+    fn deep_clone_with_lock(
+        &self,
+        lock: &SharedRwLock,
+        guard: &SharedRwLockReadGuard,
+    ) -> StyleRule {
+        StyleRule {
+            selectors: self.selectors.clone(),
+            block: Arc::new(lock.wrap(self.block.read_with(guard).clone())),
+            source_location: self.source_location.clone(),
+        }
+    }
+}
+
+impl MallocSizeOfWithGuard for StyleRule {
+    fn malloc_size_of_children(
+        &self,
+        guard: &SharedRwLockReadGuard,
+        malloc_size_of: MallocSizeOfFn
+    ) -> usize {
+        // Measurement of other fields may be added later.
+        self.block.read_with(guard).malloc_size_of_children(malloc_size_of)
+    }
+}
+
+impl ToCssWithGuard for StyleRule {
+    /// https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSStyleRule
+    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
+        where W: fmt::Write,
+    {
+        use cssparser::ToCss;
+
+        // Step 1
+        self.selectors.to_css(dest)?;
+        // Step 2
+        dest.write_str(" { ")?;
+        // Step 3
+        let declaration_block = self.block.read_with(guard);
+        declaration_block.to_css(dest)?;
+        // Step 4
+        if !declaration_block.declarations().is_empty() {
+            dest.write_str(" ")?;
+        }
+        // Step 5
+        dest.write_str("}")
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/servo/components/style/stylesheets/stylesheet.rs
@@ -0,0 +1,342 @@
+/* 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/. */
+
+use {Prefix, Namespace};
+use context::QuirksMode;
+use cssparser::{Parser, RuleListParser};
+use error_reporting::ParseErrorReporter;
+use fnv::FnvHashMap;
+use media_queries::{MediaList, Device};
+use parking_lot::RwLock;
+use parser::{PARSING_MODE_DEFAULT, ParserContext, log_css_error};
+use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard};
+use std::mem;
+use std::sync::atomic::{AtomicBool, Ordering};
+use stylearc::Arc;
+use stylesheets::{CssRule, CssRules, Origin, UrlExtraData};
+use stylesheets::loader::StylesheetLoader;
+use stylesheets::memory::{MallocSizeOfFn, MallocSizeOfWithGuard};
+use stylesheets::rule_parser::{State, TopLevelRuleParser};
+use stylesheets::rules_iterator::{EffectiveRules, EffectiveRulesIterator, NestedRuleIterationCondition, RulesIterator};
+use values::specified::NamespaceId;
+
+/// This structure holds the user-agent and user stylesheets.
+pub struct UserAgentStylesheets {
+    /// The lock used for user-agent stylesheets.
+    pub shared_lock: SharedRwLock,
+    /// The user or user agent stylesheets.
+    pub user_or_user_agent_stylesheets: Vec<Stylesheet>,
+    /// The quirks mode stylesheet.
+    pub quirks_mode_stylesheet: Stylesheet,
+}
+
+/// A set of namespaces applying to a given stylesheet.
+///
+/// The namespace id is used in gecko
+#[derive(Clone, Default, Debug)]
+#[allow(missing_docs)]
+pub struct Namespaces {
+    pub default: Option<(Namespace, NamespaceId)>,
+    pub prefixes: FnvHashMap<Prefix, (Namespace, NamespaceId)>,
+}
+
+/// The structure servo uses to represent a stylesheet.
+#[derive(Debug)]
+pub struct Stylesheet {
+    /// List of rules in the order they were found (important for
+    /// cascading order)
+    pub rules: Arc<Locked<CssRules>>,
+    /// List of media associated with the Stylesheet.
+    pub media: Arc<Locked<MediaList>>,
+    /// The origin of this stylesheet.
+    pub origin: Origin,
+    /// The url data this stylesheet should use.
+    pub url_data: UrlExtraData,
+    /// The lock used for objects inside this stylesheet
+    pub shared_lock: SharedRwLock,
+    /// The namespaces that apply to this stylesheet.
+    pub namespaces: RwLock<Namespaces>,
+    /// Whether this stylesheet would be dirty when the viewport size changes.
+    pub dirty_on_viewport_size_change: AtomicBool,
+    /// Whether this stylesheet should be disabled.
+    pub disabled: AtomicBool,
+    /// The quirks mode of this stylesheet.
+    pub quirks_mode: QuirksMode,
+}
+
+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) {
+        let namespaces = RwLock::new(Namespaces::default());
+        // FIXME: we really should update existing.url_data with the given url_data,
+        // otherwise newly inserted rule may not have the right base url.
+        let (rules, dirty_on_viewport_size_change) =
+            Stylesheet::parse_rules(
+                css,
+                url_data,
+                existing.origin,
+                &mut *namespaces.write(),
+                &existing.shared_lock,
+                stylesheet_loader,
+                error_reporter,
+                existing.quirks_mode,
+                line_number_offset
+            );
+
+        mem::swap(&mut *existing.namespaces.write(), &mut *namespaces.write());
+        existing.dirty_on_viewport_size_change
+            .store(dirty_on_viewport_size_change, Ordering::Release);
+
+        // Acquire the lock *after* parsing, to minimize the exclusive section.
+        let mut guard = existing.shared_lock.write();
+        *existing.rules.write_with(&mut guard) = CssRules(rules);
+    }
+
+    fn parse_rules(
+        css: &str,
+        url_data: &UrlExtraData,
+        origin: Origin,
+        namespaces: &mut Namespaces,
+        shared_lock: &SharedRwLock,
+        stylesheet_loader: Option<&StylesheetLoader>,
+        error_reporter: &ParseErrorReporter,
+        quirks_mode: QuirksMode,
+        line_number_offset: u64
+    ) -> (Vec<CssRule>, bool) {
+        let mut rules = Vec::new();
+        let mut input = Parser::new(css);
+
+        let context =
+            ParserContext::new_with_line_number_offset(
+                origin,
+                url_data,
+                error_reporter,
+                line_number_offset,
+                PARSING_MODE_DEFAULT,
+                quirks_mode
+            );
+
+        let rule_parser = TopLevelRuleParser {
+            stylesheet_origin: origin,
+            shared_lock: shared_lock,
+            loader: stylesheet_loader,
+            context: context,
+            state: State::Start,
+            namespaces: Some(namespaces),
+        };
+
+        input.look_for_viewport_percentages();
+
+        {
+            let mut iter =
+                RuleListParser::new_for_stylesheet(&mut input, rule_parser);
+
+            while let Some(result) = iter.next() {
+                match result {
+                    Ok(rule) => rules.push(rule),
+                    Err(range) => {
+                        let pos = range.start;
+                        let message = format!("Invalid rule: '{}'", iter.input.slice(range));
+                        log_css_error(iter.input, pos, &*message, iter.parser.context());
+                    }
+                }
+            }
+        }
+
+        (rules, input.seen_viewport_percentages())
+    }
+
+    /// Creates an empty stylesheet and parses it with a given base url, origin
+    /// and media.
+    ///
+    /// Effectively creates a new stylesheet and forwards the hard work to
+    /// `Stylesheet::update_from_str`.
+    pub fn from_str(css: &str,
+                    url_data: UrlExtraData,
+                    origin: Origin,
+                    media: Arc<Locked<MediaList>>,
+                    shared_lock: SharedRwLock,
+                    stylesheet_loader: Option<&StylesheetLoader>,
+                    error_reporter: &ParseErrorReporter,
+                    quirks_mode: QuirksMode,
+                    line_number_offset: u64)
+                    -> Stylesheet {
+        let namespaces = RwLock::new(Namespaces::default());
+        let (rules, dirty_on_viewport_size_change) = Stylesheet::parse_rules(
+            css,
+            &url_data,
+            origin,
+            &mut *namespaces.write(),
+            &shared_lock,
+            stylesheet_loader,
+            error_reporter,
+            quirks_mode,
+            line_number_offset,
+        );
+
+        Stylesheet {
+            origin: origin,
+            url_data: url_data,
+            namespaces: namespaces,
+            rules: CssRules::new(rules, &shared_lock),
+            media: media,
+            shared_lock: shared_lock,
+            dirty_on_viewport_size_change: AtomicBool::new(dirty_on_viewport_size_change),
+            disabled: AtomicBool::new(false),
+            quirks_mode: quirks_mode,
+        }
+    }
+
+    /// Whether this stylesheet can be dirty on viewport size change.
+    pub fn dirty_on_viewport_size_change(&self) -> bool {
+        self.dirty_on_viewport_size_change.load(Ordering::SeqCst)
+    }
+
+    /// When CSSOM inserts a rule or declaration into this stylesheet, it needs to call this method
+    /// with the return value of `cssparser::Parser::seen_viewport_percentages`.
+    ///
+    /// FIXME: actually make these calls
+    ///
+    /// Note: when *removing* a rule or declaration that contains a viewport percentage,
+    /// to keep the flag accurate we’d need to iterator through the rest of the stylesheet to
+    /// check for *other* such values.
+    ///
+    /// Instead, we conservatively assume there might be some.
+    /// Restyling will some some more work than necessary, but give correct results.
+    pub fn inserted_has_viewport_percentages(&self, has_viewport_percentages: bool) {
+        self.dirty_on_viewport_size_change.fetch_or(has_viewport_percentages, Ordering::SeqCst);
+    }
+
+    /// Returns whether the style-sheet applies for the current device depending
+    /// on the associated MediaList.
+    ///
+    /// 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`.
+    #[inline]
+    pub fn effective_rules<'a, 'b>(
+        &'a self,
+        device: &'a Device,
+        guard: &'a SharedRwLockReadGuard<'b>)
+        -> EffectiveRulesIterator<'a, 'b>
+    {
+        self.iter_rules::<'a, 'b, EffectiveRules>(device, guard)
+    }
+
+    /// Return an iterator using the condition `C`.
+    #[inline]
+    pub fn iter_rules<'a, 'b, C>(
+        &'a self,
+        device: &'a Device,
+        guard: &'a SharedRwLockReadGuard<'b>)
+        -> RulesIterator<'a, 'b, C>
+        where C: NestedRuleIterationCondition,
+    {
+        RulesIterator::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)
+    }
+
+    /// Records that the stylesheet has been explicitly disabled through the
+    /// CSSOM.
+    ///
+    /// Returns whether the the call resulted in a change in disabled state.
+    ///
+    /// Disabled stylesheets remain in the document, but their rules are not
+    /// added to the Stylist.
+    pub fn set_disabled(&self, disabled: bool) -> bool {
+        self.disabled.swap(disabled, Ordering::SeqCst) != disabled
+    }
+}
+
+impl Clone for Stylesheet {
+    fn clone(&self) -> Stylesheet {
+        // Create a new lock for our clone.
+        let lock = self.shared_lock.clone();
+        let guard = self.shared_lock.read();
+
+        // Make a deep clone of the rules, using the new lock.
+        let rules = self.rules.read_with(&guard);
+        let cloned_rules = rules.deep_clone_with_lock(&lock, &guard);
+
+        // Make a deep clone of the media, using the new lock.
+        let media = self.media.read_with(&guard);
+        let cloned_media = media.clone();
+
+        Stylesheet {
+            rules: Arc::new(lock.wrap(cloned_rules)),
+            media: Arc::new(lock.wrap(cloned_media)),
+            origin: self.origin,
+            url_data: self.url_data.clone(),
+            shared_lock: lock,
+            namespaces: RwLock::new((*self.namespaces.read()).clone()),
+            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,
+        }
+    }
+}
+
+impl MallocSizeOfWithGuard for Stylesheet {
+    fn malloc_size_of_children(
+        &self,
+        guard: &SharedRwLockReadGuard,
+        malloc_size_of: MallocSizeOfFn
+    ) -> usize {
+        // Measurement of other fields may be added later.
+        self.rules.read_with(guard).malloc_size_of_children(guard, malloc_size_of)
+    }
+}
+
+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(&::stylesheets::$rule_type),
+                {
+                    use stylesheets::CssRule;
+
+                    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),
+    effective_media_rules(Media => MediaRule),
+    effective_font_face_rules(FontFace => FontFaceRule),
+    effective_counter_style_rules(CounterStyle => CounterStyleRule),
+    effective_viewport_rules(Viewport => ViewportRule),
+    effective_keyframes_rules(Keyframes => KeyframesRule),
+    effective_supports_rules(Supports => SupportsRule),
+    effective_page_rules(Page => PageRule),
+    effective_document_rules(Document => DocumentRule),
+}
rename from servo/components/style/supports.rs
rename to servo/components/style/stylesheets/supports_rule.rs
--- a/servo/components/style/supports.rs
+++ b/servo/components/style/stylesheets/supports_rule.rs
@@ -1,25 +1,72 @@
 /* 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/. */
 
 //! [@supports rules](https://drafts.csswg.org/css-conditional-3/#at-supports)
 
-use cssparser::{parse_important, Parser, Token};
+use cssparser::{parse_important, Parser, SourceLocation, Token};
 use parser::ParserContext;
 use properties::{PropertyId, PropertyDeclaration, SourcePropertyDeclaration};
+use shared_lock::{DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
 use std::fmt;
 use style_traits::ToCss;
-use stylesheets::CssRuleType;
+use stylearc::Arc;
+use stylesheets::{CssRuleType, CssRules};
+
+/// An [`@supports`][supports] rule.
+///
+/// [supports]: https://drafts.csswg.org/css-conditional-3/#at-supports
+#[derive(Debug)]
+pub struct SupportsRule {
+    /// The parsed condition
+    pub condition: SupportsCondition,
+    /// Child rules
+    pub rules: Arc<Locked<CssRules>>,
+    /// The result of evaluating the condition
+    pub enabled: bool,
+    /// The line and column of the rule's source code.
+    pub source_location: SourceLocation,
+}
 
-#[derive(Clone, Debug)]
+impl ToCssWithGuard for SupportsRule {
+    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
+    where W: fmt::Write {
+        dest.write_str("@supports ")?;
+        self.condition.to_css(dest)?;
+        dest.write_str(" {")?;
+        for rule in self.rules.read_with(guard).0.iter() {
+            dest.write_str(" ")?;
+            rule.to_css(guard, dest)?;
+        }
+        dest.write_str(" }")
+    }
+}
+
+impl DeepCloneWithLock for SupportsRule {
+    fn deep_clone_with_lock(
+        &self,
+        lock: &SharedRwLock,
+        guard: &SharedRwLockReadGuard
+    ) -> Self {
+        let rules = self.rules.read_with(guard);
+        SupportsRule {
+            condition: self.condition.clone(),
+            rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
+            enabled: self.enabled,
+            source_location: self.source_location.clone(),
+        }
+    }
+}
+
 /// An @supports condition
 ///
 /// https://drafts.csswg.org/css-conditional-3/#at-supports
+#[derive(Clone, Debug)]
 pub enum SupportsCondition {
     /// `not (condition)`
     Not(Box<SupportsCondition>),
     /// `(condition)`
     Parenthesized(Box<SupportsCondition>),
     /// `(condition) and (condition) and (condition) ..`
     And(Vec<SupportsCondition>),
     /// `(condition) or (condition) or (condition) ..`
@@ -114,17 +161,18 @@ pub fn parse_condition_or_declaration(in
         Ok(SupportsCondition::Parenthesized(Box::new(condition)))
     } else {
         Declaration::parse(input).map(SupportsCondition::Declaration)
     }
 }
 
 impl ToCss for SupportsCondition {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
-        where W: fmt::Write {
+        where W: fmt::Write,
+    {
         match *self {
             SupportsCondition::Not(ref cond) => {
                 dest.write_str("not ")?;
                 cond.to_css(dest)
             }
             SupportsCondition::Parenthesized(ref cond) => {
                 dest.write_str("(")?;
                 cond.to_css(dest)?;
@@ -168,32 +216,33 @@ pub struct Declaration {
     /// The property name
     pub prop: String,
     /// The property value
     pub val: String,
 }
 
 impl ToCss for Declaration {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
-        where W: fmt::Write {
+        where W: fmt::Write
+    {
         dest.write_str(&self.prop)?;
         dest.write_str(":")?;
         // no space, the `val` already contains any possible spaces
         dest.write_str(&self.val)
     }
 }
 
 /// Slurps up input till exhausted, return string from source position
 fn parse_anything(input: &mut Parser) -> String {
     let pos = input.position();
     consume_all(input);
     input.slice_from(pos).to_owned()
 }
 
-/// consume input till done
+/// Consume input till done
 fn consume_all(input: &mut Parser) {
     while let Ok(_) = input.next() {}
 }
 
 impl Declaration {
     /// Parse a declaration
     pub fn parse(input: &mut Parser) -> Result<Declaration, ()> {
         let prop = input.expect_ident()?.into_owned();
rename from servo/components/style/viewport.rs
rename to servo/components/style/stylesheets/viewport_rule.rs
--- a/servo/components/style/viewport.rs
+++ b/servo/components/style/stylesheets/viewport_rule.rs
@@ -2,18 +2,16 @@
  * 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/. */
 
 //! The [`@viewport`][at] at-rule and [`meta`][meta] element.
 //!
 //! [at]: https://drafts.csswg.org/css-device-adapt/#atviewport-rule
 //! [meta]: https://drafts.csswg.org/css-device-adapt/#viewport-meta
 
-#![deny(missing_docs)]
-
 use app_units::Au;
 use context::QuirksMode;
 use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser, parse_important};
 use cssparser::ToCss as ParserToCss;
 use euclid::size::TypedSize2D;
 use font_metrics::get_metrics_provider_for_product;
 use media_queries::Device;
 use parser::{Parse, ParserContext, log_css_error};
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -10,17 +10,16 @@ use context::{QuirksMode, SharedStyleCon
 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};
 use selector_map::{SelectorMap, SelectorMapEntry};
@@ -37,18 +36,19 @@ 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, StyleRule};
 use stylesheets::{Stylesheet, Origin, UserAgentStylesheets};
+use stylesheets::keyframes_rule::KeyframesAnimation;
+use stylesheets::viewport_rule::{self, MaybeNew, ViewportRule};
 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.
 ///
 /// In measurements on wikipedia, we pretty much never have more than 8 applicable
@@ -351,17 +351,17 @@ impl Stylist {
 
         if !(self.is_device_dirty || stylesheets_changed) {
             return false;
         }
 
         self.num_rebuilds += 1;
 
         let cascaded_rule = ViewportRule {
-            declarations: viewport::Cascade::from_stylesheets(
+            declarations: viewport_rule::Cascade::from_stylesheets(
                 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 {
@@ -764,17 +764,17 @@ impl Stylist {
     /// FIXME(emilio): The semantics of the device for Servo and Gecko are
     /// different enough we may want to unify them.
     #[cfg(feature = "servo")]
     pub fn set_device(&mut self,
                       mut device: Device,
                       guard: &SharedRwLockReadGuard,
                       stylesheets: &[Arc<Stylesheet>]) {
         let cascaded_rule = ViewportRule {
-            declarations: viewport::Cascade::from_stylesheets(stylesheets.iter(), guard, &device).finish(),
+            declarations: viewport_rule::Cascade::from_stylesheets(stylesheets.iter(), guard, &device).finish(),
         };
 
         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);
         }
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -78,17 +78,16 @@ use style::gecko_bindings::structs::Styl
 use style::gecko_bindings::structs::URLExtraData;
 use style::gecko_bindings::structs::nsCSSValueSharedList;
 use style::gecko_bindings::structs::nsCompatibility;
 use style::gecko_bindings::structs::nsresult;
 use style::gecko_bindings::sugar::ownership::{FFIArcHelpers, HasFFI, HasArcFFI, HasBoxFFI};
 use style::gecko_bindings::sugar::ownership::{HasSimpleFFI, Strong};
 use style::gecko_bindings::sugar::refptr::RefPtr;
 use style::gecko_properties::{self, style_structs};
-use style::keyframes::{Keyframe, KeyframeSelector, KeyframesStepValue};
 use style::media_queries::{MediaList, parse_media_query_list};
 use style::parallel;
 use style::parser::{PARSING_MODE_DEFAULT, ParserContext};
 use style::properties::{CascadeFlags, ComputedValues, Importance, SourcePropertyDeclaration};
 use style::properties::{LonghandIdSet, PropertyDeclarationBlock, PropertyId, StyleBuilder};
 use style::properties::SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP;
 use style::properties::animated_properties::{Animatable, AnimationValue, TransitionProperty};
 use style::properties::parse_one_declaration_into;
@@ -99,18 +98,19 @@ use style::sequential;
 use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard, Locked};
 use style::string_cache::Atom;
 use style::style_adjuster::StyleAdjuster;
 use style::stylearc::Arc;
 use style::stylesheets::{CssRule, CssRules, CssRuleType, CssRulesHelpers, DocumentRule};
 use style::stylesheets::{ImportRule, KeyframesRule, MallocSizeOfWithGuard, MediaRule};
 use style::stylesheets::{NamespaceRule, Origin, PageRule, Stylesheet, StyleRule, 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::supports::parse_condition_or_declaration;
 use style::thread_state;
 use style::timer::Timer;
 use style::traversal::{ANIMATION_ONLY, DomTraversal, FOR_CSS_RULE_CHANGES, FOR_RECONSTRUCT};
 use style::traversal::{FOR_DEFAULT_STYLES, TraversalDriver, TraversalFlags, UNSTYLED_CHILDREN_ONLY};
 use style::traversal::{resolve_style, resolve_default_style};
 use style::values::{CustomIdent, KeyframesName};
 use style::values::computed::Context;
 use style_traits::ToCss;
--- a/servo/tests/unit/style/keyframes.rs
+++ b/servo/tests/unit/style/keyframes.rs
@@ -1,18 +1,18 @@
 /* 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/. */
 
-use style::keyframes::{Keyframe, KeyframesAnimation, KeyframePercentage,  KeyframeSelector};
-use style::keyframes::{KeyframesStep, KeyframesStepValue};
 use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, Importance};
 use style::properties::animated_properties::TransitionProperty;
 use style::shared_lock::SharedRwLock;
 use style::stylearc::Arc;
+use style::stylesheets::keyframes_rule::{Keyframe, KeyframesAnimation, KeyframePercentage,  KeyframeSelector};
+use style::stylesheets::keyframes_rule::{KeyframesStep, KeyframesStepValue};
 use style::values::specified::{LengthOrPercentageOrAuto, NoCalcLength};
 
 #[test]
 fn test_empty_keyframe() {
     let shared_lock = SharedRwLock::new();
     let keyframes = vec![];
     let animation = KeyframesAnimation::from_keyframes(&keyframes,
                                                        /* vendor_prefix = */ None,
--- a/servo/tests/unit/style/parsing/supports.rs
+++ b/servo/tests/unit/style/parsing/supports.rs
@@ -1,14 +1,14 @@
 /* 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/. */
 
 use cssparser::Parser;
-use style::supports::SupportsCondition;
+use style::stylesheets::supports_rule::SupportsCondition;
 use style_traits::ToCss;
 
 #[test]
 fn test_supports_condition() {
     assert_roundtrip!(SupportsCondition::parse, "(margin: 1px)");
     assert_roundtrip!(SupportsCondition::parse, "not (--be: to be)");
     assert_roundtrip!(SupportsCondition::parse, "(color: blue) and future-extension(4)");
 }
--- a/servo/tests/unit/style/stylesheets.rs
+++ b/servo/tests/unit/style/stylesheets.rs
@@ -10,26 +10,26 @@ use selectors::attr::*;
 use selectors::parser::*;
 use servo_atoms::Atom;
 use servo_url::ServoUrl;
 use std::borrow::ToOwned;
 use std::sync::Mutex;
 use std::sync::atomic::AtomicBool;
 use style::context::QuirksMode;
 use style::error_reporting::ParseErrorReporter;
-use style::keyframes::{Keyframe, KeyframeSelector, KeyframePercentage};
 use style::media_queries::MediaList;
 use style::properties::Importance;
 use style::properties::{CSSWideKeyword, DeclaredValueOwned, PropertyDeclaration, PropertyDeclarationBlock};
 use style::properties::longhands;
 use style::properties::longhands::animation_play_state;
 use style::shared_lock::SharedRwLock;
 use style::stylearc::Arc;
 use style::stylesheets::{Origin, Namespaces};
 use style::stylesheets::{Stylesheet, NamespaceRule, CssRule, CssRules, StyleRule, KeyframesRule};
+use style::stylesheets::keyframes_rule::{Keyframe, KeyframeSelector, KeyframePercentage};
 use style::values::{KeyframesName, CustomIdent};
 use style::values::specified::{LengthOrPercentageOrAuto, Percentage, PositionComponent};
 
 pub fn block_from<I>(iterable: I) -> PropertyDeclarationBlock
 where I: IntoIterator<Item=(PropertyDeclaration, Importance)> {
     let mut block = PropertyDeclarationBlock::new();
     for (d, i) in iterable {
         block.push(d, i)
--- a/servo/tests/unit/style/viewport.rs
+++ b/servo/tests/unit/style/viewport.rs
@@ -8,20 +8,20 @@ use media_queries::CSSErrorReporterTest;
 use servo_config::prefs::{PREFS, PrefValue};
 use servo_url::ServoUrl;
 use style::context::QuirksMode;
 use style::media_queries::{Device, MediaList, MediaType};
 use style::parser::{PARSING_MODE_DEFAULT, Parse, ParserContext};
 use style::shared_lock::SharedRwLock;
 use style::stylearc::Arc;
 use style::stylesheets::{CssRuleType, Stylesheet, Origin};
+use style::stylesheets::viewport_rule::*;
 use style::values::specified::LengthOrPercentageOrAuto::{self, Auto};
 use style::values::specified::NoCalcLength::{self, ViewportPercentage};
 use style::values::specified::ViewportPercentageLength::Vw;
-use style::viewport::*;
 use style_traits::PinchZoomFactor;
 use style_traits::viewport::*;
 
 macro_rules! stylesheet {
     ($css:expr, $origin:ident, $error_reporter:expr) => {
         stylesheet!($css, $origin, $error_reporter, SharedRwLock::new())
     };
     ($css:expr, $origin:ident, $error_reporter:expr, $shared_lock:expr) => {