servo: Merge #17213 - ID and class selectors are ASCII case-insensitive in quirks mode (from servo:quirk-case); r=bholley
authorSimon Sapin <simon.sapin@exyr.org>
Mon, 12 Jun 2017 15:52:29 -0700
changeset 363561 5eca9cc75c3fc7354be4655127f55eefbd7699d2
parent 363560 b4c6f63508ef61e275966d7c5ea853a045d472eb
child 363562 864fa58e6777ea8914939b38981803a5ab6ba4d8
push id32020
push usercbook@mozilla.com
push dateTue, 13 Jun 2017 10:08:40 +0000
treeherdermozilla-central@c8f3df191e35 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
milestone56.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 #17213 - ID and class selectors are ASCII case-insensitive in quirks mode (from servo:quirk-case); r=bholley https://bugzilla.mozilla.org/show_bug.cgi?id=1363778 Source-Repo: https://github.com/servo/servo Source-Revision: 1b077303237d5ecb8307f866e9172d0d8e6b132d
servo/Cargo.lock
servo/components/atoms/static_atoms.txt
servo/components/script/dom/element.rs
servo/components/script/dom/htmlcollection.rs
servo/components/script/dom/window.rs
servo/components/script/layout_wrapper.rs
servo/components/selectors/attr.rs
servo/components/selectors/context.rs
servo/components/selectors/lib.rs
servo/components/selectors/matching.rs
servo/components/selectors/parser.rs
servo/components/selectors/tree.rs
servo/components/style/dom.rs
servo/components/style/gecko/snapshot.rs
servo/components/style/gecko/snapshot_helpers.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/gecko_string_cache/mod.rs
servo/components/style/invalidation/stylesheets.rs
servo/components/style/lib.rs
servo/components/style/restyle_hints.rs
servo/components/style/selector_map.rs
servo/components/style/servo/selector_parser.rs
servo/components/style/stylist.rs
servo/tests/unit/style/restyle_hints.rs
servo/tests/unit/style/stylist.rs
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -1624,17 +1624,17 @@ name = "markup5ever"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
- "string_cache 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "string_cache 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "tendril 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "matches"
 version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2697,17 +2697,17 @@ dependencies = [
  "nodrop 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "servo_atoms"
 version = "0.0.1"
 dependencies = [
- "string_cache 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "string_cache 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "servo_config"
 version = "0.0.1"
 dependencies = [
  "android_injected_glue 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2846,17 +2846,17 @@ name = "smallvec"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "string_cache"
-version = "0.5.1"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "debug_unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3733,17 +3733,17 @@ dependencies = [
 "checksum shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72f20b8f3c060374edb8046591ba28f62448c369ccbdc7b02075103fb3a9e38d"
 "checksum sig 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c6649e43c1a1e68d29ed56d0dc3b5b6cf3b901da77cf107c4066b9e3da036df5"
 "checksum signpost 0.1.0 (git+https://github.com/pcwalton/signpost.git)" = "<none>"
 "checksum simd 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a94d14a2ae1f1f110937de5fb69e494372560181c7e1739a097fcc2cee37ba0"
 "checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537"
 "checksum skeptic 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd7d8dc1315094150052d0ab767840376335a98ac66ef313ff911cdf439a5b69"
 "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23"
 "checksum smallvec 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e40af10aafe98b4d8294ae8388d8a5cd0707c65d364872efe72d063ec44bee0"
-"checksum string_cache 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2c77392ab481a7b315078ae0cbfd827c7fcd7b0840235f0f9c24d8c7443593b5"
+"checksum string_cache 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7c8ba7515dd502b75080d989b819d31fb72686a82320d8006f665003c42ef79"
 "checksum string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "479cde50c3539481f33906a387f2bd17c8e87cb848c35b6021d41fb81ff9b4d7"
 "checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc"
 "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
 "checksum swapper 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca610b32bb8bfc5e7f705480c3a1edfeb70b6582495d343872c8bee0dcf758c"
 "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
 "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
 "checksum synstructure 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cf318c34a2f8381a4f3d4db2c91b45bca2b1cd8cbe56caced900647be164800c"
 "checksum syntex 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a8f5e3aaa79319573d19938ea38d068056b826db9883a5d47f86c1cecc688f0e"
--- a/servo/components/atoms/static_atoms.txt
+++ b/servo/components/atoms/static_atoms.txt
@@ -63,8 +63,10 @@ toggle
 statechange
 controllerchange
 fetch
 characteristicvaluechanged
 fullscreenchange
 fullscreenerror
 gattserverdisconnected
 onchange
+
+reftest-wait
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -80,28 +80,29 @@ use html5ever::serialize;
 use html5ever::serialize::SerializeOpts;
 use html5ever::serialize::TraversalScope;
 use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode};
 use js::jsapi::{HandleValue, JSAutoCompartment};
 use net_traits::request::CorsSettings;
 use ref_filter_map::ref_filter_map;
 use script_layout_interface::message::ReflowQueryType;
 use script_thread::Runnable;
-use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
+use selectors::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
 use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, MatchingMode};
 use selectors::matching::{HAS_EDGE_CHILD_SELECTOR, HAS_SLOW_SELECTOR, HAS_SLOW_SELECTOR_LATER_SIBLINGS};
 use selectors::matching::{RelevantLinkStatus, matches_selector_list};
 use servo_atoms::Atom;
 use std::ascii::AsciiExt;
 use std::borrow::Cow;
 use std::cell::{Cell, Ref};
 use std::convert::TryFrom;
 use std::default::Default;
 use std::fmt;
 use std::rc::Rc;
+use style::CaseSensitivityExt;
 use style::applicable_declarations::ApplicableDeclarationBlock;
 use style::attr::{AttrValue, LengthOrPercentageOrAuto};
 use style::context::{QuirksMode, ReflowGoal};
 use style::element_state::*;
 use style::properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock, parse_style_attribute};
 use style::properties::longhands::{self, background_image, border_spacing, font_family, font_size, overflow_x};
 use style::restyle_hints::RestyleHint;
 use style::rule_tree::CascadeLevel;
@@ -340,17 +341,17 @@ impl RawLayoutElementHelpers for Element
               None
             }
         }).collect()
     }
 }
 
 pub trait LayoutElementHelpers {
     #[allow(unsafe_code)]
-    unsafe fn has_class_for_layout(&self, name: &Atom) -> bool;
+    unsafe fn has_class_for_layout(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool;
     #[allow(unsafe_code)]
     unsafe fn get_classes_for_layout(&self) -> Option<&'static [Atom]>;
 
     #[allow(unsafe_code)]
     unsafe fn synthesize_presentational_hints_for_legacy_attributes<V>(&self, &mut V)
         where V: Push<ApplicableDeclarationBlock>;
     #[allow(unsafe_code)]
     unsafe fn get_colspan(self) -> u32;
@@ -368,19 +369,19 @@ pub trait LayoutElementHelpers {
     fn get_state_for_layout(&self) -> ElementState;
     fn insert_selector_flags(&self, flags: ElementSelectorFlags);
     fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool;
 }
 
 impl LayoutElementHelpers for LayoutJS<Element> {
     #[allow(unsafe_code)]
     #[inline]
-    unsafe fn has_class_for_layout(&self, name: &Atom) -> bool {
+    unsafe fn has_class_for_layout(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
         get_attr_for_layout(&*self.unsafe_get(), &ns!(), &local_name!("class")).map_or(false, |attr| {
-            attr.value_tokens_forever().unwrap().iter().any(|atom| atom == name)
+            attr.value_tokens_forever().unwrap().iter().any(|atom| case_sensitivity.eq_atom(atom, name))
         })
     }
 
     #[allow(unsafe_code)]
     #[inline]
     unsafe fn get_classes_for_layout(&self) -> Option<&'static [Atom]> {
         get_attr_for_layout(&*self.unsafe_get(), &ns!(), &local_name!("class"))
             .map(|attr| attr.value_tokens_forever().unwrap())
@@ -1153,26 +1154,20 @@ impl Element {
             attr.set_owner(None);
             if attr.namespace() == &ns!() {
                 vtable_for(self.upcast()).attribute_mutated(&attr, AttributeMutation::Removed);
             }
             attr
         })
     }
 
-    pub fn has_class(&self, name: &Atom) -> bool {
-        let quirks_mode = document_from_node(self).quirks_mode();
-        let is_equal = |lhs: &Atom, rhs: &Atom| {
-            match quirks_mode {
-                QuirksMode::NoQuirks | QuirksMode::LimitedQuirks => lhs == rhs,
-                QuirksMode::Quirks => lhs.eq_ignore_ascii_case(&rhs),
-            }
-        };
-        self.get_attribute(&ns!(), &local_name!("class"))
-            .map_or(false, |attr| attr.value().as_tokens().iter().any(|atom| is_equal(name, atom)))
+    pub fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
+        self.get_attribute(&ns!(), &local_name!("class")).map_or(false, |attr| {
+            attr.value().as_tokens().iter().any(|atom| case_sensitivity.eq_atom(name, atom))
+        })
     }
 
     pub fn set_atomic_attribute(&self, local_name: &LocalName, value: DOMString) {
         assert!(*local_name == local_name.to_ascii_lowercase());
         let value = AttrValue::from_atomic(value.into());
         self.set_attribute(local_name, value);
     }
 
@@ -2498,22 +2493,22 @@ impl<'a> ::selectors::Element for Root<E
             NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAreaElement)) |
             NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLinkElement)) => {
                 self.has_attribute(&local_name!("href"))
             },
             _ => false,
         }
     }
 
-    fn get_id(&self) -> Option<Atom> {
-        self.id_attribute.borrow().clone()
+    fn has_id(&self, id: &Atom, case_sensitivity: CaseSensitivity) -> bool {
+        self.id_attribute.borrow().as_ref().map_or(false, |atom| case_sensitivity.eq_atom(id, atom))
     }
 
-    fn has_class(&self, name: &Atom) -> bool {
-        Element::has_class(&**self, name)
+    fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
+        Element::has_class(&**self, name, case_sensitivity)
     }
 
     fn is_html_element_in_html_document(&self) -> bool {
         self.html_element_in_html_document()
     }
 }
 
 
--- a/servo/components/script/dom/htmlcollection.rs
+++ b/servo/components/script/dom/htmlcollection.rs
@@ -6,17 +6,17 @@ use dom::bindings::codegen::Bindings::HT
 use dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods;
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::{JS, Root, MutNullableJS};
 use dom::bindings::reflector::{Reflector, reflect_dom_object};
 use dom::bindings::str::DOMString;
 use dom::bindings::trace::JSTraceable;
 use dom::bindings::xmlname::namespace_from_domstring;
 use dom::element::Element;
-use dom::node::Node;
+use dom::node::{Node, document_from_node};
 use dom::window::Window;
 use dom_struct::dom_struct;
 use html5ever::{LocalName, QualName};
 use servo_atoms::Atom;
 use std::cell::Cell;
 use style::str::split_html_space_chars;
 
 pub trait CollectionFilter : JSTraceable {
@@ -194,17 +194,20 @@ impl HTMLCollection {
     pub fn by_atomic_class_name(window: &Window, root: &Node, classes: Vec<Atom>)
                          -> Root<HTMLCollection> {
         #[derive(JSTraceable, HeapSizeOf)]
         struct ClassNameFilter {
             classes: Vec<Atom>
         }
         impl CollectionFilter for ClassNameFilter {
             fn filter(&self, elem: &Element, _root: &Node) -> bool {
-                self.classes.iter().all(|class| elem.has_class(class))
+                let case_sensitivity = document_from_node(elem)
+                    .quirks_mode()
+                    .classes_and_ids_case_sensitivity();
+                self.classes.iter().all(|class| elem.has_class(class, case_sensitivity))
             }
         }
         let filter = ClassNameFilter {
             classes: classes
         };
         HTMLCollection::create(window, root, box filter)
     }
 
--- a/servo/components/script/dom/window.rs
+++ b/servo/components/script/dom/window.rs
@@ -79,17 +79,17 @@ use script_layout_interface::rpc::{Margi
 use script_layout_interface::rpc::{ResolvedStyleResponse, TextIndexResponse};
 use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThreadEventCategory};
 use script_thread::{ImageCacheMsg, MainThreadScriptChan, MainThreadScriptMsg, Runnable};
 use script_thread::{RunnableWrapper, ScriptThread, SendableMainThreadScriptChan};
 use script_traits::{ConstellationControlMsg, DocumentState, LoadData, MozBrowserEvent};
 use script_traits::{ScriptMsg as ConstellationMsg, ScrollState, TimerEvent, TimerEventId};
 use script_traits::{TimerSchedulerMsg, UntrustedNodeAddress, WindowSizeData, WindowSizeType};
 use script_traits::webdriver_msg::{WebDriverJSError, WebDriverJSResult};
-use servo_atoms::Atom;
+use selectors::attr::CaseSensitivity;
 use servo_config::opts;
 use servo_config::prefs::PREFS;
 use servo_geometry::{f32_rect_to_au_rect, max_rect};
 use servo_url::{Host, MutableOrigin, ImmutableOrigin, ServoUrl};
 use std::ascii::AsciiExt;
 use std::borrow::ToOwned;
 use std::cell::Cell;
 use std::collections::{HashMap, HashSet};
@@ -1360,17 +1360,17 @@ impl Window {
             opts::get().exit_after_load ||
             opts::get().webdriver_port.is_some()) && for_display {
             let document = self.Document();
 
             // Checks if the html element has reftest-wait attribute present.
             // See http://testthewebforward.org/docs/reftests.html
             let html_element = document.GetDocumentElement();
             let reftest_wait = html_element.map_or(false, |elem| {
-                elem.has_class(&Atom::from("reftest-wait"))
+                elem.has_class(&atom!("reftest-wait"), CaseSensitivity::CaseSensitive)
             });
 
             let ready_state = document.ReadyState();
 
             let pending_images = self.pending_layout_images.borrow().is_empty();
             if ready_state == DocumentReadyState::Complete && !reftest_wait && pending_images {
                 let global_scope = self.upcast::<GlobalScope>();
                 let event = ConstellationMsg::SetDocumentState(global_scope.pipeline_id(), DocumentState::Idle);
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -44,28 +44,29 @@ use dom::text::Text;
 use gfx_traits::ByteIndex;
 use html5ever::{LocalName, Namespace};
 use msg::constellation_msg::{BrowsingContextId, PipelineId};
 use range::Range;
 use script_layout_interface::{HTMLCanvasData, LayoutNodeType, SVGSVGData, TrustedNodeAddress};
 use script_layout_interface::{OpaqueStyleAndLayoutData, StyleData};
 use script_layout_interface::wrapper_traits::{DangerousThreadSafeLayoutNode, GetLayoutData, LayoutNode};
 use script_layout_interface::wrapper_traits::{PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
-use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
+use selectors::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
 use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, RelevantLinkStatus};
 use selectors::matching::VisitedHandlingMode;
 use servo_atoms::Atom;
 use servo_url::ServoUrl;
 use std::fmt;
 use std::fmt::Debug;
 use std::hash::{Hash, Hasher};
 use std::marker::PhantomData;
 use std::mem::transmute;
 use std::sync::atomic::Ordering;
 use style;
+use style::CaseSensitivityExt;
 use style::applicable_declarations::ApplicableDeclarationBlock;
 use style::attr::AttrValue;
 use style::computed_values::display;
 use style::context::{QuirksMode, SharedStyleContext};
 use style::data::ElementData;
 use style::dom::{DescendantsBit, DirtyDescendants, LayoutIterator, NodeInfo, OpaqueNode};
 use style::dom::{PresentationalHintsSynthesizer, TElement, TNode, UnsafeNode};
 use style::element_state::*;
@@ -409,16 +410,23 @@ impl<'le> TElement for ServoLayoutElemen
         self.element.get_state_for_layout()
     }
 
     #[inline]
     fn has_attr(&self, namespace: &Namespace, attr: &LocalName) -> bool {
         self.get_attr(namespace, attr).is_some()
     }
 
+    #[inline]
+    fn get_id(&self) -> Option<Atom> {
+        unsafe {
+            (*self.element.id_attribute()).clone()
+        }
+    }
+
     #[inline(always)]
     fn each_class<F>(&self, mut callback: F) where F: FnMut(&Atom) {
         unsafe {
             if let Some(ref classes) = self.element.get_classes_for_layout() {
                 for class in *classes {
                     callback(class)
                 }
             }
@@ -774,26 +782,28 @@ impl<'le> ::selectors::Element for Servo
                 NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLinkElement)) =>
                     (*self.element.unsafe_get()).get_attr_val_for_layout(&ns!(), &local_name!("href")).is_some(),
                 _ => false,
             }
         }
     }
 
     #[inline]
-    fn get_id(&self) -> Option<Atom> {
+    fn has_id(&self, id: &Atom, case_sensitivity: CaseSensitivity) -> bool {
         unsafe {
-            (*self.element.id_attribute()).clone()
+            (*self.element.id_attribute())
+                .as_ref()
+                .map_or(false, |atom| case_sensitivity.eq_atom(atom, id))
         }
     }
 
     #[inline]
-    fn has_class(&self, name: &Atom) -> bool {
+    fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
         unsafe {
-            self.element.has_class_for_layout(name)
+            self.element.has_class_for_layout(name, case_sensitivity)
         }
     }
 
     fn is_html_element_in_html_document(&self) -> bool {
         unsafe {
             self.element.html_element_in_html_document_for_layout()
         }
     }
@@ -1244,22 +1254,22 @@ impl<'le> ::selectors::Element for Servo
         false
     }
 
     fn is_link(&self) -> bool {
         warn!("ServoThreadSafeLayoutElement::is_link called");
         false
     }
 
-    fn get_id(&self) -> Option<Atom> {
-        debug!("ServoThreadSafeLayoutElement::get_id called");
-        None
+    fn has_id(&self, _id: &Atom, _case_sensitivity: CaseSensitivity) -> bool {
+        debug!("ServoThreadSafeLayoutElement::has_id called");
+        false
     }
 
-    fn has_class(&self, _name: &Atom) -> bool {
+    fn has_class(&self, _name: &Atom, _case_sensitivity: CaseSensitivity) -> bool {
         debug!("ServoThreadSafeLayoutElement::has_class called");
         false
     }
 
     fn is_empty(&self) -> bool {
         warn!("ServoThreadSafeLayoutElement::is_empty called");
         false
     }
--- a/servo/components/selectors/attr.rs
+++ b/servo/components/selectors/attr.rs
@@ -122,17 +122,17 @@ impl AttrSelectorOperator {
     }
 }
 
 /// The definition of whitespace per CSS Selectors Level 3 ยง 4.
 pub static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C'];
 
 #[derive(Eq, PartialEq, Clone, Copy, Debug)]
 pub enum ParsedCaseSensitivity {
-    CaseSensitive,  // Selectors spec says language-defined, but HTML says sensitive.
+    CaseSensitive,
     AsciiCaseInsensitive,
     AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument,
 }
 
 impl ParsedCaseSensitivity {
     pub fn to_unconditional(self, is_html_element_in_html_document: bool) -> CaseSensitivity {
         match self {
             ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument
@@ -145,17 +145,17 @@ impl ParsedCaseSensitivity {
             ParsedCaseSensitivity::CaseSensitive => CaseSensitivity::CaseSensitive,
             ParsedCaseSensitivity::AsciiCaseInsensitive => CaseSensitivity::AsciiCaseInsensitive,
         }
     }
 }
 
 #[derive(Eq, PartialEq, Clone, Copy, Debug)]
 pub enum CaseSensitivity {
-    CaseSensitive,  // Selectors spec says language-defined, but HTML says sensitive.
+    CaseSensitive,
     AsciiCaseInsensitive,
 }
 
 impl CaseSensitivity {
     pub fn eq(self, a: &[u8], b: &[u8]) -> bool {
         match self {
             CaseSensitivity::CaseSensitive => a == b,
             CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b),
new file mode 100644
--- /dev/null
+++ b/servo/components/selectors/context.rs
@@ -0,0 +1,152 @@
+/* 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 attr::CaseSensitivity;
+use bloom::BloomFilter;
+
+bitflags! {
+    /// Set of flags that determine the different kind of elements affected by
+    /// the selector matching process.
+    ///
+    /// This is used to implement efficient sharing.
+    #[derive(Default)]
+    pub flags StyleRelations: usize {
+        /// Whether this element is affected by presentational hints. This is
+        /// computed externally (that is, in Servo).
+        const AFFECTED_BY_PRESENTATIONAL_HINTS = 1 << 0,
+        /// Whether this element has pseudo-element styles. Computed externally.
+        const AFFECTED_BY_PSEUDO_ELEMENTS = 1 << 1,
+    }
+}
+
+/// What kind of selector matching mode we should use.
+///
+/// There are two modes of selector matching. The difference is only noticeable
+/// in presence of pseudo-elements.
+#[derive(Debug, PartialEq, Copy, Clone)]
+pub enum MatchingMode {
+    /// Don't ignore any pseudo-element selectors.
+    Normal,
+
+    /// Ignores any stateless pseudo-element selectors in the rightmost sequence
+    /// of simple selectors.
+    ///
+    /// This is useful, for example, to match against ::before when you aren't a
+    /// pseudo-element yourself.
+    ///
+    /// For example, in presence of `::before:hover`, it would never match, but
+    /// `::before` would be ignored as in "matching".
+    ///
+    /// It's required for all the selectors you match using this mode to have a
+    /// pseudo-element.
+    ForStatelessPseudoElement,
+}
+
+/// The mode to use when matching unvisited and visited links.
+#[derive(PartialEq, Eq, Copy, Clone, Debug)]
+pub enum VisitedHandlingMode {
+    /// All links are matched as if they are unvisted.
+    AllLinksUnvisited,
+    /// A element's "relevant link" is the element being matched if it is a link
+    /// or the nearest ancestor link. The relevant link is matched as though it
+    /// is visited, and all other links are matched as if they are unvisited.
+    RelevantLinkVisited,
+}
+
+/// Which quirks mode is this document in.
+///
+/// See: https://quirks.spec.whatwg.org/
+#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]
+pub enum QuirksMode {
+    /// Quirks mode.
+    Quirks,
+    /// Limited quirks mode.
+    LimitedQuirks,
+    /// No quirks mode.
+    NoQuirks,
+}
+
+impl QuirksMode {
+    #[inline]
+    pub fn classes_and_ids_case_sensitivity(self) -> CaseSensitivity {
+        match self {
+            QuirksMode::NoQuirks |
+            QuirksMode::LimitedQuirks => CaseSensitivity::CaseSensitive,
+            QuirksMode::Quirks => CaseSensitivity::AsciiCaseInsensitive,
+        }
+    }
+}
+
+/// Data associated with the matching process for a element.  This context is
+/// used across many selectors for an element, so it's not appropriate for
+/// transient data that applies to only a single selector.
+#[derive(Clone)]
+pub struct MatchingContext<'a> {
+    /// Output that records certains relations between elements noticed during
+    /// matching (and also extended after matching).
+    pub relations: StyleRelations,
+    /// Input with the matching mode we should use when matching selectors.
+    pub matching_mode: MatchingMode,
+    /// Input with the bloom filter used to fast-reject selectors.
+    pub bloom_filter: Option<&'a BloomFilter>,
+    /// Input that controls how matching for links is handled.
+    pub visited_handling: VisitedHandlingMode,
+    /// Output that records whether we encountered a "relevant link" while
+    /// matching _any_ selector for this element. (This differs from
+    /// `RelevantLinkStatus` which tracks the status for the _current_ selector
+    /// only.)
+    pub relevant_link_found: bool,
+
+    quirks_mode: QuirksMode,
+    classes_and_ids_case_sensitivity: CaseSensitivity,
+}
+
+impl<'a> MatchingContext<'a> {
+    /// Constructs a new `MatchingContext`.
+    pub fn new(matching_mode: MatchingMode,
+               bloom_filter: Option<&'a BloomFilter>,
+               quirks_mode: QuirksMode)
+               -> Self
+    {
+        Self {
+            relations: StyleRelations::empty(),
+            matching_mode: matching_mode,
+            bloom_filter: bloom_filter,
+            visited_handling: VisitedHandlingMode::AllLinksUnvisited,
+            relevant_link_found: false,
+            quirks_mode: quirks_mode,
+            classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(),
+        }
+    }
+
+    /// Constructs a new `MatchingContext` for use in visited matching.
+    pub fn new_for_visited(matching_mode: MatchingMode,
+                           bloom_filter: Option<&'a BloomFilter>,
+                           visited_handling: VisitedHandlingMode,
+                           quirks_mode: QuirksMode)
+                           -> Self
+    {
+        Self {
+            relations: StyleRelations::empty(),
+            matching_mode: matching_mode,
+            bloom_filter: bloom_filter,
+            visited_handling: visited_handling,
+            relevant_link_found: false,
+            quirks_mode: quirks_mode,
+            classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(),
+        }
+    }
+
+    /// The quirks mode of the document.
+    #[inline]
+    pub fn quirks_mode(&self) -> QuirksMode {
+        self.quirks_mode
+    }
+
+    /// The case-sensitivity for class and ID selectors
+    #[inline]
+    pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity {
+        self.classes_and_ids_case_sensitivity
+    }
+}
--- a/servo/components/selectors/lib.rs
+++ b/servo/components/selectors/lib.rs
@@ -10,16 +10,17 @@ extern crate fnv;
 extern crate phf;
 extern crate precomputed_hash;
 #[cfg(test)] #[macro_use] extern crate size_of_test;
 extern crate servo_arc;
 extern crate smallvec;
 
 pub mod attr;
 pub mod bloom;
+pub mod context;
 pub mod matching;
 pub mod parser;
 #[cfg(test)] mod size_of_tests;
 #[cfg(any(test, feature = "gecko_like_types"))] pub mod gecko_like_types;
 mod tree;
 pub mod visitor;
 
 pub use parser::{SelectorImpl, Parser, SelectorList};
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -4,37 +4,24 @@
 
 use attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstraint};
 use bloom::{BLOOM_HASH_MASK, BloomFilter};
 use parser::{AncestorHashes, Combinator, Component, LocalName};
 use parser::{Selector, SelectorImpl, SelectorIter, SelectorList};
 use std::borrow::Borrow;
 use tree::Element;
 
+pub use context::*;
+
 // The bloom filter for descendant CSS selectors will have a <1% false
 // positive rate until it has this many selectors in it, then it will
 // rapidly increase.
 pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: usize = 4096;
 
 bitflags! {
-    /// Set of flags that determine the different kind of elements affected by
-    /// the selector matching process.
-    ///
-    /// This is used to implement efficient sharing.
-    #[derive(Default)]
-    pub flags StyleRelations: usize {
-        /// Whether this element is affected by presentational hints. This is
-        /// computed externally (that is, in Servo).
-        const AFFECTED_BY_PRESENTATIONAL_HINTS = 1 << 0,
-        /// Whether this element has pseudo-element styles. Computed externally.
-        const AFFECTED_BY_PSEUDO_ELEMENTS = 1 << 1,
-    }
-}
-
-bitflags! {
     /// Set of flags that are set on either the element or its parent (depending
     /// on the flag) if the element could potentially match a selector.
     pub flags ElementSelectorFlags: usize {
         /// When a child is added or removed from the parent, all the children
         /// must be restyled, because they may match :nth-last-child,
         /// :last-of-type, :nth-last-of-type, or :only-of-type.
         const HAS_SLOW_SELECTOR = 1 << 0,
 
@@ -61,121 +48,16 @@ impl ElementSelectorFlags {
     }
 
     /// Returns the subset of flags that apply to the parent.
     pub fn for_parent(self) -> ElementSelectorFlags {
         self & (HAS_SLOW_SELECTOR | HAS_SLOW_SELECTOR_LATER_SIBLINGS | HAS_EDGE_CHILD_SELECTOR)
     }
 }
 
-/// What kind of selector matching mode we should use.
-///
-/// There are two modes of selector matching. The difference is only noticeable
-/// in presence of pseudo-elements.
-#[derive(Debug, PartialEq, Copy, Clone)]
-pub enum MatchingMode {
-    /// Don't ignore any pseudo-element selectors.
-    Normal,
-
-    /// Ignores any stateless pseudo-element selectors in the rightmost sequence
-    /// of simple selectors.
-    ///
-    /// This is useful, for example, to match against ::before when you aren't a
-    /// pseudo-element yourself.
-    ///
-    /// For example, in presence of `::before:hover`, it would never match, but
-    /// `::before` would be ignored as in "matching".
-    ///
-    /// It's required for all the selectors you match using this mode to have a
-    /// pseudo-element.
-    ForStatelessPseudoElement,
-}
-
-/// The mode to use when matching unvisited and visited links.
-#[derive(PartialEq, Eq, Copy, Clone, Debug)]
-pub enum VisitedHandlingMode {
-    /// All links are matched as if they are unvisted.
-    AllLinksUnvisited,
-    /// A element's "relevant link" is the element being matched if it is a link
-    /// or the nearest ancestor link. The relevant link is matched as though it
-    /// is visited, and all other links are matched as if they are unvisited.
-    RelevantLinkVisited,
-}
-
-/// Which quirks mode is this document in.
-///
-/// See: https://quirks.spec.whatwg.org/
-#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]
-pub enum QuirksMode {
-    /// Quirks mode.
-    Quirks,
-    /// Limited quirks mode.
-    LimitedQuirks,
-    /// No quirks mode.
-    NoQuirks,
-}
-
-/// Data associated with the matching process for a element.  This context is
-/// used across many selectors for an element, so it's not appropriate for
-/// transient data that applies to only a single selector.
-#[derive(Clone)]
-pub struct MatchingContext<'a> {
-    /// Output that records certains relations between elements noticed during
-    /// matching (and also extended after matching).
-    pub relations: StyleRelations,
-    /// Input with the matching mode we should use when matching selectors.
-    pub matching_mode: MatchingMode,
-    /// Input with the bloom filter used to fast-reject selectors.
-    pub bloom_filter: Option<&'a BloomFilter>,
-    /// Input that controls how matching for links is handled.
-    pub visited_handling: VisitedHandlingMode,
-    /// Output that records whether we encountered a "relevant link" while
-    /// matching _any_ selector for this element. (This differs from
-    /// `RelevantLinkStatus` which tracks the status for the _current_ selector
-    /// only.)
-    pub relevant_link_found: bool,
-    /// The quirks mode of the document.
-    pub quirks_mode: QuirksMode,
-}
-
-impl<'a> MatchingContext<'a> {
-    /// Constructs a new `MatchingContext`.
-    pub fn new(matching_mode: MatchingMode,
-               bloom_filter: Option<&'a BloomFilter>,
-               quirks_mode: QuirksMode)
-               -> Self
-    {
-        Self {
-            relations: StyleRelations::empty(),
-            matching_mode: matching_mode,
-            bloom_filter: bloom_filter,
-            visited_handling: VisitedHandlingMode::AllLinksUnvisited,
-            relevant_link_found: false,
-            quirks_mode: quirks_mode,
-        }
-    }
-
-    /// Constructs a new `MatchingContext` for use in visited matching.
-    pub fn new_for_visited(matching_mode: MatchingMode,
-                           bloom_filter: Option<&'a BloomFilter>,
-                           visited_handling: VisitedHandlingMode,
-                           quirks_mode: QuirksMode)
-                           -> Self
-    {
-        Self {
-            relations: StyleRelations::empty(),
-            matching_mode: matching_mode,
-            bloom_filter: bloom_filter,
-            visited_handling: visited_handling,
-            relevant_link_found: false,
-            quirks_mode: quirks_mode,
-        }
-    }
-}
-
 /// Holds per-element data alongside a pointer to MatchingContext.
 pub struct LocalMatchingContext<'a, 'b: 'a, Impl: SelectorImpl> {
     /// Shared `MatchingContext`.
     pub shared: &'a mut MatchingContext<'b>,
     /// A reference to the base selector we're matching against.
     pub selector: &'a Selector<Impl>,
     /// The offset of the current compound selector being matched, kept up to date by
     /// the callees when the iterator is advanced. This, in conjunction with the selector
@@ -203,25 +85,25 @@ impl<'a, 'b, Impl> LocalMatchingContext<
             offset: 0,
             within_functional_pseudo_class_argument: false,
         }
     }
 
     /// Updates offset of Selector to show new compound selector.
     /// To be able to correctly re-synthesize main SelectorIter.
     pub fn note_next_sequence(&mut self, selector_iter: &SelectorIter<Impl>) {
-        if let QuirksMode::Quirks = self.shared.quirks_mode {
+        if let QuirksMode::Quirks = self.shared.quirks_mode() {
             self.offset = self.selector.len() - selector_iter.selector_length();
         }
     }
 
     /// Returns true if current compound selector matches :active and :hover quirk.
     /// https://quirks.spec.whatwg.org/#the-active-and-hover-quirk
     pub fn active_hover_quirk_matches(&mut self) -> bool {
-        if self.shared.quirks_mode != QuirksMode::Quirks ||
+        if self.shared.quirks_mode() != QuirksMode::Quirks ||
            self.within_functional_pseudo_class_argument {
             return false;
         }
 
         let mut iter = if self.offset == 0 {
             self.selector.iter()
         } else {
             self.selector.iter_from(self.offset)
@@ -661,22 +543,21 @@ fn matches_simple_selector<E, F>(
         Component::Namespace(_, ref url) |
         Component::DefaultNamespace(ref url) => {
             element.get_namespace() == url.borrow()
         }
         Component::ExplicitNoNamespace => {
             let ns = ::parser::namespace_empty_string::<E::Impl>();
             element.get_namespace() == ns.borrow()
         }
-        // TODO: case-sensitivity depends on the document type and quirks mode
         Component::ID(ref id) => {
-            element.get_id().map_or(false, |attr| attr == *id)
+            element.has_id(id, context.shared.classes_and_ids_case_sensitivity())
         }
         Component::Class(ref class) => {
-            element.has_class(class)
+            element.has_class(class, context.shared.classes_and_ids_case_sensitivity())
         }
         Component::AttributeInNoNamespaceExists { ref local_name, ref local_name_lower } => {
             let is_html = element.is_html_element_in_html_document();
             element.attr_matches(
                 &NamespaceConstraint::Specific(&::parser::namespace_empty_string::<E::Impl>()),
                 select_name(is_html, local_name, local_name_lower),
                 &AttrSelectorOperation::Exists
             )
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -1380,17 +1380,20 @@ fn parse_attribute_selector<'i, 't, P, E
     }
 }
 
 
 fn parse_attribute_flags<'i, 't, E>(input: &mut CssParser<'i, 't>)
                                     -> Result<ParsedCaseSensitivity,
                                               ParseError<'i, SelectorParseError<'i, E>>> {
     match input.next() {
-        Err(_) => Ok(ParsedCaseSensitivity::CaseSensitive),
+        Err(_) => {
+            // Selectors spec says language-defined, but HTML says sensitive.
+            Ok(ParsedCaseSensitivity::CaseSensitive)
+        }
         Ok(Token::Ident(ref value)) if value.eq_ignore_ascii_case("i") => {
             Ok(ParsedCaseSensitivity::AsciiCaseInsensitive)
         }
         Ok(t) => Err(ParseError::Basic(BasicParseError::UnexpectedToken(t)))
     }
 }
 
 
--- a/servo/components/selectors/tree.rs
+++ b/servo/components/selectors/tree.rs
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Traits that nodes must implement. Breaks the otherwise-cyclic dependency
 //! between layout and style.
 
-use attr::{AttrSelectorOperation, NamespaceConstraint};
+use attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
 use matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, RelevantLinkStatus};
 use parser::SelectorImpl;
 use std::fmt::Debug;
 
 pub trait Element: Sized + Debug {
     type Impl: SelectorImpl;
 
     fn parent_element(&self) -> Option<Self>;
@@ -58,19 +58,25 @@ pub trait Element: Sized + Debug {
     fn match_pseudo_element(&self,
                             pe: &<Self::Impl as SelectorImpl>::PseudoElement,
                             context: &mut MatchingContext)
                             -> bool;
 
     /// Whether this element is a `link`.
     fn is_link(&self) -> bool;
 
-    fn get_id(&self) -> Option<<Self::Impl as SelectorImpl>::Identifier>;
+    fn has_id(&self,
+              id: &<Self::Impl as SelectorImpl>::Identifier,
+              case_sensitivity: CaseSensitivity)
+              -> bool;
 
-    fn has_class(&self, name: &<Self::Impl as SelectorImpl>::ClassName) -> bool;
+    fn has_class(&self,
+                 name: &<Self::Impl as SelectorImpl>::ClassName,
+                 case_sensitivity: CaseSensitivity)
+                 -> bool;
 
     /// Returns whether this element matches `:empty`.
     ///
     /// That is, whether it does not contain any child element or any non-zero-length text node.
     /// See http://dev.w3.org/csswg/selectors-3/#empty-pseudo
     fn is_empty(&self) -> bool;
 
     /// Returns whether this element matches `:root`,
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -371,16 +371,19 @@ pub trait TElement : Eq + PartialEq + De
     }
 
     /// Get this element's state, for non-tree-structural pseudos.
     fn get_state(&self) -> ElementState;
 
     /// Whether this element has an attribute with a given namespace.
     fn has_attr(&self, namespace: &Namespace, attr: &LocalName) -> bool;
 
+    /// The ID for this element.
+    fn get_id(&self) -> Option<Atom>;
+
     /// Internal iterator for the classes of this element.
     fn each_class<F>(&self, callback: F) where F: FnMut(&Atom);
 
     /// Get the pre-existing style to calculate restyle damage (change hints).
     ///
     /// This needs to be generic since it varies between Servo and Gecko.
     ///
     /// XXX(emilio): It's a bit unfortunate we need to pass the current computed
--- a/servo/components/style/gecko/snapshot.rs
+++ b/servo/components/style/gecko/snapshot.rs
@@ -159,23 +159,24 @@ impl ElementSnapshot for GeckoElementSna
         if ptr.is_null() {
             None
         } else {
             Some(Atom::from(ptr))
         }
     }
 
     #[inline]
-    fn has_class(&self, name: &Atom) -> bool {
+    fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
         if !self.has_any(Flags::MaybeClass) {
             return false;
         }
 
         snapshot_helpers::has_class(self.as_ptr(),
                                     name,
+                                    case_sensitivity,
                                     bindings::Gecko_SnapshotClassOrClassList)
     }
 
     #[inline]
     fn each_class<F>(&self, callback: F)
         where F: FnMut(&Atom)
     {
         if !self.has_any(Flags::MaybeClass) {
--- a/servo/components/style/gecko/snapshot_helpers.rs
+++ b/servo/components/style/gecko/snapshot_helpers.rs
@@ -1,38 +1,42 @@
 /* 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/. */
 
 //! Element an snapshot common logic.
 
+use CaseSensitivityExt;
 use gecko_bindings::structs::nsIAtom;
+use gecko_string_cache::WeakAtom;
+use selectors::attr::CaseSensitivity;
 use std::{ptr, slice};
 use string_cache::Atom;
 
 /// A function that, given an element of type `T`, allows you to get a single
 /// class or a class list.
 pub type ClassOrClassList<T> = unsafe extern fn (T, *mut *mut nsIAtom, *mut *mut *mut nsIAtom) -> u32;
 
 /// Given an item `T`, a class name, and a getter function, return whether that
 /// element has the class that `name` represents.
 pub fn has_class<T>(item: T,
                     name: &Atom,
+                    case_sensitivity: CaseSensitivity,
                     getter: ClassOrClassList<T>) -> bool
 {
     unsafe {
         let mut class: *mut nsIAtom = ptr::null_mut();
         let mut list: *mut *mut nsIAtom = ptr::null_mut();
         let length = getter(item, &mut class, &mut list);
         match length {
             0 => false,
-            1 => name.as_ptr() == class,
+            1 => case_sensitivity.eq_atom(name, WeakAtom::new(class)),
             n => {
                 let classes = slice::from_raw_parts(list, n as usize);
-                classes.iter().any(|ptr| name.as_ptr() == *ptr)
+                classes.iter().any(|ptr| case_sensitivity.eq_atom(name, WeakAtom::new(*ptr)))
             }
         }
     }
 }
 
 
 /// Given an item, a callback, and a getter, execute `callback` for each class
 /// this `item` has.
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -9,16 +9,17 @@
 //!
 //! This really follows the Servo pattern in
 //! `components/script/layout_wrapper.rs`.
 //!
 //! This theoretically should live in its own crate, but now it lives in the
 //! style system it's kind of pointless in the Stylo case, and only Servo forces
 //! the separation between the style system implementation and everything else.
 
+use CaseSensitivityExt;
 use app_units::Au;
 use applicable_declarations::ApplicableDeclarationBlock;
 use atomic_refcell::AtomicRefCell;
 use context::{QuirksMode, SharedStyleContext, UpdateAnimationsTasks};
 use data::ElementData;
 use dom::{self, DescendantsBit, LayoutIterator, NodeInfo, TElement, TNode, UnsafeNode};
 use dom::{OpaqueNode, PresentationalHintsSynthesizer};
 use element_state::ElementState;
@@ -739,16 +740,33 @@ impl<'le> TElement for GeckoElement<'le>
     fn has_attr(&self, namespace: &Namespace, attr: &Atom) -> bool {
         unsafe {
             bindings::Gecko_HasAttr(self.0,
                                     namespace.0.as_ptr(),
                                     attr.as_ptr())
         }
     }
 
+    fn get_id(&self) -> Option<Atom> {
+        if !self.has_id() {
+            return None
+        }
+
+        let ptr = unsafe {
+            bindings::Gecko_AtomAttrValue(self.0,
+                                          atom!("id").as_ptr())
+        };
+
+        if ptr.is_null() {
+            None
+        } else {
+            Some(Atom::from(ptr))
+        }
+    }
+
     fn each_class<F>(&self, callback: F)
         where F: FnMut(&Atom)
     {
         snapshot_helpers::each_class(self.0,
                                      callback,
                                      Gecko_ClassOrClassList)
     }
 
@@ -1569,40 +1587,40 @@ impl<'le> ::selectors::Element for Gecko
         }
     }
 
     #[inline]
     fn is_link(&self) -> bool {
         self.get_state().intersects(NonTSPseudoClass::AnyLink.state_flag())
     }
 
-    fn get_id(&self) -> Option<Atom> {
+    fn has_id(&self, id: &Atom, case_sensitivity: CaseSensitivity) -> bool {
         if !self.has_id() {
-            return None;
+            return false
         }
 
-        let ptr = unsafe {
-            bindings::Gecko_AtomAttrValue(self.0,
-                                          atom!("id").as_ptr())
-        };
+        unsafe {
+            let ptr = bindings::Gecko_AtomAttrValue(self.0, atom!("id").as_ptr());
 
-        if ptr.is_null() {
-            None
-        } else {
-            Some(Atom::from(ptr))
+            if ptr.is_null() {
+                false
+            } else {
+                case_sensitivity.eq_atom(WeakAtom::new(ptr), id)
+            }
         }
     }
 
-    fn has_class(&self, name: &Atom) -> bool {
+    fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
         if !self.may_have_class() {
             return false;
         }
 
         snapshot_helpers::has_class(self.0,
                                     name,
+                                    case_sensitivity,
                                     Gecko_ClassOrClassList)
     }
 
     fn is_html_element_in_html_document(&self) -> bool {
         let node = self.as_node();
         let node_info = node.node_info();
         node_info.mInner.mNamespaceID == (structs::root::kNameSpaceID_XHTML as i32) &&
         node.owner_doc().mType == structs::root::nsIDocument_Type::eHTML
--- a/servo/components/style/gecko_string_cache/mod.rs
+++ b/servo/components/style/gecko_string_cache/mod.rs
@@ -6,17 +6,17 @@
 
 //! A drop-in replacement for string_cache, but backed by Gecko `nsIAtom`s.
 
 use gecko_bindings::bindings::Gecko_AddRefAtom;
 use gecko_bindings::bindings::Gecko_Atomize;
 use gecko_bindings::bindings::Gecko_Atomize16;
 use gecko_bindings::bindings::Gecko_ReleaseAtom;
 use gecko_bindings::structs::nsIAtom;
-use nsstring::nsAString;
+use nsstring::{nsAString, nsString};
 use precomputed_hash::PrecomputedHash;
 use std::ascii::AsciiExt;
 use std::borrow::{Cow, Borrow};
 use std::char::{self, DecodeUtf16};
 use std::fmt::{self, Write};
 use std::hash::{Hash, Hasher};
 use std::iter::Cloned;
 use std::mem;
@@ -171,16 +171,64 @@ impl WeakAtom {
     }
 
     /// Returns the atom as a mutable pointer.
     #[inline]
     pub fn as_ptr(&self) -> *mut nsIAtom {
         let const_ptr: *const nsIAtom = &self.0;
         const_ptr as *mut nsIAtom
     }
+
+    /// Convert this atom to ASCII lower-case
+    pub fn to_ascii_lowercase(&self) -> Atom {
+        let slice = self.as_slice();
+        match slice.iter().position(|&char16| (b'A' as u16) <= char16 && char16 <= (b'Z' as u16)) {
+            None => self.clone(),
+            Some(i) => {
+                let mut buffer: [u16; 64] = unsafe { mem::uninitialized() };
+                let mut vec;
+                let mutable_slice = if let Some(buffer_prefix) = buffer.get_mut(..slice.len()) {
+                    buffer_prefix.copy_from_slice(slice);
+                    buffer_prefix
+                } else {
+                    vec = slice.to_vec();
+                    &mut vec
+                };
+                for char16 in &mut mutable_slice[i..] {
+                    if *char16 <= 0x7F {
+                        *char16 = (*char16 as u8).to_ascii_lowercase() as u16
+                    }
+                }
+                Atom::from(&*mutable_slice)
+            }
+        }
+    }
+
+    /// Return whether two atoms are ASCII-case-insensitive matches
+    pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
+        if self == other {
+            return true;
+        }
+
+        let a = self.as_slice();
+        let b = other.as_slice();
+        a.len() == b.len() && a.iter().zip(b).all(|(&a16, &b16)| {
+            if a16 <= 0x7F && b16 <= 0x7F {
+                (a16 as u8).eq_ignore_ascii_case(&(b16 as u8))
+            } else {
+                a16 == b16
+            }
+        })
+    }
+
+    /// Return whether this atom is an ASCII-case-insensitive match for the given string
+    pub fn eq_str_ignore_ascii_case(&self, other: &str) -> bool {
+        self.chars().map(|r| r.map(|c: char| c.to_ascii_lowercase()))
+        .eq(other.chars().map(|c: char| Ok(c.to_ascii_lowercase())))
+    }
 }
 
 impl fmt::Debug for WeakAtom {
     fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
         write!(w, "Gecko WeakAtom({:p}, {})", self, self)
     }
 }
 
@@ -228,39 +276,16 @@ impl Atom {
 
     /// Convert this atom into an addrefed nsIAtom pointer.
     #[inline]
     pub fn into_addrefed(self) -> *mut nsIAtom {
         let ptr = self.as_ptr();
         mem::forget(self);
         ptr
     }
-
-    /// Return whether two atoms are ASCII-case-insensitive matches
-    pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
-        if self == other {
-            return true;
-        }
-
-        let a = self.as_slice();
-        let b = other.as_slice();
-        a.len() == b.len() && a.iter().zip(b).all(|(&a16, &b16)| {
-            if a16 <= 0x7F && b16 <= 0x7F {
-                (a16 as u8).eq_ignore_ascii_case(&(b16 as u8))
-            } else {
-                a16 == b16
-            }
-        })
-    }
-
-    /// Return whether this atom is an ASCII-case-insensitive match for the given string
-    pub fn eq_str_ignore_ascii_case(&self, other: &str) -> bool {
-        self.chars().map(|r| r.map(|c: char| c.to_ascii_lowercase()))
-        .eq(other.chars().map(|c: char| Ok(c.to_ascii_lowercase())))
-    }
 }
 
 impl Hash for Atom {
     fn hash<H>(&self, state: &mut H) where H: Hasher {
         state.write_u32(self.get_hash());
     }
 }
 
@@ -316,16 +341,23 @@ impl<'a> From<&'a str> for Atom {
         unsafe {
             Atom(WeakAtom::new(
                 Gecko_Atomize(string.as_ptr() as *const _, string.len() as u32)
             ))
         }
     }
 }
 
+impl<'a> From<&'a [u16]> for Atom {
+    #[inline]
+    fn from(slice: &[u16]) -> Atom {
+        Atom::from(&*nsString::from(slice))
+    }
+}
+
 impl<'a> From<&'a nsAString> for Atom {
     #[inline]
     fn from(string: &nsAString) -> Atom {
         unsafe {
             Atom(WeakAtom::new(
                 Gecko_Atomize16(string)
             ))
         }
--- a/servo/components/style/invalidation/stylesheets.rs
+++ b/servo/components/style/invalidation/stylesheets.rs
@@ -7,16 +7,17 @@
 
 #![deny(unsafe_code)]
 
 use Atom;
 use data::StoredRestyleHint;
 use dom::{TElement, TNode};
 use fnv::FnvHashSet;
 use selector_parser::SelectorImpl;
+use selectors::attr::CaseSensitivity;
 use selectors::parser::{Component, Selector};
 use shared_lock::SharedRwLockReadGuard;
 use stylesheets::{CssRule, Stylesheet};
 use stylist::Stylist;
 
 /// An invalidation scope represents a kind of subtree that may need to be
 /// restyled.
 #[derive(Debug, Hash, Eq, PartialEq)]
@@ -32,17 +33,17 @@ impl InvalidationScope {
         matches!(*self, InvalidationScope::ID(..))
     }
 
     fn matches<E>(&self, element: E) -> bool
         where E: TElement,
     {
         match *self {
             InvalidationScope::Class(ref class) => {
-                element.has_class(class)
+                element.has_class(class, CaseSensitivity::CaseSensitive)
             }
             InvalidationScope::ID(ref id) => {
                 match element.get_id() {
                     Some(element_id) => element_id == *id,
                     None => false,
                 }
             }
         }
--- a/servo/components/style/lib.rs
+++ b/servo/components/style/lib.rs
@@ -212,8 +212,26 @@ pub fn serialize_comma_separated_list<W,
 
     for item in list.iter().skip(1) {
         try!(write!(dest, ", "));
         try!(item.to_css(dest));
     }
 
     Ok(())
 }
+
+#[cfg(feature = "gecko")] use gecko_string_cache::WeakAtom;
+#[cfg(feature = "servo")] use servo_atoms::Atom as WeakAtom;
+
+/// Extension methods for selectors::attr::CaseSensitivity
+pub trait CaseSensitivityExt {
+    /// Return whether two atoms compare equal according to this case sensitivity.
+    fn eq_atom(self, a: &WeakAtom, b: &WeakAtom) -> bool;
+}
+
+impl CaseSensitivityExt for selectors::attr::CaseSensitivity {
+    fn eq_atom(self, a: &WeakAtom, b: &WeakAtom) -> bool {
+        match self {
+            selectors::attr::CaseSensitivity::CaseSensitive => a == b,
+            selectors::attr::CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b),
+        }
+    }
+}
--- a/servo/components/style/restyle_hints.rs
+++ b/servo/components/style/restyle_hints.rs
@@ -2,29 +2,30 @@
  * 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/. */
 
 //! Restyle hints: an optimization to avoid unnecessarily matching selectors.
 
 #![deny(missing_docs)]
 
 use Atom;
+use CaseSensitivityExt;
 use LocalName;
 use Namespace;
-use context::{SharedStyleContext, ThreadLocalStyleContext};
+use context::{SharedStyleContext, ThreadLocalStyleContext, QuirksMode};
 use dom::TElement;
 use element_state::*;
 #[cfg(feature = "gecko")]
 use gecko_bindings::structs::nsRestyleHint;
 #[cfg(feature = "servo")]
 use heapsize::HeapSizeOf;
 use selector_map::{SelectorMap, SelectorMapEntry};
 use selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap, AttrValue};
 use selectors::Element;
-use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
+use selectors::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
 use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, MatchingMode};
 use selectors::matching::{RelevantLinkStatus, VisitedHandlingMode, matches_selector};
 use selectors::parser::{AncestorHashes, Combinator, Component};
 use selectors::parser::{Selector, SelectorAndHashes, SelectorIter, SelectorMethods};
 use selectors::visitor::SelectorVisitor;
 use smallvec::SmallVec;
 use std::cell::Cell;
 use std::clone::Clone;
@@ -544,17 +545,17 @@ pub trait ElementSnapshot : Sized {
     fn has_attrs(&self) -> bool;
 
     /// The ID attribute per this snapshot. Should only be called if
     /// `has_attrs()` returns true.
     fn id_attr(&self) -> Option<Atom>;
 
     /// Whether this snapshot contains the class `name`. Should only be called
     /// if `has_attrs()` returns true.
-    fn has_class(&self, name: &Atom) -> bool;
+    fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool;
 
     /// A callback that should be called for each class of the snapshot. Should
     /// only be called if `has_attrs()` returns true.
     fn each_class<F>(&self, F)
         where F: FnMut(&Atom);
 
     /// The `xml:lang=""` or `lang=""` attribute value per this snapshot.
     fn lang_attr(&self) -> Option<AttrValue>;
@@ -815,29 +816,31 @@ impl<'a, E> Element for ElementWrapper<'
         match self.snapshot() {
             Some(snapshot) if snapshot.has_attrs() => {
                 snapshot.attr_matches(ns, local_name, operation)
             }
             _ => self.element.attr_matches(ns, local_name, operation)
         }
     }
 
-    fn get_id(&self) -> Option<Atom> {
+    fn has_id(&self, id: &Atom, case_sensitivity: CaseSensitivity) -> bool {
         match self.snapshot() {
-            Some(snapshot) if snapshot.has_attrs()
-                => snapshot.id_attr(),
-            _   => self.element.get_id()
+            Some(snapshot) if snapshot.has_attrs() => {
+                snapshot.id_attr().map_or(false, |atom| case_sensitivity.eq_atom(&atom, id))
+            }
+            _ => self.element.has_id(id, case_sensitivity)
         }
     }
 
-    fn has_class(&self, name: &Atom) -> bool {
+    fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
         match self.snapshot() {
-            Some(snapshot) if snapshot.has_attrs()
-                => snapshot.has_class(name),
-            _   => self.element.has_class(name)
+            Some(snapshot) if snapshot.has_attrs() => {
+                snapshot.has_class(name, case_sensitivity)
+            }
+            _ => self.element.has_class(name, case_sensitivity)
         }
     }
 
     fn is_empty(&self) -> bool {
         self.element.is_empty()
     }
 
     fn is_root(&self) -> bool {
@@ -1001,17 +1004,18 @@ pub enum HintComputationContext<'a, E: '
         local_context: &'a mut ThreadLocalStyleContext<E>,
         /// The dom depth of this element.
         dom_depth: usize,
     }
 }
 
 impl DependencySet {
     /// Adds a selector to this `DependencySet`.
-    pub fn note_selector(&mut self, selector_and_hashes: &SelectorAndHashes<SelectorImpl>) {
+    pub fn note_selector(&mut self, selector_and_hashes: &SelectorAndHashes<SelectorImpl>,
+                         quirks_mode: QuirksMode) {
         let mut combinator = None;
         let mut iter = selector_and_hashes.selector.iter();
         let mut index = 0;
         let mut child_combinators_seen = 0;
         let mut saw_descendant_combinator = false;
 
         loop {
             let sequence_start = index;
@@ -1068,17 +1072,17 @@ impl DependencySet {
                 };
 
                 self.dependencies.insert(Dependency {
                     sensitivities: visitor.sensitivities,
                     hint: hint,
                     selector: selector_and_hashes.selector.clone(),
                     selector_offset: sequence_start,
                     hashes: hashes,
-                });
+                }, quirks_mode);
             }
 
             combinator = iter.next_sequence();
             if combinator.is_none() {
                 break;
             }
 
             index += 1; // Account for the combinator.
@@ -1144,17 +1148,17 @@ impl DependencySet {
         let mut additional_classes = SmallVec::<[Atom; 8]>::new();
         if attrs_changed {
             let id = snapshot.id_attr();
             if id.is_some() && id != el.get_id() {
                 additional_id = id;
             }
 
             snapshot.each_class(|c| {
-                if !el.has_class(c) {
+                if !el.has_class(c, CaseSensitivity::CaseSensitive) {
                     additional_classes.push(c.clone())
                 }
             });
         }
 
         let bloom_filter = match hint_context {
             HintComputationContext::Root => None,
             HintComputationContext::Child { mut local_context, dom_depth } => {
@@ -1167,18 +1171,19 @@ impl DependencySet {
         };
 
         let lookup_element = if el.implemented_pseudo_element().is_some() {
             el.closest_non_native_anonymous_ancestor().unwrap()
         } else {
             *el
         };
 
-        self.dependencies
-            .lookup_with_additional(lookup_element, additional_id, &additional_classes, &mut |dep| {
+        self.dependencies.lookup_with_additional(
+            lookup_element, shared_context.quirks_mode, additional_id, &additional_classes,
+            &mut |dep| {
             trace!("scanning dependency: {:?}", dep);
 
             if !dep.sensitivities.sensitive_to(attrs_changed,
                                                state_changes) {
                 trace!(" > non-sensitive");
                 return true;
             }
 
--- a/servo/components/style/selector_map.rs
+++ b/servo/components/style/selector_map.rs
@@ -2,27 +2,28 @@
  * 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 data structure to efficiently index structs containing selectors by local
 //! name, ids and hash.
 
 use {Atom, LocalName};
 use applicable_declarations::ApplicableDeclarationBlock;
+use context::QuirksMode;
 use dom::TElement;
 use fnv::FnvHashMap;
 use pdqsort::sort_by;
 use rule_tree::CascadeLevel;
 use selector_parser::SelectorImpl;
 use selectors::matching::{matches_selector, MatchingContext, ElementSelectorFlags};
 use selectors::parser::{AncestorHashes, Component, Combinator, SelectorAndHashes, SelectorIter};
 use selectors::parser::LocalName as LocalNameSelector;
 use smallvec::VecLike;
-use std::borrow::Borrow;
 use std::collections::HashMap;
+use std::collections::hash_map;
 use std::hash::Hash;
 use stylist::Rule;
 
 /// A trait to abstract over a given selector map entry.
 pub trait SelectorMapEntry : Sized + Clone {
     /// Gets the selector we should use to index in the selector map.
     fn selector(&self) -> SelectorIter<SelectorImpl>;
 
@@ -61,19 +62,19 @@ impl SelectorMapEntry for SelectorAndHas
 /// element name, etc. will contain the Selectors that actually match that
 /// element.
 ///
 /// TODO: Tune the initial capacity of the HashMap
 #[derive(Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct SelectorMap<T: SelectorMapEntry> {
     /// A hash from an ID to rules which contain that ID selector.
-    pub id_hash: FnvHashMap<Atom, Vec<T>>,
+    pub id_hash: MaybeCaseInsensitiveHashMap<Atom, Vec<T>>,
     /// A hash from a class name to rules which contain that class selector.
-    pub class_hash: FnvHashMap<Atom, Vec<T>>,
+    pub class_hash: MaybeCaseInsensitiveHashMap<Atom, Vec<T>>,
     /// A hash from local name to rules which contain that local name selector.
     pub local_name_hash: FnvHashMap<LocalName, Vec<T>>,
     /// Rules that don't have ID, class, or element selectors.
     pub other: Vec<T>,
     /// The number of entries in this map.
     pub count: usize,
 }
 
@@ -81,18 +82,18 @@ pub struct SelectorMap<T: SelectorMapEnt
 fn sort_by_key<T, F: Fn(&T) -> K, K: Ord>(v: &mut [T], f: F) {
     sort_by(v, |a, b| f(a).cmp(&f(b)))
 }
 
 impl<T: SelectorMapEntry> SelectorMap<T> {
     /// Trivially constructs an empty `SelectorMap`.
     pub fn new() -> Self {
         SelectorMap {
-            id_hash: HashMap::default(),
-            class_hash: HashMap::default(),
+            id_hash: MaybeCaseInsensitiveHashMap::new(),
+            class_hash: MaybeCaseInsensitiveHashMap::new(),
             local_name_hash: HashMap::default(),
             other: Vec::new(),
             count: 0,
         }
     }
 
     /// Returns whether there are any entries in the map.
     pub fn is_empty(&self) -> bool {
@@ -110,74 +111,72 @@ impl SelectorMap<Rule> {
     ///
     /// Extract matching rules as per element's ID, classes, tag name, etc..
     /// Sort the Rules at the end to maintain cascading order.
     pub fn get_all_matching_rules<E, V, F>(&self,
                                            element: &E,
                                            rule_hash_target: &E,
                                            matching_rules_list: &mut V,
                                            context: &mut MatchingContext,
+                                           quirks_mode: QuirksMode,
                                            flags_setter: &mut F,
                                            cascade_level: CascadeLevel)
         where E: TElement,
               V: VecLike<ApplicableDeclarationBlock>,
               F: FnMut(&E, ElementSelectorFlags),
     {
         if self.is_empty() {
             return
         }
 
         // At the end, we're going to sort the rules that we added, so remember where we began.
         let init_len = matching_rules_list.len();
         if let Some(id) = rule_hash_target.get_id() {
-            SelectorMap::get_matching_rules_from_hash(element,
-                                                      &self.id_hash,
-                                                      &id,
-                                                      matching_rules_list,
-                                                      context,
-                                                      flags_setter,
-                                                      cascade_level)
+            if let Some(rules) = self.id_hash.get(&id, quirks_mode) {
+                SelectorMap::get_matching_rules(element,
+                                                rules,
+                                                matching_rules_list,
+                                                context,
+                                                flags_setter,
+                                                cascade_level)
+            }
         }
 
         rule_hash_target.each_class(|class| {
-            SelectorMap::get_matching_rules_from_hash(element,
-                                                      &self.class_hash,
-                                                      class,
-                                                      matching_rules_list,
-                                                      context,
-                                                      flags_setter,
-                                                      cascade_level);
+            if let Some(rules) = self.class_hash.get(&class, quirks_mode) {
+                SelectorMap::get_matching_rules(element,
+                                                rules,
+                                                matching_rules_list,
+                                                context,
+                                                flags_setter,
+                                                cascade_level)
+            }
         });
 
-        SelectorMap::get_matching_rules_from_hash(element,
-                                                  &self.local_name_hash,
-                                                  rule_hash_target.get_local_name(),
-                                                  matching_rules_list,
-                                                  context,
-                                                  flags_setter,
-                                                  cascade_level);
+        if let Some(rules) = self.local_name_hash.get(rule_hash_target.get_local_name()) {
+            SelectorMap::get_matching_rules(element,
+                                            rules,
+                                            matching_rules_list,
+                                            context,
+                                            flags_setter,
+                                            cascade_level)
+        }
 
         SelectorMap::get_matching_rules(element,
                                         &self.other,
                                         matching_rules_list,
                                         context,
                                         flags_setter,
                                         cascade_level);
 
         // Sort only the rules we just added.
         sort_by_key(&mut matching_rules_list[init_len..],
                     |block| (block.specificity, block.source_order()));
     }
 
-    /// Check whether we have rules for the given id
-    #[inline]
-    pub fn has_rules_for_id(&self, id: &Atom) -> bool {
-        self.id_hash.get(id).is_some()
-    }
-
     /// Append to `rule_list` all universal Rules (rules with selector `*|*`) in
     /// `self` sorted by specificity and source order.
     pub fn get_universal_rules(&self,
                                cascade_level: CascadeLevel)
                                -> Vec<ApplicableDeclarationBlock> {
         debug_assert!(!cascade_level.is_important());
         if self.is_empty() {
             return vec![];
@@ -191,40 +190,16 @@ impl SelectorMap<Rule> {
         }
 
         sort_by_key(&mut rules_list,
                     |block| (block.specificity, block.source_order()));
 
         rules_list
     }
 
-    fn get_matching_rules_from_hash<E, Str, BorrowedStr: ?Sized, Vector, F>(
-        element: &E,
-        hash: &FnvHashMap<Str, Vec<Rule>>,
-        key: &BorrowedStr,
-        matching_rules: &mut Vector,
-        context: &mut MatchingContext,
-        flags_setter: &mut F,
-        cascade_level: CascadeLevel)
-        where E: TElement,
-              Str: Borrow<BorrowedStr> + Eq + Hash,
-              BorrowedStr: Eq + Hash,
-              Vector: VecLike<ApplicableDeclarationBlock>,
-              F: FnMut(&E, ElementSelectorFlags),
-    {
-        if let Some(rules) = hash.get(key) {
-            SelectorMap::get_matching_rules(element,
-                                            rules,
-                                            matching_rules,
-                                            context,
-                                            flags_setter,
-                                            cascade_level)
-        }
-    }
-
     /// Adds rules in `rules` that match `element` to the `matching_rules` list.
     fn get_matching_rules<E, V, F>(element: &E,
                                    rules: &[Rule],
                                    matching_rules: &mut V,
                                    context: &mut MatchingContext,
                                    flags_setter: &mut F,
                                    cascade_level: CascadeLevel)
         where E: TElement,
@@ -242,26 +217,26 @@ impl SelectorMap<Rule> {
                     rule.to_applicable_declaration_block(cascade_level));
             }
         }
     }
 }
 
 impl<T: SelectorMapEntry> SelectorMap<T> {
     /// Inserts into the correct hash, trying id, class, and localname.
-    pub fn insert(&mut self, entry: T) {
+    pub fn insert(&mut self, entry: T, quirks_mode: QuirksMode) {
         self.count += 1;
 
         if let Some(id_name) = get_id_name(entry.selector()) {
-            find_push(&mut self.id_hash, id_name, entry);
+            self.id_hash.entry(id_name, quirks_mode).or_insert_with(Vec::new).push(entry);
             return;
         }
 
         if let Some(class_name) = get_class_name(entry.selector()) {
-            find_push(&mut self.class_hash, class_name, entry);
+            self.class_hash.entry(class_name, quirks_mode).or_insert_with(Vec::new).push(entry);
             return;
         }
 
         if let Some(LocalNameSelector { name, lower_name }) = get_local_name(entry.selector()) {
             // If the local name in the selector isn't lowercase, insert it into
             // the rule hash twice. This means that, during lookup, we can always
             // find the rules based on the local name of the element, regardless
             // of whether it's an html element in an html document (in which case
@@ -288,36 +263,36 @@ impl<T: SelectorMapEntry> SelectorMap<T>
     /// Each entry is passed to the callback, which returns true to continue
     /// iterating entries, or false to terminate the lookup.
     ///
     /// Returns false if the callback ever returns false.
     ///
     /// FIXME(bholley) This overlaps with SelectorMap<Rule>::get_all_matching_rules,
     /// but that function is extremely hot and I'd rather not rearrange it.
     #[inline]
-    pub fn lookup<E, F>(&self, element: E, f: &mut F) -> bool
+    pub fn lookup<E, F>(&self, element: E, quirks_mode: QuirksMode, f: &mut F) -> bool
         where E: TElement,
               F: FnMut(&T) -> bool
     {
         // Id.
         if let Some(id) = element.get_id() {
-            if let Some(v) = self.id_hash.get(&id) {
+            if let Some(v) = self.id_hash.get(&id, quirks_mode) {
                 for entry in v.iter() {
                     if !f(&entry) {
                         return false;
                     }
                 }
             }
         }
 
         // Class.
         let mut done = false;
         element.each_class(|class| {
             if !done {
-                if let Some(v) = self.class_hash.get(class) {
+                if let Some(v) = self.class_hash.get(class, quirks_mode) {
                     for entry in v.iter() {
                         if !f(&entry) {
                             done = true;
                             return;
                         }
                     }
                 }
             }
@@ -350,42 +325,43 @@ impl<T: SelectorMapEntry> SelectorMap<T>
     ///
     /// Each entry is passed to the callback, which returns true to continue
     /// iterating entries, or false to terminate the lookup.
     ///
     /// Returns false if the callback ever returns false.
     #[inline]
     pub fn lookup_with_additional<E, F>(&self,
                                         element: E,
+                                        quirks_mode: QuirksMode,
                                         additional_id: Option<Atom>,
                                         additional_classes: &[Atom],
                                         f: &mut F)
                                         -> bool
         where E: TElement,
               F: FnMut(&T) -> bool
     {
         // Do the normal lookup.
-        if !self.lookup(element, f) {
+        if !self.lookup(element, quirks_mode, f) {
             return false;
         }
 
         // Check the additional id.
         if let Some(id) = additional_id {
-            if let Some(v) = self.id_hash.get(&id) {
+            if let Some(v) = self.id_hash.get(&id, quirks_mode) {
                 for entry in v.iter() {
                     if !f(&entry) {
                         return false;
                     }
                 }
             }
         }
 
         // Check the additional classes.
         for class in additional_classes {
-            if let Some(v) = self.class_hash.get(class) {
+            if let Some(v) = self.class_hash.get(class, quirks_mode) {
                 for entry in v.iter() {
                     if !f(&entry) {
                         return false;
                     }
                 }
             }
         }
 
@@ -467,8 +443,37 @@ pub fn get_local_name(iter: SelectorIter
 }
 
 #[inline]
 fn find_push<Str: Eq + Hash, V>(map: &mut FnvHashMap<Str, Vec<V>>,
                                 key: Str,
                                 value: V) {
     map.entry(key).or_insert_with(Vec::new).push(value)
 }
+
+/// Wrapper for FnvHashMap that does ASCII-case-insensitive lookup in quirks mode.
+#[derive(Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct MaybeCaseInsensitiveHashMap<K: Hash + Eq, V>(FnvHashMap<K, V>);
+
+impl<V> MaybeCaseInsensitiveHashMap<Atom, V> {
+    /// Empty map
+    pub fn new() -> Self {
+        MaybeCaseInsensitiveHashMap(FnvHashMap::default())
+    }
+
+    /// HashMap::entry
+    pub fn entry(&mut self, mut key: Atom, quirks_mode: QuirksMode) -> hash_map::Entry<Atom, V> {
+        if quirks_mode == QuirksMode::Quirks {
+            key = key.to_ascii_lowercase()
+        }
+        self.0.entry(key)
+    }
+
+    /// HashMap::get
+    pub fn get(&self, key: &Atom, quirks_mode: QuirksMode) -> Option<&V> {
+        if quirks_mode == QuirksMode::Quirks {
+            self.0.get(&key.to_ascii_lowercase())
+        } else {
+            self.0.get(key)
+        }
+    }
+}
--- a/servo/components/style/servo/selector_parser.rs
+++ b/servo/components/style/servo/selector_parser.rs
@@ -1,26 +1,26 @@
 /* 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/. */
 
 #![deny(missing_docs)]
 
 //! Servo's selector parser.
 
-use {Atom, Prefix, Namespace, LocalName};
+use {Atom, Prefix, Namespace, LocalName, CaseSensitivityExt};
 use attr::{AttrIdentifier, AttrValue};
 use cssparser::{Parser as CssParser, ToCss, serialize_identifier};
 use dom::{OpaqueNode, TElement, TNode};
 use element_state::ElementState;
 use fnv::FnvHashMap;
 use restyle_hints::ElementSnapshot;
 use selector_parser::{AttrValue as SelectorAttrValue, ElementExt, PseudoElementCascadeType, SelectorParser};
 use selectors::Element;
-use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
+use selectors::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
 use selectors::parser::{SelectorMethods, SelectorParseError};
 use selectors::visitor::SelectorVisitor;
 use std::ascii::AsciiExt;
 use std::borrow::Cow;
 use std::fmt;
 use std::fmt::Debug;
 use std::mem;
 use std::ops::{Deref, DerefMut};
@@ -579,19 +579,19 @@ impl ElementSnapshot for ServoElementSna
     fn has_attrs(&self) -> bool {
         self.attrs.is_some()
     }
 
     fn id_attr(&self) -> Option<Atom> {
         self.get_attr(&ns!(), &local_name!("id")).map(|v| v.as_atom().clone())
     }
 
-    fn has_class(&self, name: &Atom) -> bool {
+    fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
         self.get_attr(&ns!(), &local_name!("class"))
-            .map_or(false, |v| v.as_tokens().iter().any(|atom| atom == name))
+            .map_or(false, |v| v.as_tokens().iter().any(|atom| case_sensitivity.eq_atom(atom, name)))
     }
 
     fn each_class<F>(&self, mut callback: F)
         where F: FnMut(&Atom),
     {
         if let Some(v) = self.get_attr(&ns!(), &local_name!("class")) {
             for class in v.as_tokens() {
                 callback(class);
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -472,25 +472,28 @@ impl Stylist {
                             self.pseudos_map
                                 .entry(pseudo.canonical())
                                 .or_insert_with(PerPseudoElementSelectorMap::new)
                                 .borrow_for_origin(&stylesheet.origin)
                         } else {
                             self.element_map.borrow_for_origin(&stylesheet.origin)
                         };
 
-                        map.insert(Rule::new(selector_and_hashes.selector.clone(),
-                                             selector_and_hashes.hashes.clone(),
-                                             locked.clone(),
-                                             self.rules_source_order));
+                        map.insert(
+                            Rule::new(selector_and_hashes.selector.clone(),
+                                      selector_and_hashes.hashes.clone(),
+                                      locked.clone(),
+                                      self.rules_source_order),
+                            self.quirks_mode);
 
-                        self.dependencies.note_selector(selector_and_hashes);
+                        self.dependencies.note_selector(selector_and_hashes, self.quirks_mode);
                         if needs_revalidation(&selector_and_hashes.selector) {
                             self.selectors_for_cache_revalidation.insert(
-                                RevalidationSelectorAndHashes::new(&selector_and_hashes));
+                                RevalidationSelectorAndHashes::new(&selector_and_hashes),
+                                self.quirks_mode);
                         }
                         selector_and_hashes.selector.visit(&mut AttributeAndStateDependencyVisitor {
                             attribute_dependencies: &mut self.attribute_dependencies,
                             style_attribute_dependency: &mut self.style_attribute_dependency,
                             state_dependencies: &mut self.state_dependencies,
                         });
                         selector_and_hashes.selector.visit(&mut MappedIdVisitor {
                             mapped_ids: &mut self.mapped_ids,
@@ -941,16 +944,17 @@ impl Stylist {
         let mut matching_context =
             MatchingContext::new(MatchingMode::Normal, None, self.quirks_mode);
         let mut dummy_flag_setter = |_: &E, _: ElementSelectorFlags| {};
 
         self.element_map.author.get_all_matching_rules(element,
                                                        element,
                                                        applicable_declarations,
                                                        &mut matching_context,
+                                                       self.quirks_mode,
                                                        &mut dummy_flag_setter,
                                                        CascadeLevel::XBL);
     }
 
     /// Returns the applicable CSS declarations for the given element.
     ///
     /// This corresponds to `ElementRuleCollector` in WebKit.
     ///
@@ -1001,16 +1005,17 @@ impl Stylist {
 
         let only_default_rules = rule_inclusion == RuleInclusion::DefaultOnly;
 
         // Step 1: Normal user-agent rules.
         map.user_agent.get_all_matching_rules(element,
                                               &rule_hash_target,
                                               applicable_declarations,
                                               context,
+                                              self.quirks_mode,
                                               flags_setter,
                                               CascadeLevel::UANormal);
         debug!("UA normal: {:?}", context.relations);
 
         if pseudo_element.is_none() && !only_default_rules {
             // Step 2: Presentational hints.
             let length_before_preshints = applicable_declarations.len();
             element.synthesize_presentational_hints_for_legacy_attributes(
@@ -1040,16 +1045,17 @@ impl Stylist {
         //
         // Which may be more what you would probably expect.
         if rule_hash_target.matches_user_and_author_rules() {
             // Step 3a: User normal rules.
             map.user.get_all_matching_rules(element,
                                             &rule_hash_target,
                                             applicable_declarations,
                                             context,
+                                            self.quirks_mode,
                                             flags_setter,
                                             CascadeLevel::UserNormal);
             debug!("user normal: {:?}", context.relations);
         } else {
             debug!("skipping user rules");
         }
 
         // Step 3b: XBL rules.
@@ -1061,16 +1067,17 @@ impl Stylist {
             // Gecko skips author normal rules if cutting off inheritance.
             // See nsStyleSet::FileRules().
             if !cut_off_inheritance {
                 // Step 3c: Author normal rules.
                 map.author.get_all_matching_rules(element,
                                                   &rule_hash_target,
                                                   applicable_declarations,
                                                   context,
+                                              self.quirks_mode,
                                                   flags_setter,
                                                   CascadeLevel::AuthorNormal);
                 debug!("author normal: {:?}", context.relations);
             } else {
                 debug!("Skipping author normal rules due to cut off inheritance");
             }
 
             // Step 4: Normal style attributes.
@@ -1171,25 +1178,27 @@ impl Stylist {
             MatchingContext::new(MatchingMode::Normal, bloom, self.quirks_mode);
 
         // Note that, by the time we're revalidating, we're guaranteed that the
         // candidate and the entry have the same id, classes, and local name.
         // This means we're guaranteed to get the same rulehash buckets for all
         // the lookups, which means that the bitvecs are comparable. We verify
         // this in the caller by asserting that the bitvecs are same-length.
         let mut results = BitVec::new();
-        self.selectors_for_cache_revalidation.lookup(*element, &mut |selector_and_hashes| {
-            results.push(matches_selector(&selector_and_hashes.selector,
-                                          selector_and_hashes.selector_offset,
-                                          &selector_and_hashes.hashes,
-                                          element,
-                                          &mut matching_context,
-                                          flags_setter));
-            true
-        });
+        self.selectors_for_cache_revalidation.lookup(
+            *element, self.quirks_mode, &mut |selector_and_hashes| {
+                results.push(matches_selector(&selector_and_hashes.selector,
+                                              selector_and_hashes.selector_offset,
+                                              &selector_and_hashes.hashes,
+                                              element,
+                                              &mut matching_context,
+                                              flags_setter));
+                true
+            }
+        );
 
         results
     }
 
     /// Given an element, and a snapshot table that represents a previous state
     /// of the tree, compute the appropriate restyle hint, that is, the kind of
     /// restyle we need to do.
     pub fn compute_restyle_hint<'a, E>(&self,
--- a/servo/tests/unit/style/restyle_hints.rs
+++ b/servo/tests/unit/style/restyle_hints.rs
@@ -1,12 +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 style::context::QuirksMode;
+
 #[test]
 fn smoke_restyle_hints() {
     use cssparser::{Parser, ParserInput};
     use selectors::parser::SelectorList;
     use style::restyle_hints::DependencySet;
     use style::selector_parser::SelectorParser;
     use style::stylesheets::{Origin, Namespaces};
     let namespaces = Namespaces::default();
@@ -18,11 +20,11 @@ fn smoke_restyle_hints() {
     let mut dependencies = DependencySet::new();
 
     let mut input = ParserInput::new(":not(:active) ~ label");
     let mut p = Parser::new(&mut input);
     let selectors = SelectorList::parse(&parser, &mut p).unwrap();
     assert_eq!((selectors.0).len(), 1);
 
     let selector = (selectors.0).first().unwrap();
-    dependencies.note_selector(selector);
+    dependencies.note_selector(selector, QuirksMode::NoQuirks);
     assert_eq!(dependencies.len(), 1);
 }
--- a/servo/tests/unit/style/stylist.rs
+++ b/servo/tests/unit/style/stylist.rs
@@ -51,17 +51,17 @@ fn get_mock_rules(css_selectors: &[&str]
 }
 
 fn get_mock_map(selectors: &[&str]) -> (SelectorMap<Rule>, SharedRwLock) {
     let mut map = SelectorMap::<Rule>::new();
     let (selector_rules, shared_lock) = get_mock_rules(selectors);
 
     for rules in selector_rules.into_iter() {
         for rule in rules.into_iter() {
-            map.insert(rule)
+            map.insert(rule, QuirksMode::NoQuirks)
         }
     }
 
     (map, shared_lock)
 }
 
 fn parse_selectors(selectors: &[&str]) -> Vec<Selector<SelectorImpl>> {
     selectors.iter()
@@ -212,21 +212,21 @@ fn test_get_local_name() {
     check(2, Some(("IMG", "img")));
     check(3, Some(("ImG", "img")));
 }
 
 #[test]
 fn test_insert() {
     let (rules_list, _) = get_mock_rules(&[".intro.foo", "#top"]);
     let mut selector_map = SelectorMap::new();
-    selector_map.insert(rules_list[1][0].clone());
-    assert_eq!(1, selector_map.id_hash.get(&Atom::from("top")).unwrap()[0].source_order);
-    selector_map.insert(rules_list[0][0].clone());
-    assert_eq!(0, selector_map.class_hash.get(&Atom::from("foo")).unwrap()[0].source_order);
-    assert!(selector_map.class_hash.get(&Atom::from("intro")).is_none());
+    selector_map.insert(rules_list[1][0].clone(), QuirksMode::NoQuirks);
+    assert_eq!(1, selector_map.id_hash.get(&Atom::from("top"), QuirksMode::NoQuirks).unwrap()[0].source_order);
+    selector_map.insert(rules_list[0][0].clone(), QuirksMode::NoQuirks);
+    assert_eq!(0, selector_map.class_hash.get(&Atom::from("foo"), QuirksMode::NoQuirks).unwrap()[0].source_order);
+    assert!(selector_map.class_hash.get(&Atom::from("intro"), QuirksMode::NoQuirks).is_none());
 }
 
 #[test]
 fn test_get_universal_rules() {
     thread_state::initialize(thread_state::LAYOUT);
     let (map, _shared_lock) = get_mock_map(&["*|*", "#foo > *|*", "*|* > *|*", ".klass", "#id"]);
 
     let decls = map.get_universal_rules(CascadeLevel::UserNormal);