Bug 1475197 - Part 1: Shrink selectors::Component to 24 bytes. r?emilio draft
authorCameron McCormack <cam@mcc.id.au>
Mon, 16 Jul 2018 12:15:47 +1000
changeset 825252 065b94b9803c665aea86ac7b1432b1e29970ae7e
parent 824607 0d72c7996d60a7c07e35c5f90d78b02a47d17460
child 825253 ac66a76ff0f38d3b5aa07fc5ac273f06a85ea589
push id118046
push userbmo:cam@mcc.id.au
push dateWed, 01 Aug 2018 05:40:57 +0000
reviewersemilio
bugs1475197
milestone63.0a1
Bug 1475197 - Part 1: Shrink selectors::Component to 24 bytes. r?emilio This saves about 37 KiB of memory across the UA style sheets. MozReview-Commit-ID: EoZnlmyWwxX
layout/style/test/test_selectors.html
servo/components/malloc_size_of/Cargo.toml
servo/components/malloc_size_of/lib.rs
servo/components/selectors/Cargo.toml
servo/components/selectors/attr.rs
servo/components/selectors/lib.rs
servo/components/selectors/matching.rs
servo/components/selectors/parser.rs
servo/components/style/Cargo.toml
servo/components/style/gecko/pseudo_element.rs
servo/components/style/gecko/pseudo_element_definition.mako.rs
servo/components/style/gecko/selector_parser.rs
servo/components/style/lib.rs
servo/ports/geckolib/tests/size_of.rs
testing/web-platform/meta/FileAPI/file/__dir__.ini
testing/web-platform/meta/fetch/api/abort/__dir__.ini
testing/web-platform/meta/fetch/api/request/__dir__.ini
testing/web-platform/meta/html/semantics/forms/form-submission-0/__dir__.ini
testing/web-platform/meta/performance-timeline/__dir__.ini
testing/web-platform/meta/service-workers/service-worker/__dir__.ini
--- a/layout/style/test/test_selectors.html
+++ b/layout/style/test/test_selectors.html
@@ -298,16 +298,24 @@ function run() {
         ifdoc.body.innerHTML = "";
         style_text.data = "";
         test_unparseable_via_api(selector);
     }
 
     // [attr] selector
     test_parseable("[attr]")
     test_parseable_via_api("[attr");
+    test_parseable("[ATTR]")
+    should_serialize_to("[attr]", "[attr]");
+    should_serialize_to("[ATTR]", "[ATTR]");
+
+    // Whether we should drop the bar is debatable. This matches Edge
+    // and Safari at the time of writing.
+    should_serialize_to("[|attr]", "[attr]");
+    should_serialize_to("[|ATTR]", "[ATTR]");
 
     // [attr= ] selector
     test_parseable("[attr=\"x\"]");
     test_parseable("[attr='x']");
     test_parseable("[attr=x]");
     test_parseable("[attr=\"\"]");
     test_parseable("[attr='']");
     test_parseable("[attr=\"foo bar\"]");
--- a/servo/components/malloc_size_of/Cargo.toml
+++ b/servo/components/malloc_size_of/Cargo.toml
@@ -32,13 +32,14 @@ hyper_serde = { version = "0.8", optiona
 mozjs = { version = "0.7.1", features = ["promises"], optional = true }
 selectors = { path = "../selectors" }
 serde = { version = "1.0.27", optional = true }
 serde_bytes = { version = "0.10", optional = true }
 servo_arc = { path = "../servo_arc" }
 smallbitvec = "2.1.0"
 smallvec = "0.6"
 string_cache = { version = "0.7", optional = true }
+thin-slice = "0.1.0"
 time = { version = "0.1.17", optional = true }
 url = { version = "1.2", optional = true }
 webrender_api = { git = "https://github.com/servo/webrender", features = ["ipc"], optional = true }
 xml5ever = { version = "0.12", optional = true }
 void = "1.0.2"
--- a/servo/components/malloc_size_of/lib.rs
+++ b/servo/components/malloc_size_of/lib.rs
@@ -58,16 +58,17 @@ extern crate selectors;
 extern crate serde;
 #[cfg(feature = "servo")]
 extern crate serde_bytes;
 extern crate servo_arc;
 extern crate smallbitvec;
 extern crate smallvec;
 #[cfg(feature = "servo")]
 extern crate string_cache;
+extern crate thin_slice;
 #[cfg(feature = "servo")]
 extern crate time;
 #[cfg(feature = "url")]
 extern crate url;
 extern crate void;
 #[cfg(feature = "webrender_api")]
 extern crate webrender_api;
 #[cfg(feature = "servo")]
@@ -226,16 +227,34 @@ impl<T: ?Sized> MallocShallowSizeOf for 
 }
 
 impl<T: MallocSizeOf + ?Sized> MallocSizeOf for Box<T> {
     fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
         self.shallow_size_of(ops) + (**self).size_of(ops)
     }
 }
 
+impl<T> MallocShallowSizeOf for thin_slice::ThinBoxedSlice<T> {
+    fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
+        let mut n = 0;
+        unsafe {
+            n += thin_slice::ThinBoxedSlice::spilled_storage(self)
+                .map_or(0, |ptr| ops.malloc_size_of(ptr));
+            n += ops.malloc_size_of(&**self);
+        }
+        n
+    }
+}
+
+impl<T: MallocSizeOf> MallocSizeOf for thin_slice::ThinBoxedSlice<T> {
+    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
+        self.shallow_size_of(ops) + (**self).size_of(ops)
+    }
+}
+
 impl MallocSizeOf for () {
     fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
         0
     }
 }
 
 impl<T1, T2> MallocSizeOf for (T1, T2)
     where T1: MallocSizeOf, T2: MallocSizeOf
@@ -765,17 +784,17 @@ where
             Component::LastOfType |
             Component::OnlyOfType |
             Component::Host(None) => 0,
         }
     }
 }
 
 impl<Impl: selectors::parser::SelectorImpl> MallocSizeOf
-    for selectors::attr::AttrSelectorWithNamespace<Impl>
+    for selectors::attr::AttrSelectorWithOptionalNamespace<Impl>
 {
     fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
         0
     }
 }
 
 impl MallocSizeOf for Void {
     #[inline]
--- a/servo/components/selectors/Cargo.toml
+++ b/servo/components/selectors/Cargo.toml
@@ -25,11 +25,12 @@ matches = "0.1"
 cssparser = "0.24.0"
 log = "0.4"
 fnv = "1.0"
 fxhash = "0.2"
 phf = "0.7.18"
 precomputed-hash = "0.1"
 servo_arc = { version = "0.1", path = "../servo_arc" }
 smallvec = "0.6"
+thin-slice = "0.1.0"
 
 [build-dependencies]
 phf_codegen = "0.7.18"
--- a/servo/components/selectors/attr.rs
+++ b/servo/components/selectors/attr.rs
@@ -2,30 +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/. */
 
 use cssparser::ToCss;
 use parser::SelectorImpl;
 use std::fmt;
 
 #[derive(Clone, Eq, PartialEq)]
-pub struct AttrSelectorWithNamespace<Impl: SelectorImpl> {
-    pub namespace: NamespaceConstraint<(Impl::NamespacePrefix, Impl::NamespaceUrl)>,
+pub struct AttrSelectorWithOptionalNamespace<Impl: SelectorImpl> {
+    pub namespace: Option<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 {
+impl<Impl: SelectorImpl> AttrSelectorWithOptionalNamespace<Impl> {
+    pub fn namespace(&self) -> Option<NamespaceConstraint<&Impl::NamespaceUrl>> {
+        self.namespace.as_ref().map(|ns| match ns {
             NamespaceConstraint::Any => NamespaceConstraint::Any,
             NamespaceConstraint::Specific((_, ref url)) => NamespaceConstraint::Specific(url),
-        }
+        })
     }
 }
 
 #[derive(Clone, Eq, PartialEq)]
 pub enum NamespaceConstraint<NamespaceUrl> {
     Any,
 
     /// Empty string for no namespace
--- a/servo/components/selectors/lib.rs
+++ b/servo/components/selectors/lib.rs
@@ -14,16 +14,17 @@ extern crate fxhash;
 #[macro_use]
 extern crate log;
 #[macro_use]
 extern crate matches;
 extern crate phf;
 extern crate precomputed_hash;
 extern crate servo_arc;
 extern crate smallvec;
+extern crate thin_slice;
 
 pub mod attr;
 pub mod bloom;
 mod builder;
 pub mod context;
 pub mod matching;
 mod nth_index_cache;
 pub mod parser;
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -694,43 +694,50 @@ where
             element.attr_matches(
                 &NamespaceConstraint::Specific(&::parser::namespace_empty_string::<E::Impl>()),
                 select_name(is_html, local_name, local_name_lower),
                 &AttrSelectorOperation::Exists,
             )
         },
         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),
+                local_name,
                 &AttrSelectorOperation::WithValue {
                     operator: operator,
                     case_sensitivity: case_sensitivity.to_unconditional(is_html),
                     expected_value: value,
                 },
             )
         },
         Component::AttributeOther(ref attr_sel) => {
             if attr_sel.never_matches {
                 return false;
             }
             let is_html = element.is_html_element_in_html_document();
+            let empty_string;
+            let namespace = match attr_sel.namespace() {
+                Some(ns) => ns,
+                None => {
+                    empty_string = ::parser::namespace_empty_string::<E::Impl>();
+                    NamespaceConstraint::Specific(&empty_string)
+                }
+            };
             element.attr_matches(
-                &attr_sel.namespace(),
+                &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 {
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -1,29 +1,31 @@
 /* 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::{AttrSelectorOperator, AttrSelectorWithNamespace, ParsedAttrSelectorOperation};
-use attr::{NamespaceConstraint, ParsedCaseSensitivity, SELECTOR_WHITESPACE};
+use attr::{AttrSelectorOperator, AttrSelectorWithOptionalNamespace};
+use attr::{NamespaceConstraint, ParsedAttrSelectorOperation};
+use attr::{ParsedCaseSensitivity, SELECTOR_WHITESPACE};
 use bloom::BLOOM_HASH_MASK;
 use builder::{SelectorBuilder, SpecificityAndFlags};
 use context::QuirksMode;
 use cssparser::{BasicParseError, BasicParseErrorKind, ParseError, ParseErrorKind};
 use cssparser::{CowRcStr, Delimiter, SourceLocation};
 use cssparser::{CssStringWriter, Parser as CssParser, ToCss, Token};
 use cssparser::{parse_nth, serialize_identifier};
 use precomputed_hash::PrecomputedHash;
 use servo_arc::ThinArc;
 use sink::Push;
 use smallvec::SmallVec;
 use std::borrow::{Borrow, Cow};
 use std::fmt::{self, Debug, Display, Write};
 use std::iter::Rev;
 use std::slice;
+use thin_slice::ThinBoxedSlice;
 pub use visitor::{SelectorVisitor, Visit};
 
 /// 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
@@ -40,16 +42,18 @@ pub trait PseudoElement: Sized + ToCss {
 pub trait NonTSPseudoClass: Sized + ToCss {
     /// The `SelectorImpl` this pseudo-element is used for.
     type Impl: SelectorImpl;
 
     /// Whether this pseudo-class is :active or :hover.
     fn is_active_or_hover(&self) -> bool;
 }
 
+/// Returns a Cow::Borrowed if `s` is already ASCII lowercase, and a
+/// Cow::Owned if `s` had to be converted into ASCII lowercase.
 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()
     }
@@ -423,32 +427,39 @@ where
                     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,
+                    local_name,
                 ) {
                     return false;
                 }
             },
             AttributeOther(ref attr_selector) if !attr_selector.never_matches => {
+                let empty_string;
+                let namespace = match attr_selector.namespace() {
+                    Some(ns) => ns,
+                    None => {
+                        empty_string = ::parser::namespace_empty_string::<Impl>();
+                        NamespaceConstraint::Specific(&empty_string)
+                    }
+                };
                 if !visitor.visit_attribute_selector(
-                    &attr_selector.namespace(),
+                    &namespace,
                     &attr_selector.local_name,
                     &attr_selector.local_name_lower,
                 ) {
                     return false;
                 }
             },
 
             NonTSPseudoClass(ref pseudo_class) => {
@@ -810,38 +821,38 @@ pub enum Component<Impl: SelectorImpl> {
 
     ID(Impl::Identifier),
     Class(Impl::ClassName),
 
     AttributeInNoNamespaceExists {
         local_name: Impl::LocalName,
         local_name_lower: Impl::LocalName,
     },
+    // Used only when local_name is already lowercase.
     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>>),
+    AttributeOther(Box<AttrSelectorWithOptionalNamespace<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
     /// need to think about how this should interact with
     /// visit_complex_selector, and what the consumers of those APIs should do
     /// about the presence of combinators in negation.
-    Negation(Box<[Component<Impl>]>),
+    Negation(ThinBoxedSlice<Component<Impl>>),
     FirstChild,
     LastChild,
     OnlyChild,
     Root,
     Empty,
     Scope,
     NthChild(i32, i32),
     NthLastChild(i32, i32),
@@ -943,17 +954,17 @@ impl<Impl: SelectorImpl> Debug for Selec
     }
 }
 
 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 AttrSelectorWithNamespace<Impl> {
+impl<Impl: SelectorImpl> Debug for AttrSelectorWithOptionalNamespace<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)
     }
@@ -1233,28 +1244,29 @@ impl<Impl: SelectorImpl> ToCss for Compo
                 write_affine(dest, a, b)?;
                 dest.write_char(')')
             },
             NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest),
         }
     }
 }
 
-impl<Impl: SelectorImpl> ToCss for AttrSelectorWithNamespace<Impl> {
+impl<Impl: SelectorImpl> ToCss for AttrSelectorWithOptionalNamespace<Impl> {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
     where
         W: fmt::Write,
     {
         dest.write_char('[')?;
         match self.namespace {
-            NamespaceConstraint::Specific((ref prefix, _)) => {
+            Some(NamespaceConstraint::Specific((ref prefix, _))) => {
                 display_to_css_identifier(prefix, dest)?;
                 dest.write_char('|')?
             },
-            NamespaceConstraint::Any => dest.write_str("*|")?,
+            Some(NamespaceConstraint::Any) => dest.write_str("*|")?,
+            None => {}
         }
         display_to_css_identifier(&self.local_name, dest)?;
         match self.operation {
             ParsedAttrSelectorOperation::Exists => {},
             ParsedAttrSelectorOperation::WithValue {
                 operator,
                 case_sensitivity,
                 ref expected_value,
@@ -1623,18 +1635,18 @@ where
     let location = input.current_source_location();
     let operator = match input.next() {
         // [foo]
         Err(_) => {
             let local_name_lower = to_ascii_lowercase(&local_name).as_ref().into();
             let local_name = local_name.as_ref().into();
             if let Some(namespace) = namespace {
                 return Ok(Component::AttributeOther(Box::new(
-                    AttrSelectorWithNamespace {
-                        namespace: namespace,
+                    AttrSelectorWithOptionalNamespace {
+                        namespace: Some(namespace),
                         local_name: local_name,
                         local_name_lower: local_name_lower,
                         operation: ParsedAttrSelectorOperation::Exists,
                         never_matches: false,
                     },
                 )));
             } else {
                 return Ok(Component::AttributeInNoNamespaceExists {
@@ -1680,50 +1692,51 @@ where
         AttrSelectorOperator::Substring |
         AttrSelectorOperator::Suffix => value.is_empty(),
     };
 
     let mut case_sensitivity = parse_attribute_flags(input)?;
 
     let value = value.as_ref().into();
     let local_name_lower;
+    let local_name_is_ascii_lowercase;
     {
         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
             }
         }
         local_name_lower = local_name_lower_cow.as_ref().into();
+        local_name_is_ascii_lowercase = matches!(local_name_lower_cow, Cow::Borrowed(..));
     }
     let local_name = local_name.as_ref().into();
-    if let Some(namespace) = namespace {
+    if namespace.is_some() || !local_name_is_ascii_lowercase {
         Ok(Component::AttributeOther(Box::new(
-            AttrSelectorWithNamespace {
-                namespace: namespace,
-                local_name: local_name,
-                local_name_lower: local_name_lower,
-                never_matches: never_matches,
+            AttrSelectorWithOptionalNamespace {
+                namespace,
+                local_name,
+                local_name_lower,
+                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,
         })
     }
 }
 
@@ -1780,17 +1793,17 @@ where
             Some(SimpleSelectorParseResult::SlottedPseudo(_)) => {
                 let e = SelectorParseErrorKind::NonSimpleSelectorInNegation;
                 return Err(input.new_custom_error(e));
             },
         }
     }
 
     // Success.
-    Ok(Component::Negation(sequence.into_vec().into_boxed_slice()))
+    Ok(Component::Negation(sequence.into_vec().into_boxed_slice().into()))
 }
 
 /// simple_selector_sequence
 /// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]*
 /// | [ HASH | class | attrib | pseudo | negation ]+
 ///
 /// `Err(())` means invalid selector.
 /// `Ok(None)` is an empty selector
@@ -2620,34 +2633,34 @@ pub mod tests {
         // but not otherwise.
         assert_eq!(
             parse_ns(":not(.cl)", &parser),
             Ok(SelectorList::from_vec(vec![
                 Selector::from_vec(
                     vec![
                         Component::DefaultNamespace(MATHML.into()),
                         Component::Negation(
-                            vec![Component::Class(DummyAtom::from("cl"))].into_boxed_slice(),
+                            vec![Component::Class(DummyAtom::from("cl"))].into_boxed_slice().into(),
                         ),
                     ],
                     specificity(0, 1, 0),
                 ),
             ]))
         );
         assert_eq!(
             parse_ns(":not(*)", &parser),
             Ok(SelectorList::from_vec(vec![
                 Selector::from_vec(
                     vec![
                         Component::DefaultNamespace(MATHML.into()),
                         Component::Negation(
                             vec![
                                 Component::DefaultNamespace(MATHML.into()),
                                 Component::ExplicitUniversalType,
-                            ].into_boxed_slice(),
+                            ].into_boxed_slice().into(),
                         ),
                     ],
                     specificity(0, 0, 0),
                 ),
             ]))
         );
         assert_eq!(
             parse_ns(":not(e)", &parser),
@@ -2657,31 +2670,30 @@ pub mod tests {
                         Component::DefaultNamespace(MATHML.into()),
                         Component::Negation(
                             vec![
                                 Component::DefaultNamespace(MATHML.into()),
                                 Component::LocalName(LocalName {
                                     name: DummyAtom::from("e"),
                                     lower_name: DummyAtom::from("e"),
                                 }),
-                            ].into_boxed_slice(),
+                            ].into_boxed_slice().into(),
                         ),
                     ],
                     specificity(0, 0, 1),
                 ),
             ]))
         );
         assert_eq!(
             parse("[attr|=\"foo\"]"),
             Ok(SelectorList::from_vec(vec![
                 Selector::from_vec(
                     vec![
                         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(0, 1, 0),
                 ),
@@ -2765,17 +2777,17 @@ pub mod tests {
         assert!(parse(":not(#provel > old)").is_err());
         assert!(parse("table[rules]:not([rules=\"none\"]):not([rules=\"\"])").is_ok());
         assert_eq!(
             parse(":not(#provel)"),
             Ok(SelectorList::from_vec(vec![
                 Selector::from_vec(
                     vec![
                         Component::Negation(
-                            vec![Component::ID(DummyAtom::from("provel"))].into_boxed_slice(),
+                            vec![Component::ID(DummyAtom::from("provel"))].into_boxed_slice().into(),
                         ),
                     ],
                     specificity(1, 0, 0),
                 ),
             ]))
         );
         assert_eq!(
             parse_ns(":not(svg|circle)", &parser),
@@ -2784,78 +2796,78 @@ pub mod tests {
                     vec![
                         Component::Negation(
                             vec![
                                 Component::Namespace(DummyAtom("svg".into()), SVG.into()),
                                 Component::LocalName(LocalName {
                                     name: DummyAtom::from("circle"),
                                     lower_name: DummyAtom::from("circle"),
                                 }),
-                            ].into_boxed_slice(),
+                            ].into_boxed_slice().into(),
                         ),
                     ],
                     specificity(0, 0, 1),
                 ),
             ]))
         );
         // https://github.com/servo/servo/issues/16017
         assert_eq!(
             parse_ns(":not(*)", &parser),
             Ok(SelectorList::from_vec(vec![
                 Selector::from_vec(
                     vec![
                         Component::Negation(
-                            vec![Component::ExplicitUniversalType].into_boxed_slice(),
+                            vec![Component::ExplicitUniversalType].into_boxed_slice().into(),
                         ),
                     ],
                     specificity(0, 0, 0),
                 ),
             ]))
         );
         assert_eq!(
             parse_ns(":not(|*)", &parser),
             Ok(SelectorList::from_vec(vec![
                 Selector::from_vec(
                     vec![
                         Component::Negation(
                             vec![
                                 Component::ExplicitNoNamespace,
                                 Component::ExplicitUniversalType,
-                            ].into_boxed_slice(),
+                            ].into_boxed_slice().into(),
                         ),
                     ],
                     specificity(0, 0, 0),
                 ),
             ]))
         );
         // *| should be elided if there is no default namespace.
         // https://github.com/servo/servo/pull/17537
         assert_eq!(
             parse_ns_expected(":not(*|*)", &parser, Some(":not(*)")),
             Ok(SelectorList::from_vec(vec![
                 Selector::from_vec(
                     vec![
                         Component::Negation(
-                            vec![Component::ExplicitUniversalType].into_boxed_slice(),
+                            vec![Component::ExplicitUniversalType].into_boxed_slice().into(),
                         ),
                     ],
                     specificity(0, 0, 0),
                 ),
             ]))
         );
         assert_eq!(
             parse_ns(":not(svg|*)", &parser),
             Ok(SelectorList::from_vec(vec![
                 Selector::from_vec(
                     vec![
                         Component::Negation(
                             vec![
                                 Component::Namespace(DummyAtom("svg".into()), SVG.into()),
                                 Component::ExplicitUniversalType,
-                            ].into_boxed_slice(),
+                            ].into_boxed_slice().into(),
                         ),
                     ],
                     specificity(0, 0, 0),
                 ),
             ]))
         );
 
         assert!(parse("::slotted()").is_err());
--- a/servo/components/style/Cargo.toml
+++ b/servo/components/style/Cargo.toml
@@ -61,16 +61,17 @@ servo_arc = { path = "../servo_arc" }
 servo_atoms = {path = "../atoms", optional = true}
 servo_config = {path = "../config", optional = true}
 smallbitvec = "2.1.1"
 smallvec = "0.6"
 string_cache = { version = "0.7", optional = true }
 style_derive = {path = "../style_derive"}
 style_traits = {path = "../style_traits"}
 servo_url = {path = "../url", optional = true}
+thin-slice = "0.1.0"
 time = "0.1"
 uluru = "0.2"
 unicode-bidi = "0.3"
 unicode-segmentation = "1.0"
 void = "1.0.2"
 
 [target.'cfg(windows)'.dependencies]
 kernel32-sys = "0.2"
--- a/servo/components/style/gecko/pseudo_element.rs
+++ b/servo/components/style/gecko/pseudo_element.rs
@@ -10,16 +10,17 @@
 
 use cssparser::ToCss;
 use gecko_bindings::structs::{self, CSSPseudoElementType};
 use properties::{ComputedValues, PropertyFlags};
 use properties::longhands::display::computed_value::T as Display;
 use selector_parser::{NonTSPseudoClass, PseudoElementCascadeType, SelectorImpl};
 use std::fmt;
 use string_cache::Atom;
+use thin_slice::ThinBoxedSlice;
 use values::serialize_atom_identifier;
 
 include!(concat!(
     env!("OUT_DIR"),
     "/gecko/pseudo_element_definition.rs"
 ));
 
 impl ::selectors::parser::PseudoElement for PseudoElement {
--- a/servo/components/style/gecko/pseudo_element_definition.mako.rs
+++ b/servo/components/style/gecko/pseudo_element_definition.mako.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /// Gecko's pseudo-element definition.
 #[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
 pub enum PseudoElement {
     % for pseudo in PSEUDOS:
         /// ${pseudo.value}
         % if pseudo.is_tree_pseudo_element():
-        ${pseudo.capitalized()}(Box<[Atom]>),
+        ${pseudo.capitalized()}(ThinBoxedSlice<Atom>),
         % else:
         ${pseudo.capitalized()},
         % endif
     % endfor
 }
 
 /// Important: If you change this, you should also update Gecko's
 /// nsCSSPseudoElements::IsEagerlyCascadedInServo.
@@ -204,17 +204,17 @@ impl PseudoElement {
     }
 
     /// Construct a tree pseudo-element from atom and args.
     #[inline]
     pub fn from_tree_pseudo_atom(atom: &Atom, args: Box<[Atom]>) -> Option<Self> {
         % for pseudo in PSEUDOS:
             % if pseudo.is_tree_pseudo_element():
                 if atom == &atom!("${pseudo.value}") {
-                    return Some(PseudoElement::${pseudo.capitalized()}(args));
+                    return Some(PseudoElement::${pseudo.capitalized()}(args.into()));
                 }
             % endif
         % endfor
         None
     }
 
     /// Constructs a pseudo-element from a string of text.
     ///
@@ -251,17 +251,17 @@ impl PseudoElement {
     ///
     /// Returns `None` if the pseudo-element is not recognized.
     #[inline]
     pub fn tree_pseudo_element(name: &str, args: Box<[Atom]>) -> Option<Self> {
         debug_assert!(name.starts_with("-moz-tree-"));
         let tree_part = &name[10..];
         % for pseudo in TREE_PSEUDOS:
             if tree_part.eq_ignore_ascii_case("${pseudo.value[11:]}") {
-                return Some(${pseudo_element_variant(pseudo, "args")});
+                return Some(${pseudo_element_variant(pseudo, "args.into()")});
             }
         % endfor
         None
     }
 }
 
 impl ToCss for PseudoElement {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -8,38 +8,40 @@ use cssparser::{BasicParseError, BasicPa
 use cssparser::{CowRcStr, SourceLocation, ToCss, Token};
 use element_state::{DocumentState, ElementState};
 use gecko_bindings::structs;
 use gecko_bindings::structs::RawServoSelectorList;
 use gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI};
 use invalidation::element::document_state::InvalidationMatchingData;
 use selector_parser::{Direction, SelectorParser};
 use selectors::SelectorList;
-use selectors::parser::{self as selector_parser, Selector, SelectorParseErrorKind, Visit};
+use selectors::parser::{self as selector_parser, Selector};
+use selectors::parser::{SelectorParseErrorKind, Visit};
 use selectors::visitor::SelectorVisitor;
 use std::fmt;
 use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss as ToCss_};
+use thin_slice::ThinBoxedSlice;
 
 pub use gecko::pseudo_element::{PseudoElement, EAGER_PSEUDOS, EAGER_PSEUDO_COUNT, PSEUDO_COUNT};
 pub use gecko::snapshot::SnapshotMap;
 
 bitflags! {
     // See NonTSPseudoClass::is_enabled_in()
     struct NonTSPseudoClassFlag: u8 {
         const PSEUDO_CLASS_ENABLED_IN_UA_SHEETS = 1 << 0;
         const PSEUDO_CLASS_ENABLED_IN_CHROME = 1 << 1;
         const PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME =
             NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS.bits |
             NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_CHROME.bits;
     }
 }
 
 /// The type used for storing pseudo-class string arguments.
-pub type PseudoClassStringArg = Box<[u16]>;
+pub type PseudoClassStringArg = ThinBoxedSlice<u16>;
 
 macro_rules! pseudo_class_name {
     (bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
      string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*]) => {
         /// Our representation of a non tree-structural pseudo-class.
         #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq)]
         pub enum NonTSPseudoClass {
             $(
@@ -51,17 +53,17 @@ macro_rules! pseudo_class_name {
                 $s_name(PseudoClassStringArg),
             )*
             /// The `:dir` pseudo-class.
             Dir(Box<Direction>),
             /// The non-standard `:-moz-any` pseudo-class.
             ///
             /// TODO(emilio): We disallow combinators and pseudos here, so we
             /// should use SimpleSelector instead
-            MozAny(Box<[Selector<SelectorImpl>]>),
+            MozAny(ThinBoxedSlice<Selector<SelectorImpl>>),
             /// The non-standard `:-moz-locale-dir` pseudo-class.
             MozLocaleDir(Box<Direction>),
         }
     }
 }
 apply_non_ts_list!(pseudo_class_name);
 
 impl ToCss for NonTSPseudoClass {
@@ -400,34 +402,34 @@ impl<'a, 'i> ::selectors::Parser<'i> for
             (bare: [$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*],
              string: [$(($s_css:expr, $s_name:ident, $s_gecko_type:tt, $s_state:tt, $s_flags:tt),)*]) => {
                 match_ignore_ascii_case! { &name,
                     $($s_css => {
                         let name = parser.expect_ident_or_string()?;
                         // convert to null terminated utf16 string
                         // since that's what Gecko deals with
                         let utf16: Vec<u16> = name.encode_utf16().chain(Some(0u16)).collect();
-                        NonTSPseudoClass::$s_name(utf16.into_boxed_slice())
+                        NonTSPseudoClass::$s_name(utf16.into_boxed_slice().into())
                     }, )*
                     "-moz-locale-dir" => {
                         NonTSPseudoClass::MozLocaleDir(
                             Box::new(Direction::parse(parser)?)
                         )
                     },
                     "dir" => {
                         NonTSPseudoClass::Dir(
                             Box::new(Direction::parse(parser)?)
                         )
                     },
                     "-moz-any" => {
                         NonTSPseudoClass::MozAny(
                             selector_parser::parse_compound_selector_list(
                                 self,
                                 parser,
-                            )?
+                            )?.into()
                         )
                     }
                     _ => return Err(parser.new_custom_error(
                         SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name.clone())
                     ))
                 }
             }
         }
--- a/servo/components/style/lib.rs
+++ b/servo/components/style/lib.rs
@@ -88,16 +88,17 @@ extern crate servo_config;
 extern crate servo_url;
 extern crate smallbitvec;
 extern crate smallvec;
 #[cfg(feature = "servo")]
 extern crate string_cache;
 #[macro_use]
 extern crate style_derive;
 extern crate style_traits;
+extern crate thin_slice;
 extern crate time;
 extern crate uluru;
 extern crate unicode_bidi;
 #[allow(unused_extern_crates)]
 extern crate unicode_segmentation;
 extern crate void;
 
 #[macro_use]
--- a/servo/ports/geckolib/tests/size_of.rs
+++ b/servo/ports/geckolib/tests/size_of.rs
@@ -9,20 +9,20 @@ use style::applicable_declarations::Appl
 use style::data::{ElementData, ElementStyles};
 use style::gecko::selector_parser::{self, SelectorImpl};
 use style::properties::ComputedValues;
 use style::rule_tree::{RuleNode, StrongRuleNode};
 use style::values::computed;
 use style::values::specified;
 
 size_of_test!(size_of_selector, selectors::parser::Selector<SelectorImpl>, 8);
-size_of_test!(size_of_pseudo_element, selector_parser::PseudoElement, 24);
+size_of_test!(size_of_pseudo_element, selector_parser::PseudoElement, 16);
 
-size_of_test!(size_of_component, selectors::parser::Component<SelectorImpl>, 32);
-size_of_test!(size_of_pseudo_class, selector_parser::NonTSPseudoClass, 24);
+size_of_test!(size_of_component, selectors::parser::Component<SelectorImpl>, 24);
+size_of_test!(size_of_pseudo_class, selector_parser::NonTSPseudoClass, 16);
 
 // The size of this is critical to performance on the bloom-basic microbenchmark.
 // When iterating over a large Rule array, we want to be able to fast-reject
 // selectors (with the inline hashes) with as few cache misses as possible.
 size_of_test!(test_size_of_rule, style::stylist::Rule, 32);
 
 // Large pages generate tens of thousands of ComputedValues.
 size_of_test!(test_size_of_cv, ComputedValues, 248);
--- a/testing/web-platform/meta/FileAPI/file/__dir__.ini
+++ b/testing/web-platform/meta/FileAPI/file/__dir__.ini
@@ -1,1 +1,1 @@
-lsan-allowed: [Alloc, NewEmptyScopeData, js_pod_calloc, js_pod_malloc, js_pod_realloc, mozilla::dom::ChromeUtils::GenerateQI, mozilla::dom::Performance::CreateForMainThread]
+lsan-allowed: [Alloc, NewEmptyScopeData, __rdl_alloc, __rdl_realloc, js_pod_calloc, js_pod_malloc, js_pod_realloc, mozilla::dom::ChromeUtils::GenerateQI, mozilla::dom::Performance::CreateForMainThread]
--- a/testing/web-platform/meta/fetch/api/abort/__dir__.ini
+++ b/testing/web-platform/meta/fetch/api/abort/__dir__.ini
@@ -1,2 +1,2 @@
 prefs: [javascript.options.streams:true, dom.streams.enabled:true]
-lsan-allowed: [Alloc, AllocateProtoAndIfaceCache, NewEmptyScopeData, js_new, js_pod_calloc, js_pod_malloc, js_pod_realloc, mozilla::dom::ChromeUtils::GenerateQI, mozilla::dom::DOMException::Create, mozilla::dom::FetchStream::Create, mozilla::dom::Performance::CreateForMainThread]
+lsan-allowed: [Alloc, AllocateProtoAndIfaceCache, NewEmptyScopeData, __rdl_alloc, __rdl_realloc, js_new, js_pod_calloc, js_pod_malloc, js_pod_realloc, mozilla::dom::ChromeUtils::GenerateQI, mozilla::dom::DOMException::Create, mozilla::dom::FetchStream::Create, mozilla::dom::Performance::CreateForMainThread]
--- a/testing/web-platform/meta/fetch/api/request/__dir__.ini
+++ b/testing/web-platform/meta/fetch/api/request/__dir__.ini
@@ -1,1 +1,1 @@
-lsan-allowed: [Alloc, NewEmptyScopeData, js_new, js_pod_calloc, js_pod_malloc, js_pod_realloc, mozilla::dom::ChromeUtils::GenerateQI, mozilla::dom::Performance::CreateForMainThread]
+lsan-allowed: [Alloc, NewEmptyScopeData, __rdl_alloc, __rdl_realloc, js_new, js_pod_calloc, js_pod_malloc, js_pod_realloc, mozilla::dom::ChromeUtils::GenerateQI, mozilla::dom::Performance::CreateForMainThread]
--- a/testing/web-platform/meta/html/semantics/forms/form-submission-0/__dir__.ini
+++ b/testing/web-platform/meta/html/semantics/forms/form-submission-0/__dir__.ini
@@ -1,1 +1,1 @@
-lsan-allowed: [Alloc, NewEmptyScopeData, js_new, js_pod_calloc, js_pod_malloc, js_pod_realloc, mozilla::dom::ChromeUtils::GenerateQI, mozilla::dom::Performance::CreateForMainThread]
+lsan-allowed: [Alloc, NewEmptyScopeData, __rdl_alloc, __rdl_realloc, js_new, js_pod_calloc, js_pod_malloc, js_pod_realloc, mozilla::dom::ChromeUtils::GenerateQI, mozilla::dom::Performance::CreateForMainThread]
--- a/testing/web-platform/meta/performance-timeline/__dir__.ini
+++ b/testing/web-platform/meta/performance-timeline/__dir__.ini
@@ -1,1 +1,1 @@
-lsan-allowed: [Alloc, NewEmptyScopeData, js_new, js_pod_calloc, js_pod_malloc, js_pod_realloc, mozilla::dom::ChromeUtils::GenerateQI, mozilla::dom::Performance::CreateForMainThread, mozilla::dom::PerformanceObserver::Constructor, xpc::CreateSandboxObject]
+lsan-allowed: [Alloc, NewEmptyScopeData, __rdl_alloc, __rdl_realloc, js_new, js_pod_calloc, js_pod_malloc, js_pod_realloc, mozilla::dom::ChromeUtils::GenerateQI, mozilla::dom::Performance::CreateForMainThread, mozilla::dom::PerformanceObserver::Constructor, xpc::CreateSandboxObject]
--- a/testing/web-platform/meta/service-workers/service-worker/__dir__.ini
+++ b/testing/web-platform/meta/service-workers/service-worker/__dir__.ini
@@ -1,2 +1,2 @@
 prefs: [dom.serviceWorkers.enabled:true]
-lsan-allowed: [Alloc, Create, CreateInner, MakeUnique, NewEmptyScopeData, NewPage, OrInsert, Realloc, SharedMutex, js_new, js_pod_calloc, js_pod_malloc, js_pod_realloc, mozilla::BasePrincipal::CreateCodebasePrincipal, mozilla::ThrottledEventQueue::Create, mozilla::dom::ChromeUtils::GenerateQI, mozilla::dom::Performance::CreateForMainThread, mozilla::dom::PerformanceStorageWorker::Create, mozilla::dom::WorkerPrivate::WorkerPrivate, mozilla::net::HttpBaseChannel::HttpBaseChannel, mozilla::net::HttpChannelChild::HttpChannelChild, mozilla::net::nsHttpHandler::NewProxiedChannel2]
+lsan-allowed: [Alloc, Create, CreateInner, MakeUnique, NewEmptyScopeData, NewPage, OrInsert, Realloc, SharedMutex, __rdl_alloc, __rdl_realloc, js_new, js_pod_calloc, js_pod_malloc, js_pod_realloc, mozilla::BasePrincipal::CreateCodebasePrincipal, mozilla::ThrottledEventQueue::Create, mozilla::dom::ChromeUtils::GenerateQI, mozilla::dom::Performance::CreateForMainThread, mozilla::dom::PerformanceStorageWorker::Create, mozilla::dom::WorkerPrivate::WorkerPrivate, mozilla::net::HttpBaseChannel::HttpBaseChannel, mozilla::net::HttpChannelChild::HttpChannelChild, mozilla::net::nsHttpHandler::NewProxiedChannel2]