servo: Merge #16915 - Shrink selectors::Component, implement attr selector (in)case-sensitivity (from servo:attr-selectors); r=nox,emilio
authorSimon Sapin <simon.sapin@exyr.org>
Thu, 18 May 2017 18:45:20 -0500
changeset 409618 87c870a14a602309fb1358f6ff0603a92e45cf55
parent 409617 cf1159d4b48966d0130fad9e3e11e376e13582bf
child 409619 c50b4de09f8b0853647c06aaf8d50571463e3255
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnox, emilio
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 #16915 - Shrink selectors::Component, implement attr selector (in)case-sensitivity (from servo:attr-selectors); r=nox,emilio * https://bugzilla.mozilla.org/show_bug.cgi?id=1364148 * https://bugzilla.mozilla.org/show_bug.cgi?id=1364162 * https://bugzilla.mozilla.org/show_bug.cgi?id=1363531 * Fixes #3322 Source-Repo: https://github.com/servo/servo Source-Revision: 640b16634f2828bad46ab6d78e785dfc2025ab46
servo/Cargo.lock
servo/components/script/dom/element.rs
servo/components/script/layout_wrapper.rs
servo/components/script_layout_interface/wrapper_traits.rs
servo/components/selectors/Cargo.toml
servo/components/selectors/attr.rs
servo/components/selectors/build.rs
servo/components/selectors/lib.rs
servo/components/selectors/matching.rs
servo/components/selectors/parser.rs
servo/components/selectors/size_of_tests.rs
servo/components/selectors/tree.rs
servo/components/selectors/visitor.rs
servo/components/style/attr.rs
servo/components/style/dom.rs
servo/components/style/gecko/snapshot.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/restyle_hints.rs
servo/components/style/servo/selector_parser.rs
servo/components/style/stylist.rs
servo/python/tidy/servo_tidy/tidy.py
servo/resources/presentational-hints.css
servo/servo-tidy.toml
servo/tests/unit/style/stylesheets.rs
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -2452,16 +2452,18 @@ dependencies = [
 [[package]]
 name = "selectors"
 version = "0.18.0"
 dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 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)",
  "precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "size_of_test 0.0.1",
  "smallvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "semver"
 version = "0.1.20"
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -81,19 +81,19 @@ 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::matching::{ElementSelectorFlags, MatchingContext, MatchingMode, matches_selector_list};
 use selectors::matching::{HAS_EDGE_CHILD_SELECTOR, HAS_SLOW_SELECTOR, HAS_SLOW_SELECTOR_LATER_SIBLINGS};
-use selectors::parser::{AttrSelector, NamespaceConstraint};
 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;
@@ -283,17 +283,17 @@ impl Element {
 }
 
 #[allow(unsafe_code)]
 pub trait RawLayoutElementHelpers {
     unsafe fn get_attr_for_layout<'a>(&'a self, namespace: &Namespace, name: &LocalName)
                                       -> Option<&'a AttrValue>;
     unsafe fn get_attr_val_for_layout<'a>(&'a self, namespace: &Namespace, name: &LocalName)
                                       -> Option<&'a str>;
-    unsafe fn get_attr_vals_for_layout<'a>(&'a self, name: &LocalName) -> Vec<&'a str>;
+    unsafe fn get_attr_vals_for_layout<'a>(&'a self, name: &LocalName) -> Vec<&'a AttrValue>;
 }
 
 #[inline]
 #[allow(unsafe_code)]
 pub unsafe fn get_attr_for_layout<'a>(elem: &'a Element, namespace: &Namespace, name: &LocalName)
                                       -> Option<LayoutJS<Attr>> {
     // cast to point to T in RefCell<T> directly
     let attrs = elem.attrs.borrow_for_layout();
@@ -309,30 +309,31 @@ impl RawLayoutElementHelpers for Element
     #[inline]
     unsafe fn get_attr_for_layout<'a>(&'a self, namespace: &Namespace, name: &LocalName)
                                       -> Option<&'a AttrValue> {
         get_attr_for_layout(self, namespace, name).map(|attr| {
             attr.value_forever()
         })
     }
 
+    #[inline]
     unsafe fn get_attr_val_for_layout<'a>(&'a self, namespace: &Namespace, name: &LocalName)
                                           -> Option<&'a str> {
         get_attr_for_layout(self, namespace, name).map(|attr| {
             attr.value_ref_forever()
         })
     }
 
     #[inline]
-    unsafe fn get_attr_vals_for_layout<'a>(&'a self, name: &LocalName) -> Vec<&'a str> {
+    unsafe fn get_attr_vals_for_layout<'a>(&'a self, name: &LocalName) -> Vec<&'a AttrValue> {
         let attrs = self.attrs.borrow_for_layout();
         attrs.iter().filter_map(|attr| {
             let attr = attr.to_layout();
             if *name == attr.local_name_atom_forever() {
-              Some(attr.value_ref_forever())
+              Some(attr.value_forever())
             } else {
               None
             }
         }).collect()
     }
 }
 
 pub trait LayoutElementHelpers {
@@ -2347,47 +2348,19 @@ impl VirtualMethods for Element {
         self.super_type().unwrap().adopting_steps(old_doc);
 
         if document_from_node(self).is_html_document() != old_doc.is_html_document() {
             self.tag_name.clear();
         }
     }
 }
 
-impl<'a> ::selectors::MatchAttrGeneric for Root<Element> {
+impl<'a> ::selectors::Element for Root<Element> {
     type Impl = SelectorImpl;
 
-    fn match_attr<F>(&self, attr: &AttrSelector<SelectorImpl>, test: F) -> bool
-        where F: Fn(&str) -> bool
-    {
-        use ::selectors::Element;
-        let local_name = {
-            if self.is_html_element_in_html_document() {
-                &attr.lower_name
-            } else {
-                &attr.name
-            }
-        };
-        match attr.namespace {
-            NamespaceConstraint::Specific(ref ns) => {
-                self.get_attribute(&ns.url, local_name)
-                    .map_or(false, |attr| {
-                        test(&attr.value())
-                    })
-            },
-            NamespaceConstraint::Any => {
-                self.attrs.borrow().iter().any(|attr| {
-                    attr.local_name() == local_name && test(&attr.value())
-                })
-            }
-        }
-    }
-}
-
-impl<'a> ::selectors::Element for Root<Element> {
     fn parent_element(&self) -> Option<Root<Element>> {
         self.upcast::<Node>().GetParentElement()
     }
 
     fn match_pseudo_element(&self,
                             _pseudo: &PseudoElement,
                             _context: &mut MatchingContext)
                             -> bool
@@ -2407,16 +2380,35 @@ impl<'a> ::selectors::Element for Root<E
     fn prev_sibling_element(&self) -> Option<Root<Element>> {
         self.node.preceding_siblings().filter_map(Root::downcast).next()
     }
 
     fn next_sibling_element(&self) -> Option<Root<Element>> {
         self.node.following_siblings().filter_map(Root::downcast).next()
     }
 
+    fn attr_matches(&self,
+                    ns: &NamespaceConstraint<&Namespace>,
+                    local_name: &LocalName,
+                    operation: &AttrSelectorOperation<&String>)
+                    -> bool {
+        match *ns {
+            NamespaceConstraint::Specific(ref ns) => {
+                self.get_attribute(ns, local_name)
+                    .map_or(false, |attr| attr.value().eval_selector(operation))
+            }
+            NamespaceConstraint::Any => {
+                self.attrs.borrow().iter().any(|attr| {
+                    attr.local_name() == local_name &&
+                    attr.value().eval_selector(operation)
+                })
+            }
+        }
+    }
+
     fn is_root(&self) -> bool {
         match self.node.GetParentNode() {
             None => false,
             Some(node) => node.is::<Document>(),
         }
     }
 
     fn is_empty(&self) -> bool {
@@ -2454,16 +2446,21 @@ impl<'a> ::selectors::Element for Root<E
                         match this.get_border() {
                             None | Some(0) => false,
                             Some(_) => true,
                         }
                     }
                 }
             },
 
+            NonTSPseudoClass::ServoCaseSensitiveTypeAttr(ref expected_value) => {
+                self.get_attribute(&ns!(), &local_name!("type"))
+                    .map_or(false, |attr| attr.value().eq(expected_value))
+            }
+
             // FIXME(#15746): This is wrong, we need to instead use extended filtering as per RFC4647
             //                https://tools.ietf.org/html/rfc4647#section-3.3.2
             NonTSPseudoClass::Lang(ref lang) => extended_filtering(&*self.get_lang(), &*lang),
 
             NonTSPseudoClass::ReadOnly =>
                 !Element::state(self).contains(pseudo_class.state_flag()),
 
             NonTSPseudoClass::Active |
@@ -2484,28 +2481,16 @@ impl<'a> ::selectors::Element for Root<E
     fn get_id(&self) -> Option<Atom> {
         self.id_attribute.borrow().clone()
     }
 
     fn has_class(&self, name: &Atom) -> bool {
         Element::has_class(&**self, name)
     }
 
-    fn each_class<F>(&self, mut callback: F)
-        where F: FnMut(&Atom)
-    {
-        if let Some(ref attr) = self.get_attribute(&ns!(), &local_name!("class")) {
-            let tokens = attr.value();
-            let tokens = tokens.as_tokens();
-            for token in tokens {
-                callback(token);
-            }
-        }
-    }
-
     fn is_html_element_in_html_document(&self) -> bool {
         self.html_element_in_html_document()
     }
 }
 
 
 impl Element {
     pub fn as_maybe_activatable(&self) -> Option<&Activatable> {
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -45,18 +45,18 @@ 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, PartialPersistentLayoutData};
 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::matching::{ElementSelectorFlags, MatchingContext};
-use selectors::parser::{AttrSelector, NamespaceConstraint};
 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;
@@ -397,16 +397,27 @@ impl<'le> TElement for ServoLayoutElemen
         self.get_attr(namespace, attr).is_some()
     }
 
     #[inline]
     fn attr_equals(&self, namespace: &Namespace, attr: &LocalName, val: &Atom) -> bool {
         self.get_attr(namespace, attr).map_or(false, |x| x == val)
     }
 
+    #[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)
+                }
+            }
+        }
+    }
+
     #[inline]
     fn existing_style_for_restyle_damage<'a>(&'a self,
                                              current_cv: &'a ComputedValues,
                                              _pseudo_element: Option<&PseudoElement>)
                                              -> Option<&'a ComputedValues> {
         Some(current_cv)
     }
 
@@ -505,16 +516,23 @@ impl<'le> ServoLayoutElement<'le> {
     fn from_layout_js(el: LayoutJS<Element>) -> ServoLayoutElement<'le> {
         ServoLayoutElement {
             element: el,
             chain: PhantomData,
         }
     }
 
     #[inline]
+    fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
+        unsafe {
+            (*self.element.unsafe_get()).get_attr_for_layout(namespace, name)
+        }
+    }
+
+    #[inline]
     fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str> {
         unsafe {
             (*self.element.unsafe_get()).get_attr_val_for_layout(namespace, name)
         }
     }
 
     fn get_partial_layout_data(&self) -> Option<&AtomicRefCell<PartialPersistentLayoutData>> {
         unsafe {
@@ -553,42 +571,19 @@ impl<'le> ServoLayoutElement<'le> {
         debug_assert!(self.descendants_bit_is_propagated::<DirtyDescendants>());
     }
 }
 
 fn as_element<'le>(node: LayoutJS<Node>) -> Option<ServoLayoutElement<'le>> {
     node.downcast().map(ServoLayoutElement::from_layout_js)
 }
 
-impl<'le> ::selectors::MatchAttrGeneric for ServoLayoutElement<'le> {
+impl<'le> ::selectors::Element for ServoLayoutElement<'le> {
     type Impl = SelectorImpl;
 
-    fn match_attr<F>(&self, attr: &AttrSelector<SelectorImpl>, test: F) -> bool
-    where F: Fn(&str) -> bool {
-        use ::selectors::Element;
-        let name = if self.is_html_element_in_html_document() {
-            &attr.lower_name
-        } else {
-            &attr.name
-        };
-        match attr.namespace {
-            NamespaceConstraint::Specific(ref ns) => {
-                self.get_attr(&ns.url, name).map_or(false, |attr| test(attr))
-            },
-            NamespaceConstraint::Any => {
-                let attrs = unsafe {
-                    (*self.element.unsafe_get()).get_attr_vals_for_layout(name)
-                };
-                attrs.iter().any(|attr| test(*attr))
-            }
-        }
-    }
-}
-
-impl<'le> ::selectors::Element for ServoLayoutElement<'le> {
     fn parent_element(&self) -> Option<ServoLayoutElement<'le>> {
         unsafe {
             self.element.upcast().parent_node_ref().and_then(as_element)
         }
     }
 
     fn first_child_element(&self) -> Option<ServoLayoutElement<'le>> {
         self.as_node().children().filter_map(|n| n.as_element()).next()
@@ -615,16 +610,35 @@ impl<'le> ::selectors::Element for Servo
             if let Some(element) = sibling.as_element() {
                 return Some(element)
             }
             node = sibling;
         }
         None
     }
 
+    fn attr_matches(&self,
+                    ns: &NamespaceConstraint<&Namespace>,
+                    local_name: &LocalName,
+                    operation: &AttrSelectorOperation<&String>)
+                    -> bool {
+        match *ns {
+            NamespaceConstraint::Specific(ref ns) => {
+                self.get_attr_enum(ns, local_name)
+                    .map_or(false, |value| value.eval_selector(operation))
+            }
+            NamespaceConstraint::Any => {
+                let values = unsafe {
+                    (*self.element.unsafe_get()).get_attr_vals_for_layout(local_name)
+                };
+                values.iter().any(|value| value.eval_selector(operation))
+            }
+        }
+    }
+
     fn is_root(&self) -> bool {
         match self.as_node().parent_node() {
             None => false,
             Some(node) => {
                 match node.script_type_id() {
                     NodeTypeId::Document(_) => true,
                     _ => false
                 }
@@ -687,17 +701,20 @@ impl<'le> ::selectors::Element for Servo
             NonTSPseudoClass::Lang(ref lang) => extended_filtering(&*self.element.get_lang_for_layout(), &*lang),
 
             NonTSPseudoClass::ServoNonZeroBorder => unsafe {
                 match (*self.element.unsafe_get()).get_attr_for_layout(&ns!(), &local_name!("border")) {
                     None | Some(&AttrValue::UInt(_, 0)) => false,
                     _ => true,
                 }
             },
-
+            NonTSPseudoClass::ServoCaseSensitiveTypeAttr(ref expected_value) => {
+                self.get_attr_enum(&ns!(), &local_name!("type"))
+                    .map_or(false, |attr| attr == expected_value)
+            }
             NonTSPseudoClass::ReadOnly =>
                 !self.element.get_state_for_layout().contains(pseudo_class.state_flag()),
 
             NonTSPseudoClass::Active |
             NonTSPseudoClass::Focus |
             NonTSPseudoClass::Fullscreen |
             NonTSPseudoClass::Hover |
             NonTSPseudoClass::Enabled |
@@ -720,27 +737,16 @@ impl<'le> ::selectors::Element for Servo
 
     #[inline]
     fn has_class(&self, name: &Atom) -> bool {
         unsafe {
             self.element.has_class_for_layout(name)
         }
     }
 
-    #[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)
-                }
-            }
-        }
-    }
-
     fn is_html_element_in_html_document(&self) -> bool {
         unsafe {
             self.element.html_element_in_html_document_for_layout()
         }
     }
 }
 
 #[derive(Copy, Clone, Debug)]
@@ -1070,56 +1076,44 @@ impl<'le> ThreadSafeLayoutElement for Se
     fn type_id(&self) -> Option<LayoutNodeType> {
         self.as_node().type_id()
     }
 
     unsafe fn unsafe_get(self) -> ServoLayoutElement<'le> {
         self.element
     }
 
+    fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
+        self.element.get_attr_enum(namespace, name)
+    }
+
     fn get_attr<'a>(&'a self, namespace: &Namespace, name: &LocalName) -> Option<&'a str> {
         self.element.get_attr(namespace, name)
     }
 
     fn get_style_data(&self) -> Option<&AtomicRefCell<ElementData>> {
         self.element.get_data()
     }
 }
 
 /// This implementation of `::selectors::Element` is used for implementing lazy
 /// pseudo-elements.
 ///
 /// Lazy pseudo-elements in Servo only allows selectors using safe properties,
 /// i.e., local_name, attributes, so they can only be used for **private**
 /// pseudo-elements (like `::-servo-details-content`).
 ///
-/// Probably a few more of this functions can be implemented (like `has_class`,
-/// `each_class`, etc), but they have no use right now.
+/// Probably a few more of this functions can be implemented (like `has_class`, etc.),
+/// but they have no use right now.
 ///
 /// Note that the element implementation is needed only for selector matching,
 /// not for inheritance (styles are inherited appropiately).
-impl<'le> ::selectors::MatchAttrGeneric for ServoThreadSafeLayoutElement<'le> {
+impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> {
     type Impl = SelectorImpl;
 
-    fn match_attr<F>(&self, attr: &AttrSelector<SelectorImpl>, test: F) -> bool
-        where F: Fn(&str) -> bool {
-        match attr.namespace {
-            NamespaceConstraint::Specific(ref ns) => {
-                self.get_attr(&ns.url, &attr.name).map_or(false, |attr| test(attr))
-            },
-            NamespaceConstraint::Any => {
-                unsafe {
-                    (*self.element.element.unsafe_get()).get_attr_vals_for_layout(&attr.name).iter()
-                        .any(|attr| test(*attr))
-                }
-            }
-        }
-    }
-}
-impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> {
     fn parent_element(&self) -> Option<Self> {
         warn!("ServoThreadSafeLayoutElement::parent_element called");
         None
     }
 
     fn first_child_element(&self) -> Option<Self> {
         warn!("ServoThreadSafeLayoutElement::first_child_element called");
         None
@@ -1161,16 +1155,35 @@ impl<'le> ::selectors::Element for Servo
     fn match_pseudo_element(&self,
                             _pseudo: &PseudoElement,
                             _context: &mut MatchingContext)
                             -> bool
     {
         false
     }
 
+    fn attr_matches(&self,
+                    ns: &NamespaceConstraint<&Namespace>,
+                    local_name: &LocalName,
+                    operation: &AttrSelectorOperation<&String>)
+                    -> bool {
+        match *ns {
+            NamespaceConstraint::Specific(ref ns) => {
+                self.get_attr_enum(ns, local_name)
+                    .map_or(false, |value| value.eval_selector(operation))
+            }
+            NamespaceConstraint::Any => {
+                let values = unsafe {
+                    (*self.element.element.unsafe_get()).get_attr_vals_for_layout(local_name)
+                };
+                values.iter().any(|v| v.eval_selector(operation))
+            }
+        }
+    }
+
     fn match_non_ts_pseudo_class<F>(&self,
                                     _: &NonTSPseudoClass,
                                     _: &mut MatchingContext,
                                     _: &mut F)
                                     -> bool
         where F: FnMut(&Self, ElementSelectorFlags),
     {
         // NB: This could maybe be implemented
@@ -1192,19 +1205,14 @@ impl<'le> ::selectors::Element for Servo
         warn!("ServoThreadSafeLayoutElement::is_empty called");
         false
     }
 
     fn is_root(&self) -> bool {
         warn!("ServoThreadSafeLayoutElement::is_root called");
         false
     }
-
-    fn each_class<F>(&self, _callback: F)
-        where F: FnMut(&Atom) {
-        warn!("ServoThreadSafeLayoutElement::each_class called");
-    }
 }
 
 impl<'le> PresentationalHintsSynthesizer for ServoThreadSafeLayoutElement<'le> {
     fn synthesize_presentational_hints_for_legacy_attributes<V>(&self, _hints: &mut V)
         where V: Push<ApplicableDeclarationBlock> {}
 }
--- a/servo/components/script_layout_interface/wrapper_traits.rs
+++ b/servo/components/script_layout_interface/wrapper_traits.rs
@@ -10,16 +10,17 @@ use OpaqueStyleAndLayoutData;
 use SVGSVGData;
 use atomic_refcell::AtomicRefCell;
 use gfx_traits::{ByteIndex, FragmentType, combine_id_with_fragment_type};
 use html5ever::{Namespace, LocalName};
 use msg::constellation_msg::{BrowsingContextId, PipelineId};
 use range::Range;
 use servo_url::ServoUrl;
 use std::fmt::Debug;
+use style::attr::AttrValue;
 use style::computed_values::display;
 use style::context::SharedStyleContext;
 use style::data::ElementData;
 use style::dom::{LayoutIterator, NodeInfo, PresentationalHintsSynthesizer, TNode};
 use style::dom::OpaqueNode;
 use style::font_metrics::ServoMetricsProvider;
 use style::properties::{CascadeFlags, ServoComputedValues};
 use style::selector_parser::{PseudoElement, PseudoElementCascadeType, SelectorImpl};
@@ -330,16 +331,18 @@ pub trait ThreadSafeLayoutElement: Clone
     /// We need this so that the functions defined on this trait can call
     /// lazily_compute_pseudo_element_style, which operates on TElement.
     unsafe fn unsafe_get(self) ->
         <<Self::ConcreteThreadSafeLayoutNode as ThreadSafeLayoutNode>::ConcreteNode as TNode>::ConcreteElement;
 
     #[inline]
     fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str>;
 
+    fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue>;
+
     fn get_style_data(&self) -> Option<&AtomicRefCell<ElementData>>;
 
     #[inline]
     fn get_pseudo_element_type(&self) -> PseudoElementType<Option<display::T>>;
 
     #[inline]
     fn get_before_pseudo(&self) -> Option<Self> {
         if self.get_style_data()
--- a/servo/components/selectors/Cargo.toml
+++ b/servo/components/selectors/Cargo.toml
@@ -5,28 +5,33 @@ version = "0.18.0" # Not yet published
 authors = ["Simon Sapin <simon.sapin@exyr.org>", "Alan Jeffrey <ajeffrey@mozilla.com>"]
 documentation = "https://docs.rs/selectors/"
 
 description = "CSS Selectors matching for Rust"
 repository = "https://github.com/servo/servo"
 readme = "README.md"
 keywords = ["css", "selectors"]
 license = "MPL-2.0"
+build = "build.rs"
 
 [lib]
 name = "selectors"
 path = "lib.rs"
 # https://github.com/servo/servo/issues/16710
 doctest = false
 
 [features]
 gecko_like_types = []
 
 [dependencies]
 bitflags = "0.7"
 matches = "0.1"
 cssparser = "0.13.3"
 fnv = "1.0"
+phf = "0.7.18"
 precomputed-hash = "0.1"
 smallvec = "0.3"
 
 [dev-dependencies]
 size_of_test = {path = "../size_of_test"}
+
+[build-dependencies]
+phf_codegen = "0.7.18"
new file mode 100644
--- /dev/null
+++ b/servo/components/selectors/attr.rs
@@ -0,0 +1,190 @@
+/* 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::ToCss;
+use parser::SelectorImpl;
+use std::ascii::AsciiExt;
+use std::fmt;
+
+#[derive(Eq, PartialEq, Clone)]
+pub struct AttrSelectorWithNamespace<Impl: SelectorImpl> {
+    pub namespace: NamespaceConstraint<(Impl::NamespacePrefix, Impl::NamespaceUrl)>,
+    pub local_name: Impl::LocalName,
+    pub local_name_lower: Impl::LocalName,
+    pub operation: ParsedAttrSelectorOperation<Impl::AttrValue>,
+    pub never_matches: bool,
+}
+
+impl<Impl: SelectorImpl> AttrSelectorWithNamespace<Impl> {
+    pub fn namespace(&self) -> NamespaceConstraint<&Impl::NamespaceUrl> {
+        match self.namespace {
+            NamespaceConstraint::Any => NamespaceConstraint::Any,
+            NamespaceConstraint::Specific((_, ref url)) => {
+                NamespaceConstraint::Specific(url)
+            }
+        }
+    }
+}
+
+#[derive(Eq, PartialEq, Clone)]
+pub enum NamespaceConstraint<NamespaceUrl> {
+    Any,
+
+    /// Empty string for no namespace
+    Specific(NamespaceUrl),
+}
+
+#[derive(Eq, PartialEq, Clone)]
+pub enum ParsedAttrSelectorOperation<AttrValue> {
+    Exists,
+    WithValue {
+        operator: AttrSelectorOperator,
+        case_sensitivity: ParsedCaseSensitivity,
+        expected_value: AttrValue,
+    }
+}
+
+#[derive(Eq, PartialEq, Clone)]
+pub enum AttrSelectorOperation<AttrValue> {
+    Exists,
+    WithValue {
+        operator: AttrSelectorOperator,
+        case_sensitivity: CaseSensitivity,
+        expected_value: AttrValue,
+    }
+}
+
+impl<AttrValue> AttrSelectorOperation<AttrValue> {
+    pub fn eval_str(&self, element_attr_value: &str) -> bool where AttrValue: AsRef<str> {
+        match *self {
+            AttrSelectorOperation::Exists => true,
+            AttrSelectorOperation::WithValue { operator, case_sensitivity, ref expected_value } => {
+                operator.eval_str(element_attr_value, expected_value.as_ref(), case_sensitivity)
+            }
+        }
+    }
+}
+
+#[derive(Eq, PartialEq, Clone, Copy)]
+pub enum AttrSelectorOperator {
+    Equal,
+    Includes,
+    DashMatch,
+    Prefix,
+    Substring,
+    Suffix,
+}
+
+impl ToCss for AttrSelectorOperator {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        dest.write_str(match *self {
+            AttrSelectorOperator::Equal => " = ",
+            AttrSelectorOperator::Includes => " ~= ",
+            AttrSelectorOperator::DashMatch => " |= ",
+            AttrSelectorOperator::Prefix => " ^= ",
+            AttrSelectorOperator::Substring => " *= ",
+            AttrSelectorOperator::Suffix => " $= ",
+        })
+    }
+}
+
+impl AttrSelectorOperator {
+    pub fn eval_str(self, element_attr_value: &str, attr_selector_value: &str,
+                    case_sensitivity: CaseSensitivity) -> bool {
+        let e = element_attr_value.as_bytes();
+        let s = attr_selector_value.as_bytes();
+        let case = case_sensitivity;
+        match self {
+            AttrSelectorOperator::Equal => {
+                case.eq(e, s)
+            }
+            AttrSelectorOperator::Prefix => {
+                e.len() >= s.len() && case.eq(&e[..s.len()], s)
+            }
+            AttrSelectorOperator::Suffix => {
+                e.len() >= s.len() && case.eq(&e[(e.len() - s.len())..], s)
+            }
+            AttrSelectorOperator::Substring => {
+                case.contains(element_attr_value, attr_selector_value)
+            }
+            AttrSelectorOperator::Includes => {
+                element_attr_value.split(SELECTOR_WHITESPACE)
+                                  .any(|part| case.eq(part.as_bytes(), s))
+            }
+            AttrSelectorOperator::DashMatch => {
+                case.eq(e, s) || (
+                    e.get(s.len()) == Some(&b'-') &&
+                    case.eq(&e[..s.len()], s)
+                )
+            }
+        }
+    }
+}
+
+/// 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.
+    AsciiCaseInsensitive,
+    AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument,
+}
+
+impl ParsedCaseSensitivity {
+    pub fn to_unconditional(self, is_html_element_in_html_document: bool) -> CaseSensitivity {
+        match self {
+            ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument
+            if is_html_element_in_html_document => {
+                CaseSensitivity::AsciiCaseInsensitive
+            }
+            ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {
+                CaseSensitivity::CaseSensitive
+            }
+            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.
+    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),
+        }
+    }
+
+    pub fn contains(self, haystack: &str, needle: &str) -> bool {
+        match self {
+            CaseSensitivity::CaseSensitive => haystack.contains(needle),
+            CaseSensitivity::AsciiCaseInsensitive => {
+                if let Some((&n_first_byte, n_rest)) = needle.as_bytes().split_first() {
+                    haystack.bytes().enumerate().any(|(i, byte)| {
+                        if !byte.eq_ignore_ascii_case(&n_first_byte) {
+                            return false
+                        }
+                        let after_this_byte = &haystack.as_bytes()[i + 1..];
+                        match after_this_byte.get(..n_rest.len()) {
+                            None => false,
+                            Some(haystack_slice) => {
+                                haystack_slice.eq_ignore_ascii_case(n_rest)
+                            }
+                        }
+                    })
+                } else {
+                    // any_str.contains("") == true,
+                    // though these cases should be handled with *NeverMatches and never go here.
+                    true
+                }
+            }
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/components/selectors/build.rs
@@ -0,0 +1,75 @@
+/* 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/. */
+
+extern crate phf_codegen;
+
+use std::env;
+use std::fs::File;
+use std::io::{BufWriter, Write};
+use std::path::Path;
+
+fn main() {
+    let path = Path::new(&env::var("OUT_DIR").unwrap())
+        .join("ascii_case_insensitive_html_attributes.rs");
+    let mut file = BufWriter::new(File::create(&path).unwrap());
+
+    write!(&mut file, "{{ static SET: ::phf::Set<&'static str> = ",
+    ).unwrap();
+    let mut set = phf_codegen::Set::new();
+    for name in ASCII_CASE_INSENSITIVE_HTML_ATTRIBUTES.split_whitespace() {
+        set.entry(name);
+    }
+    set.build(&mut file).unwrap();
+    write!(&mut file, "; &SET }}").unwrap();
+}
+
+/// https://html.spec.whatwg.org/multipage/#selectors
+static ASCII_CASE_INSENSITIVE_HTML_ATTRIBUTES: &'static str = r#"
+    accept
+    accept-charset
+    align
+    alink
+    axis
+    bgcolor
+    charset
+    checked
+    clear
+    codetype
+    color
+    compact
+    declare
+    defer
+    dir
+    direction
+    disabled
+    enctype
+    face
+    frame
+    hreflang
+    http-equiv
+    lang
+    language
+    link
+    media
+    method
+    multiple
+    nohref
+    noresize
+    noshade
+    nowrap
+    readonly
+    rel
+    rev
+    rules
+    scope
+    scrolling
+    selected
+    shape
+    target
+    text
+    type
+    valign
+    valuetype
+    vlink
+"#;
--- a/servo/components/selectors/lib.rs
+++ b/servo/components/selectors/lib.rs
@@ -1,24 +1,25 @@
 /* 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/. */
 
 #[macro_use] extern crate bitflags;
 #[macro_use] extern crate cssparser;
 #[macro_use] extern crate matches;
 extern crate fnv;
+extern crate phf;
 extern crate precomputed_hash;
 #[cfg(test)] #[macro_use] extern crate size_of_test;
 extern crate smallvec;
 
 pub mod arcslice;
+pub mod attr;
 pub mod bloom;
 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};
 pub use tree::Element;
-pub use tree::{MatchAttr, MatchAttrGeneric};
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -1,14 +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/. */
+
+use attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstraint};
 use bloom::BloomFilter;
-use parser::{CaseSensitivity, Combinator, ComplexSelector, Component, LocalName};
-use parser::{Selector, SelectorInner, SelectorIter, SelectorImpl};
+use parser::{Combinator, ComplexSelector, Component, LocalName};
+use parser::{Selector, SelectorInner, SelectorIter};
 use std::borrow::Borrow;
 use tree::Element;
 
 // 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;
 
@@ -380,69 +382,92 @@ fn matches_simple_selector<E, F>(
     }
 
     match *selector {
         Component::Combinator(_) => unreachable!(),
         Component::PseudoElement(ref pseudo) => {
             element.match_pseudo_element(pseudo, context)
         }
         Component::LocalName(LocalName { ref name, ref lower_name }) => {
-            let name = if element.is_html_element_in_html_document() { lower_name } else { name };
-            element.get_local_name() == name.borrow()
+            let is_html = element.is_html_element_in_html_document();
+            element.get_local_name() == select_name(is_html, name, lower_name).borrow()
         }
         Component::ExplicitUniversalType |
         Component::ExplicitAnyNamespace => {
             true
         }
         Component::Namespace(_, ref url) |
         Component::DefaultNamespace(ref url) => {
             element.get_namespace() == url.borrow()
         }
         Component::ExplicitNoNamespace => {
-            // Rust type’s default, not default namespace
-            let empty_string = <E::Impl as SelectorImpl>::NamespaceUrl::default();
-            element.get_namespace() == empty_string.borrow()
+            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) => {
             relation_if!(element.get_id().map_or(false, |attr| attr == *id),
                          AFFECTED_BY_ID_SELECTOR)
         }
         Component::Class(ref class) => {
             element.has_class(class)
         }
-        Component::AttrExists(ref attr) => {
-            element.match_attr_has(attr)
-        }
-        Component::AttrEqual(ref attr, ref value, case_sensitivity) => {
-            match case_sensitivity {
-                CaseSensitivity::CaseSensitive => element.match_attr_equals(attr, value),
-                CaseSensitivity::CaseInsensitive => element.match_attr_equals_ignore_ascii_case(attr, value),
-            }
-        }
-        Component::AttrIncludes(ref attr, ref value) => {
-            element.match_attr_includes(attr, value)
-        }
-        Component::AttrDashMatch(ref attr, ref value) => {
-            element.match_attr_dash(attr, value)
+        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
+            )
         }
-        Component::AttrPrefixMatch(ref attr, ref value) => {
-            element.match_attr_prefix(attr, value)
-        }
-        Component::AttrSubstringMatch(ref attr, ref value) => {
-            element.match_attr_substring(attr, value)
+        Component::AttributeInNoNamespace {
+            ref local_name,
+            ref local_name_lower,
+            ref value,
+            operator,
+            case_sensitivity,
+            never_matches,
+        } => {
+            if never_matches {
+                return false
+            }
+            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::WithValue {
+                    operator: operator,
+                    case_sensitivity: case_sensitivity.to_unconditional(is_html),
+                    expected_value: value,
+                }
+            )
         }
-        Component::AttrSuffixMatch(ref attr, ref value) => {
-            element.match_attr_suffix(attr, value)
-        }
-        Component::AttrIncludesNeverMatch(..) |
-        Component::AttrPrefixNeverMatch(..) |
-        Component::AttrSubstringNeverMatch(..) |
-        Component::AttrSuffixNeverMatch(..) => {
-            false
+        Component::AttributeOther(ref attr_sel) => {
+            if attr_sel.never_matches {
+                return false
+            }
+            let is_html = element.is_html_element_in_html_document();
+            element.attr_matches(
+                &attr_sel.namespace(),
+                select_name(is_html, &attr_sel.local_name, &attr_sel.local_name_lower),
+                &match attr_sel.operation {
+                    ParsedAttrSelectorOperation::Exists => AttrSelectorOperation::Exists,
+                    ParsedAttrSelectorOperation::WithValue {
+                        operator,
+                        case_sensitivity,
+                        ref expected_value,
+                    } => {
+                        AttrSelectorOperation::WithValue {
+                            operator: operator,
+                            case_sensitivity: case_sensitivity.to_unconditional(is_html),
+                            expected_value: expected_value,
+                        }
+                    }
+                }
+            )
         }
         Component::NonTSPseudoClass(ref pc) => {
             element.match_non_ts_pseudo_class(pc, context, flags_setter)
         }
         Component::FirstChild => {
             matches_first_child(element, flags_setter)
         }
         Component::LastChild => {
@@ -484,16 +509,24 @@ fn matches_simple_selector<E, F>(
             matches_generic_nth_child(element, 0, 1, true, true, flags_setter)
         }
         Component::Negation(ref negated) => {
             !negated.iter().all(|ss| matches_simple_selector(ss, element, context, flags_setter))
         }
     }
 }
 
+fn select_name<'a, T>(is_html: bool, local_name: &'a T, local_name_lower: &'a T) -> &'a T {
+    if is_html {
+        local_name_lower
+    } else {
+        local_name
+    }
+}
+
 #[inline]
 fn matches_generic_nth_child<E, F>(element: &E,
                                    a: i32,
                                    b: i32,
                                    is_of_type: bool,
                                    is_from_end: bool,
                                    flags_setter: &mut F)
                                    -> bool
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -1,24 +1,25 @@
 /* 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 arcslice::ArcSlice;
+use attr::{AttrSelectorWithNamespace, ParsedAttrSelectorOperation, AttrSelectorOperator};
+use attr::{ParsedCaseSensitivity, SELECTOR_WHITESPACE, NamespaceConstraint};
 use cssparser::{Token, Parser as CssParser, parse_nth, ToCss, serialize_identifier, CssStringWriter};
 use precomputed_hash::PrecomputedHash;
 use smallvec::SmallVec;
 use std::ascii::AsciiExt;
 use std::borrow::{Borrow, Cow};
 use std::cmp;
 use std::fmt::{self, Display, Debug, Write};
 use std::iter::Rev;
 use std::ops::Add;
 use std::slice;
-use tree::SELECTOR_WHITESPACE;
 use visitor::SelectorVisitor;
 
 /// A trait that represents a pseudo-element.
 pub trait PseudoElement : Sized + ToCss {
     /// The `SelectorImpl` this pseudo-element is used for.
     type Impl: SelectorImpl;
 
     /// Whether the pseudo-element supports a given state selector to the right
@@ -27,39 +28,39 @@ pub trait PseudoElement : Sized + ToCss 
         &self,
         _pseudo_class: &<Self::Impl as SelectorImpl>::NonTSPseudoClass)
         -> bool
     {
         false
     }
 }
 
+fn to_ascii_lowercase(s: &str) -> Cow<str> {
+    if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') {
+        let mut string = s.to_owned();
+        string[first_uppercase..].make_ascii_lowercase();
+        string.into()
+    } else {
+        s.into()
+    }
+}
+
 macro_rules! with_all_bounds {
     (
         [ $( $InSelector: tt )* ]
         [ $( $CommonBounds: tt )* ]
         [ $( $FromStr: tt )* ]
     ) => {
         fn from_cow_str<T>(cow: Cow<str>) -> T where T: $($FromStr)* {
             match cow {
                 Cow::Borrowed(s) => T::from(s),
                 Cow::Owned(s) => T::from(s),
             }
         }
 
-        fn from_ascii_lowercase<T>(s: &str) -> T where T: $($FromStr)* {
-            if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') {
-                let mut string = s.to_owned();
-                string[first_uppercase..].make_ascii_lowercase();
-                T::from(string)
-            } else {
-                T::from(s)
-            }
-        }
-
         /// This trait allows to define the parser implementation in regards
         /// of pseudo-classes/elements
         ///
         /// NB: We need Clone so that we can derive(Clone) on struct with that
         /// are parameterized on SelectorImpl. See
         /// https://github.com/rust-lang/rust/issues/26925
         pub trait SelectorImpl: Clone + Sized {
             type AttrValue: $($InSelector)*;
@@ -308,40 +309,64 @@ impl<Impl: SelectorImpl> SelectorMethods
 
         match *self {
             Negation(ref negated) => {
                 for component in negated.iter() {
                     if !component.visit(visitor) {
                         return false;
                     }
                 }
-            },
-            AttrExists(ref selector) |
-            AttrEqual(ref selector, _, _) |
-            AttrIncludes(ref selector, _) |
-            AttrDashMatch(ref selector, _) |
-            AttrPrefixMatch(ref selector, _) |
-            AttrSubstringMatch(ref selector, _) |
-            AttrSuffixMatch(ref selector, _) => {
-                if !visitor.visit_attribute_selector(selector) {
+            }
+
+            AttributeInNoNamespaceExists { ref local_name, ref local_name_lower } => {
+                if !visitor.visit_attribute_selector(
+                    &NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()),
+                    local_name,
+                    local_name_lower,
+                ) {
                     return false;
                 }
             }
+            AttributeInNoNamespace { ref local_name, ref local_name_lower, never_matches, .. }
+            if !never_matches => {
+                if !visitor.visit_attribute_selector(
+                    &NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()),
+                    local_name,
+                    local_name_lower,
+                ) {
+                    return false;
+                }
+            }
+            AttributeOther(ref attr_selector) if !attr_selector.never_matches => {
+                if !visitor.visit_attribute_selector(
+                    &attr_selector.namespace(),
+                    &attr_selector.local_name,
+                    &attr_selector.local_name_lower,
+                ) {
+                    return false;
+                }
+            }
+
             NonTSPseudoClass(ref pseudo_class) => {
                 if !pseudo_class.visit(visitor) {
                     return false;
                 }
             },
             _ => {}
         }
 
         true
     }
 }
 
+pub fn namespace_empty_string<Impl: SelectorImpl>() -> Impl::NamespaceUrl {
+    // Rust type’s default, not default namespace
+    Impl::NamespaceUrl::default()
+}
+
 /// A ComplexSelectors stores a sequence of simple selectors and combinators. The
 /// iterator classes allow callers to iterate at either the raw sequence level or
 /// at the level of sequences of simple selectors separated by combinators. Most
 /// callers want the higher-level iterator.
 ///
 /// We store selectors internally left-to-right (in parsing order), but the
 /// canonical iteration order is right-to-left (selector matching order). The
 /// iterators abstract over these details.
@@ -518,29 +543,30 @@ pub enum Component<Impl: SelectorImpl> {
     Namespace(Impl::NamespacePrefix, Impl::NamespaceUrl),
 
     ExplicitUniversalType,
     LocalName(LocalName<Impl>),
 
     ID(Impl::Identifier),
     Class(Impl::ClassName),
 
-    // Attribute selectors
-    AttrExists(AttrSelector<Impl>),  // [foo]
-    AttrEqual(AttrSelector<Impl>, Impl::AttrValue, CaseSensitivity),  // [foo=bar]
-    AttrIncludes(AttrSelector<Impl>, Impl::AttrValue),  // [foo~=bar]
-    AttrDashMatch(AttrSelector<Impl>, Impl::AttrValue), // [foo|=bar]
-    AttrPrefixMatch(AttrSelector<Impl>, Impl::AttrValue),  // [foo^=bar]
-    AttrSubstringMatch(AttrSelector<Impl>, Impl::AttrValue),  // [foo*=bar]
-    AttrSuffixMatch(AttrSelector<Impl>, Impl::AttrValue),  // [foo$=bar]
-
-    AttrIncludesNeverMatch(AttrSelector<Impl>, Impl::AttrValue),  // empty value or with whitespace
-    AttrPrefixNeverMatch(AttrSelector<Impl>, Impl::AttrValue),  // empty value
-    AttrSubstringNeverMatch(AttrSelector<Impl>, Impl::AttrValue),  // empty value
-    AttrSuffixNeverMatch(AttrSelector<Impl>, Impl::AttrValue),  // empty value
+    AttributeInNoNamespaceExists {
+        local_name: Impl::LocalName,
+        local_name_lower: Impl::LocalName,
+    },
+    AttributeInNoNamespace {
+        local_name: Impl::LocalName,
+        local_name_lower: Impl::LocalName,
+        operator: AttrSelectorOperator,
+        value: Impl::AttrValue,
+        case_sensitivity: ParsedCaseSensitivity,
+        never_matches: bool,
+    },
+    // Use a Box in the less common cases with more data to keep size_of::<Component>() small.
+    AttributeOther(Box<AttrSelectorWithNamespace<Impl>>),
 
     // Pseudo-classes
     //
     // CSS3 Negation only takes a simple simple selector, but we still need to
     // treat it as a compound selector because it might be a type selector which
     // we represent as a namespace and a localname.
     //
     // Note: if/when we upgrade this to CSS4, which supports combinators, we
@@ -555,17 +581,16 @@ pub enum Component<Impl: SelectorImpl> {
     NthLastChild(i32, i32),
     NthOfType(i32, i32),
     NthLastOfType(i32, i32),
     FirstOfType,
     LastOfType,
     OnlyOfType,
     NonTSPseudoClass(Impl::NonTSPseudoClass),
     PseudoElement(Impl::PseudoElement),
-    // ...
 }
 
 impl<Impl: SelectorImpl> Component<Impl> {
     /// Compute the ancestor hash to check against the bloom filter.
     fn ancestor_hash(&self) -> Option<u32> {
         match *self {
             Component::LocalName(LocalName { ref name, ref lower_name }) => {
                 // Only insert the local-name into the filter if it's all lowercase.
@@ -600,58 +625,22 @@ impl<Impl: SelectorImpl> Component<Impl>
     pub fn as_combinator(&self) -> Option<Combinator> {
         match *self {
             Component::Combinator(c) => Some(c),
             _ => None,
         }
     }
 }
 
-#[derive(Eq, PartialEq, Clone, Copy, Debug)]
-pub enum CaseSensitivity {
-    CaseSensitive,  // Selectors spec says language-defined, but HTML says sensitive.
-    CaseInsensitive,
-}
-
-
 #[derive(Eq, PartialEq, Clone)]
 pub struct LocalName<Impl: SelectorImpl> {
     pub name: Impl::LocalName,
     pub lower_name: Impl::LocalName,
 }
 
-#[derive(Eq, PartialEq, Clone)]
-pub struct AttrSelector<Impl: SelectorImpl> {
-    pub name: Impl::LocalName,
-    pub lower_name: Impl::LocalName,
-    pub namespace: NamespaceConstraint<Impl>,
-}
-
-#[derive(Eq, PartialEq, Clone, Debug)]
-pub enum NamespaceConstraint<Impl: SelectorImpl> {
-    Any,
-    Specific(Namespace<Impl>),
-}
-
-#[derive(Eq, PartialEq, Clone)]
-pub struct Namespace<Impl: SelectorImpl> {
-    pub prefix: Option<Impl::NamespacePrefix>,
-    pub url: Impl::NamespaceUrl,
-}
-
-impl<Impl: SelectorImpl> Default for Namespace<Impl> {
-    fn default() -> Self {
-        Namespace {
-            prefix: None,
-            url: Impl::NamespaceUrl::default(),  // empty string
-        }
-    }
-}
-
-
 impl<Impl: SelectorImpl> Debug for Selector<Impl> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         f.write_str("Selector(")?;
         self.to_css(f)?;
         write!(f, ", specificity = 0x{:x})", self.specificity())
     }
 }
 
@@ -659,20 +648,17 @@ impl<Impl: SelectorImpl> Debug for Selec
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.complex.to_css(f) }
 }
 impl<Impl: SelectorImpl> Debug for ComplexSelector<Impl> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
 }
 impl<Impl: SelectorImpl> Debug for Component<Impl> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
 }
-impl<Impl: SelectorImpl> Debug for AttrSelector<Impl> {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
-}
-impl<Impl: SelectorImpl> Debug for Namespace<Impl> {
+impl<Impl: SelectorImpl> Debug for AttrSelectorWithNamespace<Impl> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
 }
 impl<Impl: SelectorImpl> Debug for LocalName<Impl> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_css(f) }
 }
 
 impl<Impl: SelectorImpl> ToCss for SelectorList<Impl> {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
@@ -740,37 +726,36 @@ impl<Impl: SelectorImpl> ToCss for Compo
             DefaultNamespace(_) => Ok(()),
             ExplicitNoNamespace => dest.write_char('|'),
             ExplicitAnyNamespace => dest.write_str("*|"),
             Namespace(ref prefix, _) => {
                 display_to_css_identifier(prefix, dest)?;
                 dest.write_char('|')
             }
 
-            // Attribute selectors
-            AttrExists(ref a) => {
+            AttributeInNoNamespaceExists { ref local_name, .. } => {
                 dest.write_char('[')?;
-                a.to_css(dest)?;
+                display_to_css_identifier(local_name, dest)?;
                 dest.write_char(']')
             }
-            AttrEqual(ref a, ref v, case) => {
-                attr_selector_to_css(a, " = ", v, match case {
-                    CaseSensitivity::CaseSensitive => None,
-                    CaseSensitivity::CaseInsensitive => Some(" i"),
-                }, dest)
+            AttributeInNoNamespace { ref local_name, operator, ref value, case_sensitivity, .. } => {
+                dest.write_char('[')?;
+                display_to_css_identifier(local_name, dest)?;
+                operator.to_css(dest)?;
+                dest.write_char('"')?;
+                write!(CssStringWriter::new(dest), "{}", value)?;
+                dest.write_char('"')?;
+                match case_sensitivity {
+                    ParsedCaseSensitivity::CaseSensitive |
+                    ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {},
+                    ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?,
+                }
+                dest.write_char(']')
             }
-            AttrDashMatch(ref a, ref v) => attr_selector_to_css(a, " |= ", v, None, dest),
-            AttrIncludesNeverMatch(ref a, ref v) |
-            AttrIncludes(ref a, ref v) => attr_selector_to_css(a, " ~= ", v, None, dest),
-            AttrPrefixNeverMatch(ref a, ref v) |
-            AttrPrefixMatch(ref a, ref v) => attr_selector_to_css(a, " ^= ", v, None, dest),
-            AttrSubstringNeverMatch(ref a, ref v) |
-            AttrSubstringMatch(ref a, ref v) => attr_selector_to_css(a, " *= ", v, None, dest),
-            AttrSuffixNeverMatch(ref a, ref v) |
-            AttrSuffixMatch(ref a, ref v) => attr_selector_to_css(a, " $= ", v, None, dest),
+            AttributeOther(ref attr_selector) => attr_selector.to_css(dest),
 
             // Pseudo-classes
             Negation(ref arg) => {
                 dest.write_str(":not(")?;
                 debug_assert!(single_simple_selector(arg));
                 for component in arg.iter() {
                     component.to_css(dest)?;
                 }
@@ -789,61 +774,55 @@ impl<Impl: SelectorImpl> ToCss for Compo
             NthLastChild(a, b) => write!(dest, ":nth-last-child({}n{:+})", a, b),
             NthOfType(a, b) => write!(dest, ":nth-of-type({}n{:+})", a, b),
             NthLastOfType(a, b) => write!(dest, ":nth-last-of-type({}n{:+})", a, b),
             NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest),
         }
     }
 }
 
-impl<Impl: SelectorImpl> ToCss for AttrSelector<Impl> {
+impl<Impl: SelectorImpl> ToCss for AttrSelectorWithNamespace<Impl> {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        if let NamespaceConstraint::Specific(ref ns) = self.namespace {
-            ns.to_css(dest)?;
+        dest.write_char('[')?;
+        match self.namespace {
+            NamespaceConstraint::Specific((ref prefix, _)) => {
+                display_to_css_identifier(prefix, dest)?;
+                dest.write_char('|')?
+            }
+            NamespaceConstraint::Any => {
+                dest.write_str("*|")?
+            }
         }
-        display_to_css_identifier(&self.name, dest)
-    }
-}
-
-impl<Impl: SelectorImpl> ToCss for Namespace<Impl> {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        if let Some(ref prefix) = self.prefix {
-            display_to_css_identifier(prefix, dest)?;
-            dest.write_char('|')?;
+        display_to_css_identifier(&self.local_name, dest)?;
+        match self.operation {
+            ParsedAttrSelectorOperation::Exists => {},
+            ParsedAttrSelectorOperation::WithValue {
+                operator, case_sensitivity, ref expected_value
+            } => {
+                operator.to_css(dest)?;
+                dest.write_char('"')?;
+                write!(CssStringWriter::new(dest), "{}", expected_value)?;
+                dest.write_char('"')?;
+                match case_sensitivity {
+                    ParsedCaseSensitivity::CaseSensitive |
+                    ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {},
+                    ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?,
+                }
+            },
         }
-        Ok(())
+        dest.write_char(']')
     }
 }
 
 impl<Impl: SelectorImpl> ToCss for LocalName<Impl> {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         display_to_css_identifier(&self.name, dest)
     }
 }
 
-fn attr_selector_to_css<Impl, W>(attr: &AttrSelector<Impl>,
-                                 operator: &str,
-                                 value: &Impl::AttrValue,
-                                 modifier: Option<&str>,
-                                 dest: &mut W)
-                                 -> fmt::Result
-where Impl: SelectorImpl, W: fmt::Write
-{
-    dest.write_char('[')?;
-    attr.to_css(dest)?;
-    dest.write_str(operator)?;
-    dest.write_char('"')?;
-    write!(CssStringWriter::new(dest), "{}", value)?;
-    dest.write_char('"')?;
-    if let Some(m) = modifier {
-        dest.write_str(m)?;
-    }
-    dest.write_char(']')
-}
-
 /// Serialize the output of Display as a CSS identifier
 fn display_to_css_identifier<T: Display, W: fmt::Write>(x: &T, dest: &mut W) -> fmt::Result {
     // FIXME(SimonSapin): it is possible to avoid this heap allocation
     // by creating a stream adapter like cssparser::CssStringWriter
     // that holds and writes to `&mut W` and itself implements `fmt::Write`.
     //
     // I haven’t done this yet because it would require somewhat complex and fragile state machine
     // to support in `fmt::Write::write_char` cases that,
@@ -929,28 +908,19 @@ fn complex_selector_specificity<Impl>(se
             Component::PseudoElement(..) |
             Component::LocalName(..) => {
                 specificity.element_selectors += 1
             }
             Component::ID(..) => {
                 specificity.id_selectors += 1
             }
             Component::Class(..) |
-            Component::AttrExists(..) |
-            Component::AttrEqual(..) |
-            Component::AttrIncludes(..) |
-            Component::AttrDashMatch(..) |
-            Component::AttrPrefixMatch(..) |
-            Component::AttrSubstringMatch(..) |
-            Component::AttrSuffixMatch(..) |
-
-            Component::AttrIncludesNeverMatch(..) |
-            Component::AttrPrefixNeverMatch(..) |
-            Component::AttrSubstringNeverMatch(..) |
-            Component::AttrSuffixNeverMatch(..) |
+            Component::AttributeInNoNamespace { .. } |
+            Component::AttributeInNoNamespaceExists { .. } |
+            Component::AttributeOther(..) |
 
             Component::FirstChild | Component::LastChild |
             Component::OnlyChild | Component::Root |
             Component::Empty |
             Component::NthChild(..) |
             Component::NthLastChild(..) |
             Component::NthOfType(..) |
             Component::NthLastOfType(..) |
@@ -1116,17 +1086,17 @@ fn parse_type_selector<P, Impl>(parser: 
                 }
                 QNamePrefix::ImplicitNoNamespace => {
                     unreachable!()  // Not returned with in_attr_selector = false
                 }
             }
             match local_name {
                 Some(name) => {
                     sequence.push(Component::LocalName(LocalName {
-                        lower_name: from_ascii_lowercase(&name),
+                        lower_name: from_cow_str(to_ascii_lowercase(&name)),
                         name: from_cow_str(name),
                     }))
                 }
                 None => {
                     sequence.push(Component::ExplicitUniversalType)
                 }
             }
             Ok(true)
@@ -1226,108 +1196,153 @@ fn parse_qualified_name<'i, 't, P, Impl>
     }
 }
 
 
 fn parse_attribute_selector<P, Impl>(parser: &P, input: &mut CssParser)
                                      -> Result<Component<Impl>, ()>
     where P: Parser<Impl=Impl>, Impl: SelectorImpl
 {
-    let attr = match parse_qualified_name(parser, input, /* in_attr_selector = */ true)? {
+    let namespace;
+    let local_name;
+    match parse_qualified_name(parser, input, /* in_attr_selector = */ true)? {
         None => return Err(()),
         Some((_, None)) => unreachable!(),
-        Some((namespace, Some(local_name))) => AttrSelector {
-            namespace: match namespace {
+        Some((ns, Some(ln))) => {
+            local_name = ln;
+            namespace = match ns {
                 QNamePrefix::ImplicitNoNamespace |
                 QNamePrefix::ExplicitNoNamespace => {
-                    NamespaceConstraint::Specific(Namespace {
-                        prefix: None,
-                        url: Impl::NamespaceUrl::default(),
-                    })
+                    None
                 }
                 QNamePrefix::ExplicitNamespace(prefix, url) => {
-                    NamespaceConstraint::Specific(Namespace {
-                        prefix: Some(prefix),
-                        url: url,
-                    })
+                    Some(NamespaceConstraint::Specific((prefix, url)))
                 }
                 QNamePrefix::ExplicitAnyNamespace => {
-                    NamespaceConstraint::Any
+                    Some(NamespaceConstraint::Any)
                 }
                 QNamePrefix::ImplicitAnyNamespace |
                 QNamePrefix::ImplicitDefaultNamespace(_) => {
                     unreachable!()  // Not returned with in_attr_selector = true
                 }
-            },
-            lower_name: from_ascii_lowercase(&local_name),
-            name: from_cow_str(local_name),
-        },
-    };
+            }
+        }
+    }
 
+    let operator;
+    let value;
+    let never_matches;
     match input.next() {
         // [foo]
-        Err(()) => Ok(Component::AttrExists(attr)),
+        Err(()) => {
+            let local_name_lower = from_cow_str(to_ascii_lowercase(&local_name));
+            let local_name = from_cow_str(local_name);
+            if let Some(namespace) = namespace {
+                return Ok(Component::AttributeOther(Box::new(AttrSelectorWithNamespace {
+                    namespace: namespace,
+                    local_name: local_name,
+                    local_name_lower: local_name_lower,
+                    operation: ParsedAttrSelectorOperation::Exists,
+                    never_matches: false,
+                })))
+            } else {
+                return Ok(Component::AttributeInNoNamespaceExists {
+                    local_name: local_name,
+                    local_name_lower: local_name_lower,
+                })
+            }
+        }
 
         // [foo=bar]
         Ok(Token::Delim('=')) => {
-            let value = input.expect_ident_or_string()?;
-            let flags = parse_attribute_flags(input)?;
-            Ok(Component::AttrEqual(attr, from_cow_str(value), flags))
+            value = input.expect_ident_or_string()?;
+            never_matches = false;
+            operator = AttrSelectorOperator::Equal;
         }
         // [foo~=bar]
         Ok(Token::IncludeMatch) => {
-            let value = input.expect_ident_or_string()?;
-            if value.is_empty() || value.contains(SELECTOR_WHITESPACE) {
-                Ok(Component::AttrIncludesNeverMatch(attr, from_cow_str(value)))
-            } else {
-                Ok(Component::AttrIncludes(attr, from_cow_str(value)))
-            }
+            value = input.expect_ident_or_string()?;
+            never_matches = value.is_empty() || value.contains(SELECTOR_WHITESPACE);
+            operator = AttrSelectorOperator::Includes;
         }
         // [foo|=bar]
         Ok(Token::DashMatch) => {
-            let value = input.expect_ident_or_string()?;
-            Ok(Component::AttrDashMatch(attr, from_cow_str(value)))
+            value = input.expect_ident_or_string()?;
+            never_matches = false;
+            operator = AttrSelectorOperator::DashMatch;
         }
         // [foo^=bar]
         Ok(Token::PrefixMatch) => {
-            let value = input.expect_ident_or_string()?;
-            if value.is_empty() {
-                Ok(Component::AttrPrefixNeverMatch(attr, from_cow_str(value)))
-            } else {
-                Ok(Component::AttrPrefixMatch(attr, from_cow_str(value)))
-            }
+            value = input.expect_ident_or_string()?;
+            never_matches = value.is_empty();
+            operator = AttrSelectorOperator::Prefix;
         }
         // [foo*=bar]
         Ok(Token::SubstringMatch) => {
-            let value = input.expect_ident_or_string()?;
-            if value.is_empty() {
-                Ok(Component::AttrSubstringNeverMatch(attr, from_cow_str(value)))
-            } else {
-                Ok(Component::AttrSubstringMatch(attr, from_cow_str(value)))
-            }
+            value = input.expect_ident_or_string()?;
+            never_matches = value.is_empty();
+            operator = AttrSelectorOperator::Substring;
         }
         // [foo$=bar]
         Ok(Token::SuffixMatch) => {
-            let value = input.expect_ident_or_string()?;
-            if value.is_empty() {
-                Ok(Component::AttrSuffixNeverMatch(attr, from_cow_str(value)))
-            } else {
-                Ok(Component::AttrSuffixMatch(attr, from_cow_str(value)))
+            value = input.expect_ident_or_string()?;
+            never_matches = value.is_empty();
+            operator = AttrSelectorOperator::Suffix;
+        }
+        _ => return Err(())
+    }
+
+    let mut case_sensitivity = parse_attribute_flags(input)?;
+
+    let value = from_cow_str(value);
+    let local_name_lower;
+    {
+        let local_name_lower_cow = to_ascii_lowercase(&local_name);
+        if let ParsedCaseSensitivity::CaseSensitive = case_sensitivity {
+            if namespace.is_none() &&
+                include!(concat!(env!("OUT_DIR"), "/ascii_case_insensitive_html_attributes.rs"))
+                .contains(&*local_name_lower_cow)
+            {
+                case_sensitivity =
+                    ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument
             }
         }
-        _ => Err(())
+        local_name_lower = from_cow_str(local_name_lower_cow);
+    }
+    let local_name = from_cow_str(local_name);
+    if let Some(namespace) = namespace {
+        Ok(Component::AttributeOther(Box::new(AttrSelectorWithNamespace {
+            namespace: namespace,
+            local_name: local_name,
+            local_name_lower: local_name_lower,
+            never_matches: never_matches,
+            operation: ParsedAttrSelectorOperation::WithValue {
+                operator: operator,
+                case_sensitivity: case_sensitivity,
+                expected_value: value,
+            }
+        })))
+    } else {
+        Ok(Component::AttributeInNoNamespace {
+            local_name: local_name,
+            local_name_lower: local_name_lower,
+            operator: operator,
+            value: value,
+            case_sensitivity: case_sensitivity,
+            never_matches: never_matches,
+        })
     }
 }
 
 
-fn parse_attribute_flags(input: &mut CssParser) -> Result<CaseSensitivity, ()> {
+fn parse_attribute_flags(input: &mut CssParser) -> Result<ParsedCaseSensitivity, ()> {
     match input.next() {
-        Err(()) => Ok(CaseSensitivity::CaseSensitive),
+        Err(()) => Ok(ParsedCaseSensitivity::CaseSensitive),
         Ok(Token::Ident(ref value)) if value.eq_ignore_ascii_case("i") => {
-            Ok(CaseSensitivity::CaseInsensitive)
+            Ok(ParsedCaseSensitivity::AsciiCaseInsensitive)
         }
         _ => Err(())
     }
 }
 
 
 /// Level 3: Parse **one** simple_selector.  (Though we might insert a second
 /// implied "<defaultns>|*" type selector.)
@@ -1848,24 +1863,22 @@ pub mod tests {
                            Component::ID(DummyAtom::from("bar")),
                        )),
             specificity_and_flags: specificity(1, 1, 1),
         }))));
         // Default namespace does not apply to attribute selectors
         // https://github.com/mozilla/servo/pull/1652
         let mut parser = DummyParser::default();
         assert_eq!(parse_ns("[Foo]", &parser), Ok(SelectorList(vec!(Selector {
-            inner: SelectorInner::from_vec(vec!(
-                Component::AttrExists(AttrSelector {
-                    name: DummyAtom::from("Foo"),
-                    lower_name: DummyAtom::from("foo"),
-                    namespace: NamespaceConstraint::Specific(Namespace {
-                        prefix: None,
-                        url: "".into(),
-                    }) }))),
+            inner: SelectorInner::from_vec(vec![
+                Component::AttributeInNoNamespaceExists {
+                    local_name: DummyAtom::from("Foo"),
+                    local_name_lower: DummyAtom::from("foo"),
+                }
+            ]),
             specificity_and_flags: specificity(0, 1, 0),
         }))));
         assert_eq!(parse_ns("svg|circle", &parser), Err(()));
         parser.ns_prefixes.insert(DummyAtom("svg".into()), DummyAtom(SVG.into()));
         assert_eq!(parse_ns("svg|circle", &parser), Ok(SelectorList(vec![Selector {
             inner: SelectorInner::from_vec(
                 vec![
                     Component::Namespace(DummyAtom("svg".into()), SVG.into()),
@@ -1888,24 +1901,20 @@ pub mod tests {
         // https://github.com/mozilla/servo/pull/1652
         // but it does apply to implicit type selectors
         // https://github.com/servo/rust-selectors/pull/82
         parser.default_ns = Some(MATHML.into());
         assert_eq!(parse_ns("[Foo]", &parser), Ok(SelectorList(vec!(Selector {
             inner: SelectorInner::from_vec(
                 vec![
                     Component::DefaultNamespace(MATHML.into()),
-                    Component::AttrExists(AttrSelector {
-                        name: DummyAtom::from("Foo"),
-                        lower_name: DummyAtom::from("foo"),
-                        namespace: NamespaceConstraint::Specific(Namespace {
-                            prefix: None,
-                            url: "".into(),
-                        }),
-                    }),
+                    Component::AttributeInNoNamespaceExists {
+                        local_name: DummyAtom::from("Foo"),
+                        local_name_lower: DummyAtom::from("foo"),
+                    },
                 ]),
             specificity_and_flags: specificity(0, 1, 0),
         }))));
         // Default namespace does apply to type selectors
         assert_eq!(parse_ns("e", &parser), Ok(SelectorList(vec!(Selector {
             inner: SelectorInner::from_vec(
                 vec!(
                     Component::DefaultNamespace(MATHML.into()),
@@ -1963,24 +1972,24 @@ pub mod tests {
                     }),
                 ].into_boxed_slice())
             )),
             specificity_and_flags: specificity(0, 0, 1),
         }))));
         assert_eq!(parse("[attr |= \"foo\"]"), Ok(SelectorList(vec![Selector {
             inner: SelectorInner::from_vec(
                 vec![
-                    Component::AttrDashMatch(AttrSelector {
-                        name: DummyAtom::from("attr"),
-                        lower_name: DummyAtom::from("attr"),
-                        namespace: NamespaceConstraint::Specific(Namespace {
-                            prefix: None,
-                            url: "".into(),
-                        }),
-                    }, DummyAtom::from("foo"))
+                    Component::AttributeInNoNamespace {
+                        local_name: DummyAtom::from("attr"),
+                        local_name_lower: DummyAtom::from("attr"),
+                        operator: AttrSelectorOperator::DashMatch,
+                        value: DummyAtom::from("foo"),
+                        never_matches: false,
+                        case_sensitivity: ParsedCaseSensitivity::CaseSensitive,
+                    }
                 ]),
             specificity_and_flags: specificity(0, 1, 0),
         }])));
         // https://github.com/mozilla/servo/issues/1723
         assert_eq!(parse("::before"), Ok(SelectorList(vec!(Selector {
             inner: SelectorInner::from_vec(
                 vec![
                     Component::PseudoElement(PseudoElement::Before),
--- a/servo/components/selectors/size_of_tests.rs
+++ b/servo/components/selectors/size_of_tests.rs
@@ -11,18 +11,17 @@ use precomputed_hash::PrecomputedHash;
 use std::fmt;
 use visitor::SelectorVisitor;
 
 size_of_test!(size_of_selector, Selector<Impl>, 48);
 size_of_test!(size_of_pseudo_element, gecko_like_types::PseudoElement, 1);
 size_of_test!(size_of_selector_inner, SelectorInner<Impl>, 40);
 size_of_test!(size_of_complex_selector, ComplexSelector<Impl>, 24);
 
-size_of_test!(size_of_component, Component<Impl>, 64);
-size_of_test!(size_of_attr_selector, AttrSelector<Impl>, 48);
+size_of_test!(size_of_component, Component<Impl>, 32);
 size_of_test!(size_of_pseudo_class, PseudoClass, 24);
 
 impl parser::PseudoElement for gecko_like_types::PseudoElement {
     type Impl = Impl;
 }
 
 // Boilerplate
 
--- a/servo/components/selectors/tree.rs
+++ b/servo/components/selectors/tree.rs
@@ -1,182 +1,77 @@
 /* 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 matching::{ElementSelectorFlags, MatchingContext};
-use parser::{AttrSelector, SelectorImpl};
-use std::ascii::AsciiExt;
+use parser::SelectorImpl;
 
-/// The definition of whitespace per CSS Selectors Level 3 § 4.
-pub static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C'];
-
-// Attribute matching routines. Consumers with simple implementations can implement
-// MatchAttrGeneric instead.
-pub trait MatchAttr {
+pub trait Element: Sized {
     type Impl: SelectorImpl;
 
-    fn match_attr_has(
-        &self,
-        attr: &AttrSelector<Self::Impl>) -> bool;
-
-    fn match_attr_equals(
-        &self,
-        attr: &AttrSelector<Self::Impl>,
-        value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
-
-    fn match_attr_equals_ignore_ascii_case(
-        &self,
-        attr: &AttrSelector<Self::Impl>,
-        value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
-
-    fn match_attr_includes(
-        &self,
-        attr: &AttrSelector<Self::Impl>,
-        value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
-
-    fn match_attr_dash(
-        &self,
-        attr: &AttrSelector<Self::Impl>,
-        value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
-
-    fn match_attr_prefix(
-        &self,
-        attr: &AttrSelector<Self::Impl>,
-        value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
-
-    fn match_attr_substring(
-        &self,
-        attr: &AttrSelector<Self::Impl>,
-        value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
-
-    fn match_attr_suffix(
-        &self,
-        attr: &AttrSelector<Self::Impl>,
-        value: &<Self::Impl as SelectorImpl>::AttrValue) -> bool;
-}
-
-pub trait MatchAttrGeneric {
-    type Impl: SelectorImpl;
-    fn match_attr<F>(&self, attr: &AttrSelector<Self::Impl>, test: F) -> bool where F: Fn(&str) -> bool;
-}
-
-impl<T> MatchAttr for T where T: MatchAttrGeneric, T::Impl: SelectorImpl<AttrValue = String> {
-    type Impl = T::Impl;
-
-    fn match_attr_has(&self, attr: &AttrSelector<Self::Impl>) -> bool {
-        self.match_attr(attr, |_| true)
-    }
-
-    fn match_attr_equals(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
-        self.match_attr(attr, |v| v == value)
-    }
-
-    fn match_attr_equals_ignore_ascii_case(&self, attr: &AttrSelector<Self::Impl>,
-                                           value: &String) -> bool {
-        self.match_attr(attr, |v| v.eq_ignore_ascii_case(value))
-    }
-
-    fn match_attr_includes(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
-        self.match_attr(attr, |attr_value| {
-            attr_value.split(SELECTOR_WHITESPACE).any(|v| v == value)
-        })
-    }
-
-    fn match_attr_dash(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
-        self.match_attr(attr, |attr_value| {
-            // The attribute must start with the pattern.
-            if !attr_value.starts_with(value) {
-                return false
-            }
-
-            // If the strings are the same, we're done.
-            if attr_value.len() == value.len() {
-                return true
-            }
-
-            // The attribute is long than the pattern, so the next character must be '-'.
-            attr_value.as_bytes()[value.len()] == '-' as u8
-        })
-    }
-
-    fn match_attr_prefix(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
-        self.match_attr(attr, |attr_value| {
-            attr_value.starts_with(value)
-        })
-    }
-
-    fn match_attr_substring(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
-        self.match_attr(attr, |attr_value| {
-            attr_value.contains(value)
-        })
-    }
-
-    fn match_attr_suffix(&self, attr: &AttrSelector<Self::Impl>, value: &String) -> bool {
-        self.match_attr(attr, |attr_value| {
-            attr_value.ends_with(value)
-        })
-    }
-}
-
-pub trait Element: MatchAttr + Sized {
     fn parent_element(&self) -> Option<Self>;
 
     /// The parent of a given pseudo-element, after matching a pseudo-element
     /// selector.
     ///
     /// This is guaranteed to be called in a pseudo-element.
     fn pseudo_element_originating_element(&self) -> Option<Self> {
         self.parent_element()
     }
 
-    // Skips non-element nodes
+    /// Skips non-element nodes
     fn first_child_element(&self) -> Option<Self>;
 
-    // Skips non-element nodes
+    /// Skips non-element nodes
     fn last_child_element(&self) -> Option<Self>;
 
-    // Skips non-element nodes
+    /// Skips non-element nodes
     fn prev_sibling_element(&self) -> Option<Self>;
 
-    // Skips non-element nodes
+    /// Skips non-element nodes
     fn next_sibling_element(&self) -> Option<Self>;
 
     fn is_html_element_in_html_document(&self) -> bool;
+
     fn get_local_name(&self) -> &<Self::Impl as SelectorImpl>::BorrowedLocalName;
+
+    /// Empty string for no namespace
     fn get_namespace(&self) -> &<Self::Impl as SelectorImpl>::BorrowedNamespaceUrl;
 
+    fn attr_matches(&self,
+                    ns: &NamespaceConstraint<&<Self::Impl as SelectorImpl>::NamespaceUrl>,
+                    local_name: &<Self::Impl as SelectorImpl>::LocalName,
+                    operation: &AttrSelectorOperation<&<Self::Impl as SelectorImpl>::AttrValue>)
+                    -> bool;
+
     fn match_non_ts_pseudo_class<F>(&self,
                                     pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass,
                                     context: &mut MatchingContext,
                                     flags_setter: &mut F) -> bool
         where F: FnMut(&Self, ElementSelectorFlags);
 
     fn match_pseudo_element(&self,
                             pe: &<Self::Impl as SelectorImpl>::PseudoElement,
                             context: &mut MatchingContext)
                             -> bool;
 
     fn get_id(&self) -> Option<<Self::Impl as SelectorImpl>::Identifier>;
+
     fn has_class(&self, name: &<Self::Impl as SelectorImpl>::ClassName) -> 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`,
     /// i.e. whether it is the root element of a document.
     ///
     /// Note: this can be false even if `.parent_element()` is `None`
     /// if the parent node is a `DocumentFragment`.
     fn is_root(&self) -> bool;
-
-    // Ordinarily I wouldn't use callbacks like this, but the alternative is
-    // really messy, since there is a `JSRef` and a `RefCell` involved. Maybe
-    // in the future when we have associated types and/or a more convenient
-    // JS GC story... --pcwalton
-    fn each_class<F>(&self, callback: F) where F: FnMut(&<Self::Impl as SelectorImpl>::ClassName);
 }
--- a/servo/components/selectors/visitor.rs
+++ b/servo/components/selectors/visitor.rs
@@ -1,31 +1,36 @@
 /* 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/. */
 
 //! Visitor traits for selectors.
 
 #![deny(missing_docs)]
 
-use parser::{AttrSelector, Combinator, Component};
-use parser::{SelectorImpl, SelectorIter};
+use attr::NamespaceConstraint;
+use parser::{Combinator, Component, SelectorImpl, SelectorIter};
 
 /// A trait to visit selector properties.
 ///
 /// All the `visit_foo` methods return a boolean indicating whether the
 /// traversal should continue or not.
 pub trait SelectorVisitor {
     /// The selector implementation this visitor wants to visit.
     type Impl: SelectorImpl;
 
     /// Visit an attribute selector that may match (there are other selectors
     /// that may never match, like those containing whitespace or the empty
     /// string).
-    fn visit_attribute_selector(&mut self, _: &AttrSelector<Self::Impl>) -> bool {
+    fn visit_attribute_selector(
+        &mut self,
+        _namespace: &NamespaceConstraint<&<Self::Impl as SelectorImpl>::NamespaceUrl>,
+        _local_name: &<Self::Impl as SelectorImpl>::LocalName,
+        _local_name_lower: &<Self::Impl as SelectorImpl>::LocalName,
+    ) -> bool {
         true
     }
 
     /// Visit a simple selector.
     fn visit_simple_selector(&mut self, _: &Component<Self::Impl>) -> bool {
         true
     }
 
--- a/servo/components/style/attr.rs
+++ b/servo/components/style/attr.rs
@@ -7,16 +7,17 @@
 //! [attr]: https://dom.spec.whatwg.org/#interface-attr
 
 use {Atom, Prefix, Namespace, LocalName};
 use app_units::Au;
 use cssparser::{self, Color, RGBA};
 use euclid::num::Zero;
 use num_traits::ToPrimitive;
 use properties::PropertyDeclarationBlock;
+use selectors::attr::AttrSelectorOperation;
 use servo_url::ServoUrl;
 use shared_lock::Locked;
 use std::ascii::AsciiExt;
 use std::str::FromStr;
 use str::{HTML_SPACE_CHARACTERS, read_exponent, read_fraction};
 use str::{read_numbers, split_commas, split_html_space_chars};
 use str::str_join;
 use stylearc::Arc;
@@ -344,16 +345,23 @@ impl AttrValue {
     /// Panics if the `AttrValue` is not a `UInt`
     pub fn as_uint_px_dimension(&self) -> LengthOrPercentageOrAuto {
         if let AttrValue::UInt(_, value) = *self {
             LengthOrPercentageOrAuto::Length(Au::from_px(value as i32))
         } else {
             panic!("Uint not found");
         }
     }
+
+    pub fn eval_selector(&self, selector: &AttrSelectorOperation<&String>) -> bool {
+        // FIXME(SimonSapin) this can be more efficient by matching on `(self, selector)` variants
+        // and doing Atom comparisons instead of string comparisons where possible,
+        // with SelectorImpl::AttrValue changed to Atom.
+        selector.eval_str(self)
+    }
 }
 
 impl ::std::ops::Deref for AttrValue {
     type Target = str;
 
     fn deref(&self) -> &str {
         match *self {
             AttrValue::String(ref value) |
@@ -366,16 +374,25 @@ impl ::std::ops::Deref for AttrValue {
                 AttrValue::Url(ref value, _) |
                 AttrValue::Declaration(ref value, _) |
                 AttrValue::Dimension(ref value, _) => &value,
             AttrValue::Atom(ref value) => &value,
         }
     }
 }
 
+impl PartialEq<Atom> for AttrValue {
+    fn eq(&self, other: &Atom) -> bool {
+        match *self {
+            AttrValue::Atom(ref value) => value == other,
+            _ => other == &**self,
+        }
+    }
+}
+
 /// https://html.spec.whatwg.org/multipage/#rules-for-parsing-non-zero-dimension-values
 pub fn parse_nonzero_length(value: &str) -> LengthOrPercentageOrAuto {
     match parse_length(value) {
         LengthOrPercentageOrAuto::Length(x) if x == Au::zero() => LengthOrPercentageOrAuto::Auto,
         LengthOrPercentageOrAuto::Percentage(x) if x == 0. => LengthOrPercentageOrAuto::Auto,
         x => x,
     }
 }
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -376,16 +376,19 @@ pub trait TElement : Eq + PartialEq + De
     fn get_state(&self) -> ElementState;
 
     /// Whether this element has an attribute with a given namespace.
     fn has_attr(&self, namespace: &Namespace, attr: &LocalName) -> bool;
 
     /// Whether an attribute value equals `value`.
     fn attr_equals(&self, namespace: &Namespace, attr: &LocalName, value: &Atom) -> bool;
 
+    /// 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
     /// values as an argument here, but otherwise Servo would crash due to
     /// double borrows to return it.
     fn existing_style_for_restyle_damage<'a>(&'a self,
--- a/servo/components/style/gecko/snapshot.rs
+++ b/servo/components/style/gecko/snapshot.rs
@@ -3,25 +3,24 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! A gecko snapshot, that stores the element attributes and state before they
 //! change in order to properly calculate restyle hints.
 
 use dom::TElement;
 use element_state::ElementState;
 use gecko::snapshot_helpers;
-use gecko::wrapper::{AttrSelectorHelpers, GeckoElement};
+use gecko::wrapper::{NamespaceConstraintHelpers, GeckoElement};
 use gecko_bindings::bindings;
 use gecko_bindings::structs::ServoElementSnapshot;
 use gecko_bindings::structs::ServoElementSnapshotFlags as Flags;
 use gecko_bindings::structs::ServoElementSnapshotTable;
 use restyle_hints::ElementSnapshot;
-use selector_parser::SelectorImpl;
-use selectors::parser::AttrSelector;
-use string_cache::Atom;
+use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint};
+use string_cache::{Atom, Namespace};
 
 /// A snapshot of a Gecko element.
 pub type GeckoElementSnapshot = ServoElementSnapshot;
 
 /// A map from elements to snapshots for Gecko's style back-end.
 pub type SnapshotMap = ServoElementSnapshotTable;
 
 impl SnapshotMap {
@@ -43,98 +42,84 @@ impl SnapshotMap {
 
             bindings::Gecko_GetElementSnapshot(self, element.0).as_ref()
         }
     }
 }
 
 impl GeckoElementSnapshot {
     #[inline]
-    fn is_html_element_in_html_document(&self) -> bool {
-        self.mIsHTMLElementInHTMLDocument
-    }
-
-    #[inline]
     fn has_any(&self, flags: Flags) -> bool {
         (self.mContains as u8 & flags as u8) != 0
     }
 
     fn as_ptr(&self) -> *const Self {
         self
     }
-}
 
-impl ::selectors::MatchAttr for GeckoElementSnapshot {
-    type Impl = SelectorImpl;
-
-    fn match_attr_has(&self, attr: &AttrSelector<SelectorImpl>) -> bool {
-        unsafe {
-            bindings::Gecko_SnapshotHasAttr(self,
-                                            attr.ns_or_null(),
-                                            attr.select_name(self.is_html_element_in_html_document()))
-        }
-    }
-
-    fn match_attr_equals(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
-        unsafe {
-            bindings::Gecko_SnapshotAttrEquals(self,
-                                               attr.ns_or_null(),
-                                               attr.select_name(self.is_html_element_in_html_document()),
-                                               value.as_ptr(),
-                                               /* ignoreCase = */ false)
-        }
-    }
-
-    fn match_attr_equals_ignore_ascii_case(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
-        unsafe {
-            bindings::Gecko_SnapshotAttrEquals(self,
-                                               attr.ns_or_null(),
-                                               attr.select_name(self.is_html_element_in_html_document()),
-                                               value.as_ptr(),
-                                               /* ignoreCase = */ true)
-        }
-    }
-    fn match_attr_includes(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
+    /// selectors::Element::attr_matches
+    pub fn attr_matches(&self,
+                        ns: &NamespaceConstraint<&Namespace>,
+                        local_name: &Atom,
+                        operation: &AttrSelectorOperation<&Atom>)
+                        -> bool {
         unsafe {
-            bindings::Gecko_SnapshotAttrIncludes(self,
-                                                 attr.ns_or_null(),
-                                                 attr.select_name(self.is_html_element_in_html_document()),
-                                                 value.as_ptr())
-        }
-    }
-    fn match_attr_dash(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
-        unsafe {
-            bindings::Gecko_SnapshotAttrDashEquals(self,
-                                                   attr.ns_or_null(),
-                                                   attr.select_name(self.is_html_element_in_html_document()),
-                                                   value.as_ptr())
-        }
-    }
-    fn match_attr_prefix(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
-        unsafe {
-            bindings::Gecko_SnapshotAttrHasPrefix(self,
-                                                  attr.ns_or_null(),
-                                                  attr.select_name(self.is_html_element_in_html_document()),
-                                                  value.as_ptr())
-        }
-    }
-    fn match_attr_substring(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
-        unsafe {
-            bindings::Gecko_SnapshotAttrHasSubstring(self,
-                                                     attr.ns_or_null(),
-                                                     attr.select_name(self.is_html_element_in_html_document()),
-                                                     value.as_ptr())
-        }
-    }
-    fn match_attr_suffix(&self, attr: &AttrSelector<SelectorImpl>, value: &Atom) -> bool {
-        unsafe {
-            bindings::Gecko_SnapshotAttrHasSuffix(self,
-                                                  attr.ns_or_null(),
-                                                  attr.select_name(self.is_html_element_in_html_document()),
-                                                  value.as_ptr())
+            match *operation {
+                AttrSelectorOperation::Exists => {
+                    bindings:: Gecko_SnapshotHasAttr(self,
+                                                     ns.atom_or_null(),
+                                                     local_name.as_ptr())
+                }
+                AttrSelectorOperation::WithValue { operator, case_sensitivity, expected_value } => {
+                    let ignore_case = match case_sensitivity {
+                        CaseSensitivity::CaseSensitive => false,
+                        CaseSensitivity::AsciiCaseInsensitive => true,
+                    };
+                    // FIXME: case sensitivity for operators other than Equal
+                    match operator {
+                        AttrSelectorOperator::Equal => bindings::Gecko_SnapshotAttrEquals(
+                            self,
+                            ns.atom_or_null(),
+                            local_name.as_ptr(),
+                            expected_value.as_ptr(),
+                            ignore_case
+                        ),
+                        AttrSelectorOperator::Includes => bindings::Gecko_SnapshotAttrIncludes(
+                            self,
+                            ns.atom_or_null(),
+                            local_name.as_ptr(),
+                            expected_value.as_ptr(),
+                        ),
+                        AttrSelectorOperator::DashMatch => bindings::Gecko_SnapshotAttrDashEquals(
+                            self,
+                            ns.atom_or_null(),
+                            local_name.as_ptr(),
+                            expected_value.as_ptr(),
+                        ),
+                        AttrSelectorOperator::Prefix => bindings::Gecko_SnapshotAttrHasPrefix(
+                            self,
+                            ns.atom_or_null(),
+                            local_name.as_ptr(),
+                            expected_value.as_ptr(),
+                        ),
+                        AttrSelectorOperator::Suffix => bindings::Gecko_SnapshotAttrHasSuffix(
+                            self,
+                            ns.atom_or_null(),
+                            local_name.as_ptr(),
+                            expected_value.as_ptr(),
+                        ),
+                        AttrSelectorOperator::Substring => bindings::Gecko_SnapshotAttrHasSubstring(
+                            self,
+                            ns.atom_or_null(),
+                            local_name.as_ptr(),
+                            expected_value.as_ptr(),
+                        ),
+                    }
+                }
+            }
         }
     }
 }
 
 impl ElementSnapshot for GeckoElementSnapshot {
     fn state(&self) -> Option<ElementState> {
         if self.has_any(Flags::State) {
             Some(ElementState::from_bits_truncate(self.mState))
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -59,18 +59,18 @@ use logical_geometry::WritingMode;
 use media_queries::Device;
 use properties::{ComputedValues, parse_style_attribute};
 use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock};
 use properties::animated_properties::{AnimationValue, AnimationValueMap, TransitionProperty};
 use properties::style_structs::Font;
 use rule_tree::CascadeLevel as ServoCascadeLevel;
 use selector_parser::ElementExt;
 use selectors::Element;
+use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint};
 use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
-use selectors::parser::{AttrSelector, NamespaceConstraint};
 use shared_lock::Locked;
 use sink::Push;
 use std::cell::RefCell;
 use std::collections::HashMap;
 use std::fmt;
 use std::hash::{Hash, Hasher};
 use std::ptr;
 use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
@@ -650,16 +650,24 @@ impl<'le> TElement for GeckoElement<'le>
             bindings::Gecko_AttrEquals(self.0,
                                        namespace.0.as_ptr(),
                                        attr.as_ptr(),
                                        val.as_ptr(),
                                        /* ignoreCase = */ false)
         }
     }
 
+    fn each_class<F>(&self, callback: F)
+        where F: FnMut(&Atom)
+    {
+        snapshot_helpers::each_class(self.0,
+                                     callback,
+                                     Gecko_ClassOrClassList)
+    }
+
     fn existing_style_for_restyle_damage<'a>(&'a self,
                                              _existing_values: &'a ComputedValues,
                                              pseudo: Option<&PseudoElement>)
                                              -> Option<&'a nsStyleContext> {
         // TODO(emilio): Migrate this to CSSPseudoElementType.
         let atom_ptr = pseudo.map_or(ptr::null_mut(), |p| p.atom().as_ptr());
         unsafe {
             let context_ptr = Gecko_GetStyleContext(self.0, atom_ptr);
@@ -1077,16 +1085,18 @@ impl<'le> PresentationalHintsSynthesizer
             if self.get_local_name().as_ptr() == atom!("math").as_ptr() {
                 hints.push(MATHML_LANG_RULE.clone());
             }
         }
     }
 }
 
 impl<'le> ::selectors::Element for GeckoElement<'le> {
+    type Impl = SelectorImpl;
+
     fn parent_element(&self) -> Option<Self> {
         let parent_node = self.as_node().parent_node();
         parent_node.and_then(|n| n.as_element())
     }
 
     fn pseudo_element_originating_element(&self) -> Option<Self> {
         debug_assert!(self.implemented_pseudo_element().is_some());
         self.closest_non_native_anonymous_ancestor()
@@ -1131,16 +1141,78 @@ impl<'le> ::selectors::Element for Gecko
             if let Some(el) = sibling_node.as_element() {
                 return Some(el)
             }
             sibling = sibling_node.next_sibling();
         }
         None
     }
 
+    fn attr_matches(&self,
+                    ns: &NamespaceConstraint<&Namespace>,
+                    local_name: &Atom,
+                    operation: &AttrSelectorOperation<&Atom>)
+                    -> bool {
+        unsafe {
+            match *operation {
+                AttrSelectorOperation::Exists => {
+                    bindings::Gecko_HasAttr(self.0,
+                                            ns.atom_or_null(),
+                                            local_name.as_ptr())
+                }
+                AttrSelectorOperation::WithValue { operator, case_sensitivity, expected_value } => {
+                    let ignore_case = match case_sensitivity {
+                        CaseSensitivity::CaseSensitive => false,
+                        CaseSensitivity::AsciiCaseInsensitive => true,
+                    };
+                    // FIXME: case sensitivity for operators other than Equal
+                    match operator {
+                        AttrSelectorOperator::Equal => bindings::Gecko_AttrEquals(
+                            self.0,
+                            ns.atom_or_null(),
+                            local_name.as_ptr(),
+                            expected_value.as_ptr(),
+                            ignore_case
+                        ),
+                        AttrSelectorOperator::Includes => bindings::Gecko_AttrIncludes(
+                            self.0,
+                            ns.atom_or_null(),
+                            local_name.as_ptr(),
+                            expected_value.as_ptr(),
+                        ),
+                        AttrSelectorOperator::DashMatch => bindings::Gecko_AttrDashEquals(
+                            self.0,
+                            ns.atom_or_null(),
+                            local_name.as_ptr(),
+                            expected_value.as_ptr(),
+                        ),
+                        AttrSelectorOperator::Prefix => bindings::Gecko_AttrHasPrefix(
+                            self.0,
+                            ns.atom_or_null(),
+                            local_name.as_ptr(),
+                            expected_value.as_ptr(),
+                        ),
+                        AttrSelectorOperator::Suffix => bindings::Gecko_AttrHasSuffix(
+                            self.0,
+                            ns.atom_or_null(),
+                            local_name.as_ptr(),
+                            expected_value.as_ptr(),
+                        ),
+                        AttrSelectorOperator::Substring => bindings::Gecko_AttrHasSubstring(
+                            self.0,
+                            ns.atom_or_null(),
+                            local_name.as_ptr(),
+                            expected_value.as_ptr(),
+                        ),
+                    }
+                }
+            }
+        }
+    }
+
     fn is_root(&self) -> bool {
         unsafe {
             Gecko_IsRootElement(self.0)
         }
     }
 
     fn is_empty(&self) -> bool {
         !self.as_node().dom_children().any(|child| unsafe {
@@ -1317,128 +1389,35 @@ impl<'le> ::selectors::Element for Gecko
             return false;
         }
 
         snapshot_helpers::has_class(self.0,
                                     name,
                                     Gecko_ClassOrClassList)
     }
 
-    fn each_class<F>(&self, callback: F)
-        where F: FnMut(&Atom)
-    {
-        if !self.may_have_class() {
-            return;
-        }
-
-        snapshot_helpers::each_class(self.0,
-                                     callback,
-                                     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 few helpers to help with attribute selectors and snapshotting.
-pub trait AttrSelectorHelpers {
+pub trait NamespaceConstraintHelpers {
     /// Returns the namespace of the selector, or null otherwise.
-    fn ns_or_null(&self) -> *mut nsIAtom;
-    /// Returns the proper selector name depending on whether the requesting
-    /// element is an HTML element in an HTML document or not.
-    fn select_name(&self, is_html_element_in_html_document: bool) -> *mut nsIAtom;
-}
-
-impl AttrSelectorHelpers for AttrSelector<SelectorImpl> {
-    fn ns_or_null(&self) -> *mut nsIAtom {
-        match self.namespace {
-            NamespaceConstraint::Any => ptr::null_mut(),
-            NamespaceConstraint::Specific(ref ns) => ns.url.0.as_ptr(),
-        }
-    }
-
-    fn select_name(&self, is_html_element_in_html_document: bool) -> *mut nsIAtom {
-        if is_html_element_in_html_document {
-            self.lower_name.as_ptr()
-        } else {
-            self.name.as_ptr()
-        }
-    }
+    fn atom_or_null(&self) -> *mut nsIAtom;
 }
 
-impl<'le> ::selectors::MatchAttr for GeckoElement<'le> {
-    type Impl = SelectorImpl;
-
-    fn match_attr_has(&self, attr: &AttrSelector<Self::Impl>) -> bool {
-        unsafe {
-            bindings::Gecko_HasAttr(self.0,
-                                    attr.ns_or_null(),
-                                    attr.select_name(self.is_html_element_in_html_document()))
-        }
-    }
-    fn match_attr_equals(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool {
-        unsafe {
-            bindings::Gecko_AttrEquals(self.0,
-                                       attr.ns_or_null(),
-                                       attr.select_name(self.is_html_element_in_html_document()),
-                                       value.as_ptr(),
-                                       /* ignoreCase = */ false)
-        }
-    }
-    fn match_attr_equals_ignore_ascii_case(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool {
-        unsafe {
-            bindings::Gecko_AttrEquals(self.0,
-                                       attr.ns_or_null(),
-                                       attr.select_name(self.is_html_element_in_html_document()),
-                                       value.as_ptr(),
-                                       /* ignoreCase = */ false)
-        }
-    }
-    fn match_attr_includes(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool {
-        unsafe {
-            bindings::Gecko_AttrIncludes(self.0,
-                                         attr.ns_or_null(),
-                                         attr.select_name(self.is_html_element_in_html_document()),
-                                         value.as_ptr())
-        }
-    }
-    fn match_attr_dash(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool {
-        unsafe {
-            bindings::Gecko_AttrDashEquals(self.0,
-                                           attr.ns_or_null(),
-                                           attr.select_name(self.is_html_element_in_html_document()),
-                                           value.as_ptr())
-        }
-    }
-    fn match_attr_prefix(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool {
-        unsafe {
-            bindings::Gecko_AttrHasPrefix(self.0,
-                                          attr.ns_or_null(),
-                                          attr.select_name(self.is_html_element_in_html_document()),
-                                          value.as_ptr())
-        }
-    }
-    fn match_attr_substring(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool {
-        unsafe {
-            bindings::Gecko_AttrHasSubstring(self.0,
-                                             attr.ns_or_null(),
-                                             attr.select_name(self.is_html_element_in_html_document()),
-                                             value.as_ptr())
-        }
-    }
-    fn match_attr_suffix(&self, attr: &AttrSelector<Self::Impl>, value: &Atom) -> bool {
-        unsafe {
-            bindings::Gecko_AttrHasSuffix(self.0,
-                                          attr.ns_or_null(),
-                                          attr.select_name(self.is_html_element_in_html_document()),
-                                          value.as_ptr())
+impl<'a> NamespaceConstraintHelpers for NamespaceConstraint<&'a Namespace> {
+    fn atom_or_null(&self) -> *mut nsIAtom {
+        match *self {
+            NamespaceConstraint::Any => ptr::null_mut(),
+            NamespaceConstraint::Specific(ref ns) => ns.0.as_ptr(),
         }
     }
 }
 
 impl<'le> ElementExt for GeckoElement<'le> {
     #[inline]
     fn is_link(&self) -> bool {
         let mut context = MatchingContext::new(MatchingMode::Normal, None);
--- a/servo/components/style/restyle_hints.rs
+++ b/servo/components/style/restyle_hints.rs
@@ -2,28 +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 LocalName;
+use Namespace;
 use dom::TElement;
 use element_state::*;
 #[cfg(feature = "gecko")]
 use gecko_bindings::structs::nsRestyleHint;
 #[cfg(feature = "servo")]
 use heapsize::HeapSizeOf;
-use selector_parser::{AttrValue, NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap};
-use selectors::{Element, MatchAttr};
+use selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap, AttrValue};
+use selectors::Element;
+use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
 use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
 use selectors::matching::matches_selector;
-use selectors::parser::{AttrSelector, Combinator, Component, Selector};
-use selectors::parser::{SelectorInner, SelectorMethods};
+use selectors::parser::{Combinator, Component, Selector, SelectorInner, SelectorMethods};
 use selectors::visitor::SelectorVisitor;
 use smallvec::SmallVec;
 use std::borrow::Borrow;
 use std::cell::Cell;
 use std::clone::Clone;
 use stylist::SelectorMap;
 
 bitflags! {
@@ -165,17 +167,17 @@ impl HeapSizeOf for RestyleHint {
 /// Gecko does this differently for element states, and passes a mask called
 /// mStateMask, which indicates the states that need to be ignored during
 /// selector matching. This saves an ElementWrapper allocation and an additional
 /// selector match call at the expense of additional complexity inside the
 /// selector matching logic. This only works for boolean states though, so we
 /// still need to take the ElementWrapper approach for attribute-dependent
 /// style. So we do it the same both ways for now to reduce complexity, but it's
 /// worth measuring the performance impact (if any) of the mStateMask approach.
-pub trait ElementSnapshot : Sized + MatchAttr<Impl=SelectorImpl> {
+pub trait ElementSnapshot : Sized {
     /// The state of the snapshot, if any.
     fn state(&self) -> Option<ElementState>;
 
     /// If this snapshot contains attribute information.
     fn has_attrs(&self) -> bool;
 
     /// The ID attribute per this snapshot. Should only be called if
     /// `has_attrs()` returns true.
@@ -237,100 +239,16 @@ impl<'a, E> ElementWrapper<'a, E>
 
         match snapshot.state() {
             Some(state) => state ^ self.element.get_state(),
             None => ElementState::empty(),
         }
     }
 }
 
-impl<'a, E> MatchAttr for ElementWrapper<'a, E>
-    where E: TElement,
-{
-    type Impl = SelectorImpl;
-
-    fn match_attr_has(&self, attr: &AttrSelector<SelectorImpl>) -> bool {
-        match self.snapshot() {
-            Some(snapshot) if snapshot.has_attrs()
-                => snapshot.match_attr_has(attr),
-            _   => self.element.match_attr_has(attr)
-        }
-    }
-
-    fn match_attr_equals(&self,
-                         attr: &AttrSelector<SelectorImpl>,
-                         value: &AttrValue) -> bool {
-        match self.snapshot() {
-            Some(snapshot) if snapshot.has_attrs()
-                => snapshot.match_attr_equals(attr, value),
-            _   => self.element.match_attr_equals(attr, value)
-        }
-    }
-
-    fn match_attr_equals_ignore_ascii_case(&self,
-                                           attr: &AttrSelector<SelectorImpl>,
-                                           value: &AttrValue) -> bool {
-        match self.snapshot() {
-            Some(snapshot) if snapshot.has_attrs()
-                => snapshot.match_attr_equals_ignore_ascii_case(attr, value),
-            _   => self.element.match_attr_equals_ignore_ascii_case(attr, value)
-        }
-    }
-
-    fn match_attr_includes(&self,
-                           attr: &AttrSelector<SelectorImpl>,
-                           value: &AttrValue) -> bool {
-        match self.snapshot() {
-            Some(snapshot) if snapshot.has_attrs()
-                => snapshot.match_attr_includes(attr, value),
-            _   => self.element.match_attr_includes(attr, value)
-        }
-    }
-
-    fn match_attr_dash(&self,
-                       attr: &AttrSelector<SelectorImpl>,
-                       value: &AttrValue) -> bool {
-        match self.snapshot() {
-            Some(snapshot) if snapshot.has_attrs()
-                => snapshot.match_attr_dash(attr, value),
-            _   => self.element.match_attr_dash(attr, value)
-        }
-    }
-
-    fn match_attr_prefix(&self,
-                         attr: &AttrSelector<SelectorImpl>,
-                         value: &AttrValue) -> bool {
-        match self.snapshot() {
-            Some(snapshot) if snapshot.has_attrs()
-                => snapshot.match_attr_prefix(attr, value),
-            _   => self.element.match_attr_prefix(attr, value)
-        }
-    }
-
-    fn match_attr_substring(&self,
-                            attr: &AttrSelector<SelectorImpl>,
-                            value: &AttrValue) -> bool {
-        match self.snapshot() {
-            Some(snapshot) if snapshot.has_attrs()
-                => snapshot.match_attr_substring(attr, value),
-            _   => self.element.match_attr_substring(attr, value)
-        }
-    }
-
-    fn match_attr_suffix(&self,
-                         attr: &AttrSelector<SelectorImpl>,
-                         value: &AttrValue) -> bool {
-        match self.snapshot() {
-            Some(snapshot) if snapshot.has_attrs()
-                => snapshot.match_attr_suffix(attr, value),
-            _   => self.element.match_attr_suffix(attr, value)
-        }
-    }
-}
-
 #[cfg(feature = "gecko")]
 fn dir_selector_to_state(s: &[u16]) -> ElementState {
     // Jump through some hoops to deal with our Box<[u16]> thing.
     const LTR: [u16; 4] = [b'l' as u16, b't' as u16, b'r' as u16, 0];
     const RTL: [u16; 4] = [b'r' as u16, b't' as u16, b'l' as u16, 0];
     if LTR == *s {
         IN_LTR_STATE
     } else if RTL == *s {
@@ -340,16 +258,18 @@ fn dir_selector_to_state(s: &[u16]) -> E
         // match anything.
         ElementState::empty()
     }
 }
 
 impl<'a, E> Element for ElementWrapper<'a, E>
     where E: TElement,
 {
+    type Impl = SelectorImpl;
+
     fn match_non_ts_pseudo_class<F>(&self,
                                     pseudo_class: &NonTSPseudoClass,
                                     context: &mut MatchingContext,
                                     _setter: &mut F)
                                     -> bool
         where F: FnMut(&Self, ElementSelectorFlags),
     {
         // :moz-any is quite special, because we need to keep matching as a
@@ -445,16 +365,29 @@ impl<'a, E> Element for ElementWrapper<'
     fn get_local_name(&self) -> &<Self::Impl as ::selectors::SelectorImpl>::BorrowedLocalName {
         self.element.get_local_name()
     }
 
     fn get_namespace(&self) -> &<Self::Impl as ::selectors::SelectorImpl>::BorrowedNamespaceUrl {
         self.element.get_namespace()
     }
 
+    fn attr_matches(&self,
+                    ns: &NamespaceConstraint<&Namespace>,
+                    local_name: &LocalName,
+                    operation: &AttrSelectorOperation<&AttrValue>)
+                    -> bool {
+        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> {
         match self.snapshot() {
             Some(snapshot) if snapshot.has_attrs()
                 => snapshot.id_attr(),
             _   => self.element.get_id()
         }
     }
 
@@ -469,25 +402,16 @@ impl<'a, E> Element for ElementWrapper<'
     fn is_empty(&self) -> bool {
         self.element.is_empty()
     }
 
     fn is_root(&self) -> bool {
         self.element.is_root()
     }
 
-    fn each_class<F>(&self, callback: F)
-        where F: FnMut(&Atom) {
-        match self.snapshot() {
-            Some(snapshot) if snapshot.has_attrs()
-                => snapshot.each_class(callback),
-            _   => self.element.each_class(callback)
-        }
-    }
-
     fn pseudo_element_originating_element(&self) -> Option<Self> {
         self.element.closest_non_native_anonymous_ancestor()
             .map(|e| ElementWrapper::new(e, self.snapshot_map))
     }
 }
 
 fn selector_to_state(sel: &Component<SelectorImpl>) -> ElementState {
     match *sel {
@@ -499,23 +423,19 @@ fn selector_to_state(sel: &Component<Sel
         _ => ElementState::empty(),
     }
 }
 
 fn is_attr_selector(sel: &Component<SelectorImpl>) -> bool {
     match *sel {
         Component::ID(_) |
         Component::Class(_) |
-        Component::AttrExists(_) |
-        Component::AttrEqual(_, _, _) |
-        Component::AttrIncludes(_, _) |
-        Component::AttrDashMatch(_, _) |
-        Component::AttrPrefixMatch(_, _) |
-        Component::AttrSubstringMatch(_, _) |
-        Component::AttrSuffixMatch(_, _) => true,
+        Component::AttributeInNoNamespaceExists { .. } |
+        Component::AttributeInNoNamespace { .. } |
+        Component::AttributeOther(_) => true,
         _ => false,
     }
 }
 
 fn combinator_to_restyle_hint(combinator: Option<Combinator>) -> RestyleHint {
     match combinator {
         None => RESTYLE_SELF,
         Some(c) => match c {
--- a/servo/components/style/servo/selector_parser.rs
+++ b/servo/components/style/servo/selector_parser.rs
@@ -9,19 +9,20 @@
 use {Atom, Prefix, Namespace, LocalName};
 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::{ElementExt, PseudoElementCascadeType, SelectorParser};
-use selectors::{Element, MatchAttrGeneric};
+use selectors::Element;
+use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
 use selectors::matching::{MatchingContext, MatchingMode};
-use selectors::parser::{AttrSelector, SelectorMethods};
+use selectors::parser::SelectorMethods;
 use selectors::visitor::SelectorVisitor;
 use std::borrow::Cow;
 use std::fmt;
 use std::fmt::Debug;
 use std::mem;
 use std::ops::{Deref, DerefMut};
 
 /// A pseudo-element, both public and private.
@@ -169,47 +170,57 @@ pub enum NonTSPseudoClass {
     Hover,
     Indeterminate,
     Lang(Box<str>),
     Link,
     PlaceholderShown,
     ReadWrite,
     ReadOnly,
     ServoNonZeroBorder,
+    ServoCaseSensitiveTypeAttr(Atom),
     Target,
     Visited,
 }
 
 impl ToCss for NonTSPseudoClass {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         use self::NonTSPseudoClass::*;
-        if let Lang(ref lang) = *self {
-            dest.write_str(":lang(")?;
-            serialize_identifier(lang, dest)?;
-            return dest.write_str(")");
+        match *self {
+            Lang(ref lang) => {
+                dest.write_str(":lang(")?;
+                serialize_identifier(lang, dest)?;
+                return dest.write_str(")")
+            }
+            ServoCaseSensitiveTypeAttr(ref value) => {
+                dest.write_str(":-servo-case-sensitive-type-attr(")?;
+                serialize_identifier(value, dest)?;
+                return dest.write_str(")")
+            }
+            _ => {}
         }
 
         dest.write_str(match *self {
             Active => ":active",
             AnyLink => ":any-link",
             Checked => ":checked",
             Disabled => ":disabled",
             Enabled => ":enabled",
             Focus => ":focus",
             Fullscreen => ":fullscreen",
             Hover => ":hover",
             Indeterminate => ":indeterminate",
-            Lang(_) => unreachable!(),
             Link => ":link",
             PlaceholderShown => ":placeholder-shown",
             ReadWrite => ":read-write",
             ReadOnly => ":read-only",
             ServoNonZeroBorder => ":-servo-nonzero-border",
             Target => ":target",
             Visited => ":visited",
+            Lang(_) |
+            ServoCaseSensitiveTypeAttr(_) => unreachable!(),
         })
     }
 }
 
 impl SelectorMethods for NonTSPseudoClass {
     type Impl = SelectorImpl;
 
 
@@ -238,17 +249,18 @@ impl NonTSPseudoClass {
             ReadOnly | ReadWrite => IN_READ_WRITE_STATE,
             PlaceholderShown => IN_PLACEHOLDER_SHOWN_STATE,
             Target => IN_TARGET_STATE,
 
             AnyLink |
             Lang(_) |
             Link |
             Visited |
-            ServoNonZeroBorder => ElementState::empty(),
+            ServoNonZeroBorder |
+            ServoCaseSensitiveTypeAttr(_) => ElementState::empty(),
         }
     }
 
     /// Returns true if the given pseudoclass should trigger style sharing cache revalidation.
     pub fn needs_cache_revalidation(&self) -> bool {
         self.state_flag().is_empty()
     }
 }
@@ -307,17 +319,25 @@ impl<'a> ::selectors::Parser for Selecto
     }
 
     fn parse_non_ts_functional_pseudo_class(&self,
                                             name: Cow<str>,
                                             parser: &mut CssParser)
                                             -> Result<NonTSPseudoClass, ()> {
         use self::NonTSPseudoClass::*;
         let pseudo_class = match_ignore_ascii_case!{ &name,
-            "lang" => Lang(String::from(try!(parser.expect_ident_or_string())).into_boxed_str()),
+            "lang" => {
+                Lang(parser.expect_ident_or_string()?.into_owned().into_boxed_str())
+            }
+            "-servo-case-sensitive-type-attr" => {
+                if !self.in_user_agent_stylesheet() {
+                    return Err(());
+                }
+                ServoCaseSensitiveTypeAttr(Atom::from(parser.expect_ident()?))
+            }
             _ => return Err(())
         };
 
         Ok(pseudo_class)
     }
 
     fn parse_pseudo_element(&self, name: Cow<str>)
                             -> Result<PseudoElement, ()> {
@@ -514,20 +534,20 @@ impl ServoElementSnapshot {
 
     fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
         self.attrs.as_ref().unwrap().iter()
             .find(|&&(ref ident, _)| ident.local_name == *name &&
                                      ident.namespace == *namespace)
             .map(|&(_, ref v)| v)
     }
 
-    fn get_attr_ignore_ns(&self, name: &LocalName) -> Option<&AttrValue> {
+    fn any_attr_ignore_ns<F>(&self, name: &LocalName, mut f: F) -> bool
+    where F: FnMut(&AttrValue) -> bool {
         self.attrs.as_ref().unwrap().iter()
-                  .find(|&&(ref ident, _)| ident.local_name == *name)
-                  .map(|&(_, ref v)| v)
+                  .any(|&(ref ident, ref v)| ident.local_name == *name && f(v))
     }
 }
 
 impl ElementSnapshot for ServoElementSnapshot {
     fn state(&self) -> Option<ElementState> {
         self.state.clone()
     }
 
@@ -550,29 +570,32 @@ impl ElementSnapshot for ServoElementSna
         if let Some(v) = self.get_attr(&ns!(), &local_name!("class")) {
             for class in v.as_tokens() {
                 callback(class);
             }
         }
     }
 }
 
-impl MatchAttrGeneric for ServoElementSnapshot {
-    type Impl = SelectorImpl;
-
-    fn match_attr<F>(&self, attr: &AttrSelector<SelectorImpl>, test: F) -> bool
-        where F: Fn(&str) -> bool
-    {
-        use selectors::parser::NamespaceConstraint;
-        let html = self.is_html_element_in_html_document;
-        let local_name = if html { &attr.lower_name } else { &attr.name };
-        match attr.namespace {
-            NamespaceConstraint::Specific(ref ns) => self.get_attr(&ns.url, local_name),
-            NamespaceConstraint::Any => self.get_attr_ignore_ns(local_name),
-        }.map_or(false, |v| test(v))
+impl ServoElementSnapshot {
+    /// selectors::Element::attr_matches
+    pub fn attr_matches(&self,
+                        ns: &NamespaceConstraint<&Namespace>,
+                        local_name: &LocalName,
+                        operation: &AttrSelectorOperation<&String>)
+                        -> bool {
+        match *ns {
+            NamespaceConstraint::Specific(ref ns) => {
+                self.get_attr(ns, local_name)
+                    .map_or(false, |value| value.eval_selector(operation))
+            }
+            NamespaceConstraint::Any => {
+                self.any_attr_ignore_ns(local_name, |value| value.eval_selector(operation))
+            }
+        }
     }
 }
 
 impl<E: Element<Impl=SelectorImpl> + Debug> ElementExt for E {
     fn is_link(&self) -> bool {
         let mut context = MatchingContext::new(MatchingMode::Normal, None);
         self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink,
                                        &mut context,
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -1,17 +1,17 @@
 /* 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/. */
 
 //! Selector matching.
 
 #![deny(missing_docs)]
 
-use {Atom, LocalName};
+use {Atom, LocalName, Namespace};
 use bit_vec::BitVec;
 use context::QuirksMode;
 use data::ComputedStyle;
 use dom::{AnimationRules, TElement};
 use element_state::ElementState;
 use error_reporting::RustLogReporter;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")]
@@ -21,20 +21,21 @@ use media_queries::Device;
 use pdqsort::sort_by;
 use properties::{self, CascadeFlags, ComputedValues};
 #[cfg(feature = "servo")]
 use properties::INHERIT_ALL;
 use properties::PropertyDeclarationBlock;
 use restyle_hints::{RestyleHint, DependencySet};
 use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
 use selector_parser::{SelectorImpl, PseudoElement, SnapshotMap};
+use selectors::attr::NamespaceConstraint;
 use selectors::bloom::BloomFilter;
 use selectors::matching::{AFFECTED_BY_STYLE_ATTRIBUTE, AFFECTED_BY_PRESENTATIONAL_HINTS};
 use selectors::matching::{ElementSelectorFlags, matches_selector, MatchingContext, MatchingMode};
-use selectors::parser::{AttrSelector, Combinator, Component, Selector, SelectorInner, SelectorIter};
+use selectors::parser::{Combinator, Component, Selector, SelectorInner, SelectorIter};
 use selectors::parser::{SelectorMethods, LocalName as LocalNameSelector};
 use selectors::visitor::SelectorVisitor;
 use shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards};
 use sink::Push;
 use smallvec::{SmallVec, VecLike};
 use std::borrow::Borrow;
 use std::collections::HashMap;
 use std::hash::Hash;
@@ -497,17 +498,17 @@ impl Stylist {
         if needs_revalidation(selector) {
             self.selectors_for_cache_revalidation.insert(selector.inner.clone());
         }
     }
 
     /// Returns whether the given attribute might appear in an attribute
     /// selector of some rule in the stylist.
     pub fn might_have_attribute_dependency(&self,
-                                           local_name: &<SelectorImpl as ::selectors::SelectorImpl>::LocalName)
+                                           local_name: &LocalName)
                                            -> bool {
         #[cfg(feature = "servo")]
         let style_lower_name = local_name!("style");
         #[cfg(feature = "gecko")]
         let style_lower_name = atom!("style");
 
         if *local_name == style_lower_name {
             self.style_attribute_dependency
@@ -1083,27 +1084,29 @@ impl Drop for Stylist {
 
 /// Visitor to collect names that appear in attribute selectors and any
 /// dependencies on ElementState bits.
 struct AttributeAndStateDependencyVisitor<'a>(&'a mut Stylist);
 
 impl<'a> SelectorVisitor for AttributeAndStateDependencyVisitor<'a> {
     type Impl = SelectorImpl;
 
-    fn visit_attribute_selector(&mut self, selector: &AttrSelector<Self::Impl>) -> bool {
+    fn visit_attribute_selector(&mut self, _ns: &NamespaceConstraint<&Namespace>,
+                                name: &LocalName, lower_name: &LocalName)
+                                -> bool {
         #[cfg(feature = "servo")]
         let style_lower_name = local_name!("style");
         #[cfg(feature = "gecko")]
         let style_lower_name = atom!("style");
 
-        if selector.lower_name == style_lower_name {
+        if *lower_name == style_lower_name {
             self.0.style_attribute_dependency = true;
         } else {
-            self.0.attribute_dependencies.insert(&selector.name);
-            self.0.attribute_dependencies.insert(&selector.lower_name);
+            self.0.attribute_dependencies.insert(&name);
+            self.0.attribute_dependencies.insert(&lower_name);
         }
         true
     }
 
     fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool {
         if let Component::NonTSPseudoClass(ref p) = *s {
             self.0.state_dependencies.insert(p.state_flag());
         }
@@ -1152,23 +1155,19 @@ impl SelectorVisitor for RevalidationVis
     /// selector to be explicitly matched against both the style sharing cache
     /// entry and the candidate.
     ///
     /// We use this for selectors that can have different matching behavior
     /// between siblings that are otherwise identical as far as the cache is
     /// concerned.
     fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool {
         match *s {
-            Component::AttrExists(_) |
-            Component::AttrEqual(_, _, _) |
-            Component::AttrIncludes(_, _) |
-            Component::AttrDashMatch(_, _) |
-            Component::AttrPrefixMatch(_, _) |
-            Component::AttrSubstringMatch(_, _) |
-            Component::AttrSuffixMatch(_, _) |
+            Component::AttributeInNoNamespaceExists { .. } |
+            Component::AttributeInNoNamespace { .. } |
+            Component::AttributeOther(_) |
             Component::Empty |
             Component::FirstChild |
             Component::LastChild |
             Component::OnlyChild |
             Component::NthChild(..) |
             Component::NthLastChild(..) |
             Component::NthOfType(..) |
             Component::NthLastOfType(..) |
--- a/servo/python/tidy/servo_tidy/tidy.py
+++ b/servo/python/tidy/servo_tidy/tidy.py
@@ -442,16 +442,17 @@ def check_rust(file_name, lines):
     merged_lines = ''
     import_block = False
     whitespace = False
 
     is_lib_rs_file = file_name.endswith("lib.rs")
 
     prev_use = None
     prev_open_brace = False
+    multi_line_string = False
     current_indent = 0
     prev_crate = {}
     prev_mod = {}
     prev_feature_name = ""
     indent = 0
     prev_indent = 0
 
     decl_message = "{} is not in alphabetical order"
@@ -459,16 +460,25 @@ def check_rust(file_name, lines):
     decl_found = "\n\t\033[91mfound: {}\033[0m"
 
     for idx, original_line in enumerate(lines):
         # simplify the analysis
         line = original_line.strip()
         prev_indent = indent
         indent = len(original_line) - len(line)
 
+        # Hack for components/selectors/build.rs
+        if multi_line_string:
+            if line.startswith('"#'):
+                multi_line_string = False
+            else:
+                continue
+        if line.endswith('r#"'):
+            multi_line_string = True
+
         is_attribute = re.search(r"#\[.*\]", line)
         is_comment = re.search(r"^//|^/\*|^\*", line)
 
         # Simple heuristic to avoid common case of no comments.
         if '/' in line:
             comment_depth += line.count('/*')
             comment_depth -= line.count('*/')
 
--- a/servo/resources/presentational-hints.css
+++ b/servo/resources/presentational-hints.css
@@ -13,21 +13,25 @@ div[align=center i], div[align=middle i]
 div[align=justify i] { text-align: justify; }
 
 
 br[clear=left i] { clear: left; }
 br[clear=right i] { clear: right; }
 br[clear=all i], br[clear=both i] { clear: both; }
 
 
-ol[type=1], li[type=1] { list-style-type: decimal; }
-ol[type=a], li[type=a] { list-style-type: lower-alpha; }
-ol[type=A], li[type=A] { list-style-type: upper-alpha; }
-ol[type=i], li[type=i] { list-style-type: lower-roman; }
-ol[type=I], li[type=I] { list-style-type: upper-roman; }
+ol[type="1"], li[type="1"] { list-style-type: decimal; }
+ol:-servo-case-sensitive-type-attr(a),
+li:-servo-case-sensitive-type-attr(a) { list-style-type: lower-alpha; }
+ol:-servo-case-sensitive-type-attr(A),
+li:-servo-case-sensitive-type-attr(A) { list-style-type: upper-alpha; }
+ol:-servo-case-sensitive-type-attr(i),
+li:-servo-case-sensitive-type-attr(i) { list-style-type: lower-roman; }
+ol:-servo-case-sensitive-type-attr(I),
+li:-servo-case-sensitive-type-attr(I) { list-style-type: upper-roman; }
 ul[type=none i], li[type=none i] { list-style-type: none; }
 ul[type=disc i], li[type=disc i] { list-style-type: disc; }
 ul[type=circle i], li[type=circle i] { list-style-type: circle; }
 ul[type=square i], li[type=square i] { list-style-type: square; }
 
 
 table[align=left i] { float: left; }
 table[align=right i] { float: right; }
--- a/servo/servo-tidy.toml
+++ b/servo/servo-tidy.toml
@@ -51,19 +51,16 @@ files = [
   "./components/script/dom/webidls/ForceTouchEvent.webidl",
   "./support/android/openssl.sh",
   # Ignore those files since the issues reported are on purpose
   "./tests/html/bad-line-ends.html",
   "./tests/unit/net/parsable_mime/text",
   "./tests/wpt/mozilla/tests/css/fonts",
   "./tests/wpt/mozilla/tests/css/pre_with_tab.html",
   "./tests/wpt/mozilla/tests/mozilla/textarea_placeholder.html",
-  # Tidy complains about taking &String instead of &str, but they aren't
-  # equivalent given the way the traits are set up.
-  "./components/selectors/tree.rs",
 ]
 # Directories that are ignored for the non-WPT tidy check.
 directories = [
   # Upstream
   "./support/android/apk",
   "./tests/wpt/css-tests",
   "./tests/wpt/harness",
   "./tests/wpt/update",
--- a/servo/tests/unit/style/stylesheets.rs
+++ b/servo/tests/unit/style/stylesheets.rs
@@ -1,16 +1,17 @@
 /* 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::{self, Parser as CssParser, SourcePosition, SourceLocation};
 use html5ever::{Namespace as NsAtom};
 use media_queries::CSSErrorReporterTest;
 use parking_lot::RwLock;
+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;
@@ -92,24 +93,24 @@ fn test_parse_stylesheet() {
                 selectors: SelectorList(vec![
                     Selector::new_for_unit_testing(
                         SelectorInner::from_vec(vec![
                             Component::DefaultNamespace(NsAtom::from("http://www.w3.org/1999/xhtml")),
                             Component::LocalName(LocalName {
                                 name: local_name!("input"),
                                 lower_name: local_name!("input"),
                             }),
-                            Component::AttrEqual(AttrSelector {
-                                name: local_name!("type"),
-                                lower_name: local_name!("type"),
-                                namespace: NamespaceConstraint::Specific(Namespace {
-                                    prefix: None,
-                                    url: ns!()
-                                }),
-                            }, "hidden".to_owned(), CaseSensitivity::CaseInsensitive)
+                            Component::AttributeInNoNamespace {
+                                local_name: local_name!("type"),
+                                local_name_lower: local_name!("type"),
+                                operator: AttrSelectorOperator::Equal,
+                                value: "hidden".to_owned(),
+                                case_sensitivity: ParsedCaseSensitivity::AsciiCaseInsensitive,
+                                never_matches: false,
+                            }
                         ]),
                         (0 << 20) + (1 << 10) + (1 << 0)
                     ),
                 ]),
                 block: Arc::new(stylesheet.shared_lock.wrap(block_from(vec![
                     (PropertyDeclaration::Display(longhands::display::SpecifiedValue::none),
                      Importance::Important),
                     (PropertyDeclaration::Custom(Atom::from("a"),