servo: Merge #19841 - stylo: Avoid separate monomorphizations of CSS serialization for utf-8 and utf-16 (from bholley:single_tocss); r=emilio
authorBobby Holley <bobbyholley@gmail.com>
Mon, 22 Jan 2018 16:58:30 -0600
changeset 400338 073c7a9e4309e0fa34852e220426fe2a5e8680c8
parent 400337 76d337a7168f75dae16403f1697f080527b02e1a
child 400339 336ec3ad9df4be2aab93580018a50270173439dc
push id99138
push userdluca@mozilla.com
push dateTue, 23 Jan 2018 10:14:28 +0000
treeherdermozilla-inbound@95b09e6e9613 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
milestone60.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 #19841 - stylo: Avoid separate monomorphizations of CSS serialization for utf-8 and utf-16 (from bholley:single_tocss); r=emilio https://bugzilla.mozilla.org/show_bug.cgi?id=1431268 Source-Repo: https://github.com/servo/servo Source-Revision: 6f543d3de1658e3cacf7fc2caed7b9bda69e1d23
servo/components/script/dom/cssstyledeclaration.rs
servo/components/style/counter_style/mod.rs
servo/components/style/font_face.rs
servo/components/style/gecko/rules.rs
servo/components/style/properties/declaration_block.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/shared_lock.rs
servo/components/style/str.rs
servo/components/style/stylesheets/document_rule.rs
servo/components/style/stylesheets/font_feature_values_rule.rs
servo/components/style/stylesheets/import_rule.rs
servo/components/style/stylesheets/keyframes_rule.rs
servo/components/style/stylesheets/media_rule.rs
servo/components/style/stylesheets/mod.rs
servo/components/style/stylesheets/namespace_rule.rs
servo/components/style/stylesheets/page_rule.rs
servo/components/style/stylesheets/rule_list.rs
servo/components/style/stylesheets/style_rule.rs
servo/components/style/stylesheets/supports_rule.rs
servo/components/style/stylesheets/viewport_rule.rs
servo/ports/geckolib/glue.rs
servo/tests/unit/style/properties/serialization.rs
--- a/servo/components/script/dom/cssstyledeclaration.rs
+++ b/servo/components/script/dom/cssstyledeclaration.rs
@@ -16,17 +16,17 @@ use dom::window::Window;
 use dom_struct::dom_struct;
 use servo_arc::Arc;
 use servo_url::ServoUrl;
 use style::attr::AttrValue;
 use style::properties::{DeclarationSource, Importance, PropertyDeclarationBlock, PropertyId, LonghandId, ShorthandId};
 use style::properties::{parse_one_declaration_into, parse_style_attribute, SourcePropertyDeclaration};
 use style::selector_parser::PseudoElement;
 use style::shared_lock::Locked;
-use style_traits::{ParsingMode, ToCss};
+use style_traits::ParsingMode;
 
 // http://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface
 #[dom_struct]
 pub struct CSSStyleDeclaration {
     reflector_: Reflector,
     owner: CSSStyleOwner,
     readonly: bool,
     pseudo: Option<PseudoElement>,
@@ -80,17 +80,18 @@ impl CSSStyleOwner {
                     // Note that there's no need to remove the attribute here if
                     // the declaration block is empty[1], and if `attr` is
                     // `None` it means that it necessarily didn't change, so no
                     // need to go through all the set_attribute machinery.
                     //
                     // [1]: https://github.com/whatwg/html/issues/2306
                     if let Some(pdb) = attr {
                         let guard = shared_lock.read();
-                        let serialization = pdb.read_with(&guard).to_css_string();
+                        let mut serialization = String::new();
+                        pdb.read_with(&guard).to_css(&mut serialization).unwrap();
                         el.set_attribute(&local_name!("style"),
                                          AttrValue::Declaration(serialization,
                                                                 pdb));
                     }
                 } else {
                     // Remember to put it back.
                     *el.style_attribute().borrow_mut() = attr;
                 }
@@ -410,29 +411,32 @@ impl CSSStyleDeclarationMethods for CSSS
         self.SetPropertyValue(DOMString::from("float"), value)
     }
 
     // https://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface
     fn IndexedGetter(&self, index: u32) -> Option<DOMString> {
         self.owner.with_block(|pdb| {
             pdb.declarations().get(index as usize).map(|declaration| {
                 let important = pdb.declarations_importance().get(index);
-                let mut css = declaration.to_css_string();
+                let mut css = String::new();
+                declaration.to_css(&mut css).unwrap();
                 if important {
                     css += " !important";
                 }
                 DOMString::from(css)
             })
         })
     }
 
     // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext
     fn CssText(&self) -> DOMString {
         self.owner.with_block(|pdb| {
-            DOMString::from(pdb.to_css_string())
+            let mut serialization = String::new();
+            pdb.to_css(&mut serialization).unwrap();
+            DOMString::from(serialization)
         })
     }
 
     // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-csstext
     fn SetCssText(&self, value: DOMString) -> ErrorResult {
         let window = self.owner.window();
 
         // Step 1
--- a/servo/components/style/counter_style/mod.rs
+++ b/servo/components/style/counter_style/mod.rs
@@ -12,18 +12,19 @@ use cssparser::{Parser, Token, serialize
 use error_reporting::{ContextualParseError, ParseErrorReporter};
 #[cfg(feature = "gecko")] use gecko::rules::CounterStyleDescriptors;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::{ nsCSSCounterDesc, nsCSSValue };
 use parser::{ParserContext, ParserErrorContext, Parse};
 use selectors::parser::SelectorParseErrorKind;
 use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
 #[allow(unused_imports)] use std::ascii::AsciiExt;
 use std::borrow::Cow;
-use std::fmt;
+use std::fmt::{self, Write};
 use std::ops::Range;
+use str::CssStringWriter;
 use style_traits::{Comma, OneOrMoreSeparated, ParseError, StyleParseErrorKind, ToCss};
 use values::CustomIdent;
 
 /// Parse a counter style name reference.
 ///
 /// This allows the reserved counter style names "decimal" and "disc".
 pub fn parse_counter_style_name<'i, 't>(
     input: &mut Parser<'i, 't>
@@ -223,18 +224,17 @@ macro_rules! counter_style_descriptors {
                     )*
                     _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
                 }
                 Ok(())
             }
         }
 
         impl ToCssWithGuard for CounterStyleRuleData {
-            fn to_css<W>(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-            where W: fmt::Write {
+            fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
                 dest.write_str("@counter-style ")?;
                 self.name.to_css(dest)?;
                 dest.write_str(" {\n")?;
                 $(
                     if let Some(ref value) = self.$ident {
                         dest.write_str(concat!("  ", $name, ": "))?;
                         ToCss::to_css(value, dest)?;
                         dest.write_str(";\n")?;
--- a/servo/components/style/font_face.rs
+++ b/servo/components/style/font_face.rs
@@ -15,17 +15,18 @@ use cssparser::{SourceLocation, CowRcStr
 use error_reporting::{ContextualParseError, ParseErrorReporter};
 #[cfg(feature = "gecko")] use gecko_bindings::structs::CSSFontFaceDescriptors;
 #[cfg(feature = "gecko")] use cssparser::UnicodeRange;
 use parser::{ParserContext, ParserErrorContext, Parse};
 #[cfg(feature = "gecko")]
 use properties::longhands::font_language_override;
 use selectors::parser::SelectorParseErrorKind;
 use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
-use std::fmt;
+use std::fmt::{self, Write};
+use str::CssStringWriter;
 use style_traits::{Comma, OneOrMoreSeparated, ParseError, StyleParseErrorKind, ToCss};
 use values::computed::font::FamilyName;
 use values::specified::url::SpecifiedUrl;
 
 /// A source for a font-face rule.
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
 #[derive(Clone, Debug, Eq, PartialEq, ToCss)]
 pub enum Source {
@@ -267,18 +268,17 @@ macro_rules! font_face_descriptors_commo
                 // Leave unset descriptors to eCSSUnit_Null,
                 // FontFaceSet::FindOrCreateUserFontEntryFromFontFace does the defaulting
                 // to initial values.
             }
         }
 
         impl ToCssWithGuard for FontFaceRuleData {
             // Serialization of FontFaceRule is not specced.
-            fn to_css<W>(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-            where W: fmt::Write {
+            fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
                 dest.write_str("@font-face {\n")?;
                 $(
                     if let Some(ref value) = self.$ident {
                         dest.write_str(concat!("  ", $name, ": "))?;
                         ToCss::to_css(value, dest)?;
                         dest.write_str(";\n")?;
                     }
                 )*
--- a/servo/components/style/gecko/rules.rs
+++ b/servo/components/style/gecko/rules.rs
@@ -12,17 +12,19 @@ use font_face::{FontFaceRuleData, Source
 use gecko_bindings::bindings;
 use gecko_bindings::structs::{self, nsCSSFontFaceRule, nsCSSValue};
 use gecko_bindings::structs::{nsCSSCounterDesc, nsCSSCounterStyleRule};
 use gecko_bindings::sugar::ns_css_value::ToNsCssValue;
 use gecko_bindings::sugar::refptr::{RefPtr, UniqueRefPtr};
 use nsstring::nsString;
 use properties::longhands::font_language_override;
 use shared_lock::{ToCssWithGuard, SharedRwLockReadGuard};
-use std::{fmt, str};
+use std::fmt::{self, Write};
+use std::str;
+use str::CssStringWriter;
 use values::computed::font::FamilyName;
 use values::generics::FontSettings;
 
 /// A @font-face rule
 pub type FontFaceRule = RefPtr<nsCSSFontFaceRule>;
 
 impl ToNsCssValue for FamilyName {
     fn convert(self, nscssvalue: &mut nsCSSValue) {
@@ -195,18 +197,17 @@ impl From<FontFaceRuleData> for FontFace
             ))
         };
         data.set_descriptors(&mut result.mDecl.mDescriptors);
         result.get()
     }
 }
 
 impl ToCssWithGuard for FontFaceRule {
-    fn to_css<W>(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
+    fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
         let mut css_text = nsString::new();
         unsafe {
             bindings::Gecko_CSSFontFaceRule_GetCssText(self.get(), &mut *css_text);
         }
         write!(dest, "{}", css_text)
     }
 }
 
@@ -232,18 +233,17 @@ impl From<counter_style::CounterStyleRul
                 bindings::Gecko_CSSCounterStyle_Create(data.name().0.as_ptr()))
         };
         data.set_descriptors(&mut result.mValues);
         result.get()
     }
 }
 
 impl ToCssWithGuard for CounterStyleRule {
-    fn to_css<W>(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
+    fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
         let mut css_text = nsString::new();
         unsafe {
             bindings::Gecko_CSSCounterStyle_GetCssText(self.get(), &mut *css_text);
         }
         write!(dest, "{}", css_text)
     }
 }
 
--- a/servo/components/style/properties/declaration_block.rs
+++ b/servo/components/style/properties/declaration_block.rs
@@ -14,16 +14,17 @@ use error_reporting::{ParseErrorReporter
 use parser::{ParserContext, ParserErrorContext};
 use properties::animated_properties::AnimationValue;
 use shared_lock::Locked;
 use smallbitvec::{self, SmallBitVec};
 use smallvec::SmallVec;
 use std::fmt;
 use std::iter::{DoubleEndedIterator, Zip};
 use std::slice::Iter;
+use str::{CssString, CssStringBorrow, CssStringWriter};
 use style_traits::{ToCss, ParseError, ParsingMode, StyleParseErrorKind};
 use stylesheets::{CssRuleType, Origin, UrlExtraData};
 use super::*;
 use values::computed::Context;
 #[cfg(feature = "gecko")] use properties::animated_properties::AnimationValueMap;
 
 /// The animation rules.
 ///
@@ -301,19 +302,17 @@ impl PropertyDeclarationBlock {
             };
             (decl, importance)
         })
     }
 
     /// Find the value of the given property in this block and serialize it
     ///
     /// <https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertyvalue>
-    pub fn property_value_to_css<W>(&self, property: &PropertyId, dest: &mut W) -> fmt::Result
-        where W: fmt::Write,
-    {
+    pub fn property_value_to_css(&self, property: &PropertyId, dest: &mut CssStringWriter) -> fmt::Result {
         // Step 1.1: done when parsing a string to PropertyId
 
         // Step 1.2
         match property.as_shorthand() {
             Ok(shorthand) => {
                 // Step 1.2.1
                 let mut list = Vec::new();
                 let mut important_count = 0;
@@ -603,25 +602,23 @@ impl PropertyDeclarationBlock {
 
         if longhand_id.is_some() {
             debug_assert!(removed_at_least_one);
         }
         removed_at_least_one
     }
 
     /// Take a declaration block known to contain a single property and serialize it.
-    pub fn single_value_to_css<W>(
+    pub fn single_value_to_css(
         &self,
         property: &PropertyId,
-        dest: &mut W,
+        dest: &mut CssStringWriter,
         computed_values: Option<&ComputedValues>,
         custom_properties_block: Option<&PropertyDeclarationBlock>,
     ) -> fmt::Result
-    where
-        W: fmt::Write,
     {
         match property.as_shorthand() {
             Err(_longhand_or_custom) => {
                 if self.declarations.len() == 1 {
                     let declaration = &self.declarations[0];
                     let custom_properties = if let Some(cv) = computed_values {
                         // If there are extra custom properties for this
                         // declaration block, factor them in too.
@@ -659,17 +656,17 @@ impl PropertyDeclarationBlock {
             }
             Ok(shorthand) => {
                 if !self.declarations.iter().all(|decl| decl.shorthands().contains(&shorthand)) {
                     return Err(fmt::Error)
                 }
                 let iter = self.declarations.iter();
                 match shorthand.get_shorthand_appendable_value(iter) {
                     Some(AppendableValue::Css { css, .. }) => {
-                        dest.write_str(css)
+                        css.append_to(dest)
                     },
                     Some(AppendableValue::DeclarationsForShorthand(_, decls)) => {
                         shorthand.longhands_to_css(decls, dest)
                     }
                     _ => Ok(())
                 }
             }
         }
@@ -733,21 +730,23 @@ impl PropertyDeclarationBlock {
                 builder.cascade(name, value.borrow());
             }
         }
 
         builder.build()
     }
 }
 
-impl ToCss for PropertyDeclarationBlock {
-    // https://drafts.csswg.org/cssom/#serialize-a-css-declaration-block
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
-        where W: fmt::Write,
-    {
+impl PropertyDeclarationBlock {
+    /// Like the method on ToCss, but without the type parameter to avoid
+    /// accidentally monomorphizing this large function multiple times for
+    /// different writers.
+    ///
+    /// https://drafts.csswg.org/cssom/#serialize-a-css-declaration-block
+    pub fn to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
         let mut is_first_serialization = true; // trailing serializations should have a prepended space
 
         // Step 1 -> dest = result list
 
         // Step 2
         let mut already_serialized = PropertyDeclarationIdSet::new();
 
         // Step 3
@@ -830,60 +829,60 @@ impl ToCss for PropertyDeclarationBlock 
                     let appendable_value =
                         match shorthand.get_shorthand_appendable_value(current_longhands.iter().cloned()) {
                             None => continue,
                             Some(appendable_value) => appendable_value,
                         };
 
                     // We avoid re-serializing if we're already an
                     // AppendableValue::Css.
-                    let mut v = String::new();
+                    let mut v = CssString::new();
                     let value = match (appendable_value, found_system) {
                         (AppendableValue::Css { css, with_variables }, _) => {
                             debug_assert!(!css.is_empty());
                             AppendableValue::Css {
                                 css: css,
                                 with_variables: with_variables,
                             }
                         }
                         #[cfg(feature = "gecko")]
                         (_, Some(sys)) => {
                             sys.to_css(&mut v)?;
                             AppendableValue::Css {
-                                css: &v,
+                                css: CssStringBorrow::from(&v),
                                 with_variables: false,
                             }
                         }
                         (other, _) => {
                             append_declaration_value(&mut v, other)?;
 
                             // Substep 6
                             if v.is_empty() {
                                 continue;
                             }
 
                             AppendableValue::Css {
-                                css: &v,
+                                css: CssStringBorrow::from(&v),
                                 with_variables: false,
                             }
                         }
                     };
 
                     // Substeps 7 and 8
                     // We need to check the shorthand whether it's an alias property or not.
                     // If it's an alias property, it should be serialized like its longhand.
                     if shorthand.flags().contains(PropertyFlags::SHORTHAND_ALIAS_PROPERTY) {
-                        append_serialization::<_, Cloned<slice::Iter< _>>, _>(
+                        append_serialization::<Cloned<slice::Iter< _>>, _>(
                              dest,
                              &property,
                              value,
                              importance,
                              &mut is_first_serialization)?;
                     } else {
-                        append_serialization::<_, Cloned<slice::Iter< _>>, _>(
+                        append_serialization::<Cloned<slice::Iter< _>>, _>(
                              dest,
                              &shorthand,
                              value,
                              importance,
                              &mut is_first_serialization)?;
                     }
 
                     for current_longhand in &current_longhands {
@@ -908,17 +907,17 @@ impl ToCss for PropertyDeclarationBlock 
             use std::iter::Cloned;
             use std::slice;
 
             // Steps 3.3.5, 3.3.6 & 3.3.7
             // Need to specify an iterator type here even though it’s unused to work around
             // "error: unable to infer enough type information about `_`;
             //  type annotations or generic parameter binding required [E0282]"
             // Use the same type as earlier call to reuse generated code.
-            append_serialization::<_, Cloned<slice::Iter<_>>, _>(
+            append_serialization::<Cloned<slice::Iter<_>>, _>(
                 dest,
                 &property,
                 AppendableValue::Declaration(declaration),
                 importance,
                 &mut is_first_serialization)?;
 
             // Step 3.3.8
             already_serialized.insert(property);
@@ -940,17 +939,17 @@ pub enum AppendableValue<'a, I>
     ///
     /// FIXME: This needs more docs, where are the shorthands expanded? We print
     /// the property name before-hand, don't we?
     DeclarationsForShorthand(ShorthandId, I),
     /// A raw CSS string, coming for example from a property with CSS variables,
     /// or when storing a serialized shorthand value before appending directly.
     Css {
         /// The raw CSS string.
-        css: &'a str,
+        css: CssStringBorrow<'a>,
         /// Whether the original serialization contained variables or not.
         with_variables: bool,
     }
 }
 
 /// Potentially appends whitespace after the first (property: value;) pair.
 fn handle_first_serialization<W>(dest: &mut W,
                                  is_first_serialization: &mut bool)
@@ -961,45 +960,47 @@ fn handle_first_serialization<W>(dest: &
         dest.write_str(" ")
     } else {
         *is_first_serialization = false;
         Ok(())
     }
 }
 
 /// Append a given kind of appendable value to a serialization.
-pub fn append_declaration_value<'a, W, I>(dest: &mut W,
-                                          appendable_value: AppendableValue<'a, I>)
-                                          -> fmt::Result
-    where W: fmt::Write,
-          I: Iterator<Item=&'a PropertyDeclaration>,
+pub fn append_declaration_value<'a, I>(
+    dest: &mut CssStringWriter,
+    appendable_value: AppendableValue<'a, I>,
+) -> fmt::Result
+where
+    I: Iterator<Item=&'a PropertyDeclaration>,
 {
     match appendable_value {
         AppendableValue::Css { css, .. } => {
-            dest.write_str(css)
+            css.append_to(dest)
         },
         AppendableValue::Declaration(decl) => {
             decl.to_css(dest)
         },
         AppendableValue::DeclarationsForShorthand(shorthand, decls) => {
             shorthand.longhands_to_css(decls, dest)
         }
     }
 }
 
 /// Append a given property and value pair to a serialization.
-pub fn append_serialization<'a, W, I, N>(dest: &mut W,
-                                         property_name: &N,
-                                         appendable_value: AppendableValue<'a, I>,
-                                         importance: Importance,
-                                         is_first_serialization: &mut bool)
-                                         -> fmt::Result
-    where W: fmt::Write,
-          I: Iterator<Item=&'a PropertyDeclaration>,
-          N: ToCss,
+pub fn append_serialization<'a, I, N>(
+    dest: &mut CssStringWriter,
+    property_name: &N,
+    appendable_value: AppendableValue<'a, I>,
+    importance: Importance,
+    is_first_serialization: &mut bool
+) -> fmt::Result
+where
+    I: Iterator<Item=&'a PropertyDeclaration>,
+    N: ToCss,
 {
     handle_first_serialization(dest, is_first_serialization)?;
 
     property_name.to_css(dest)?;
     dest.write_char(':')?;
 
     // for normal parsed values, add a space between key: and value
     match appendable_value {
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -11,18 +11,20 @@
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 #[cfg(feature = "servo")]
 use app_units::Au;
 use custom_properties::CustomPropertiesBuilder;
 use servo_arc::{Arc, UniqueArc};
 use smallbitvec::SmallBitVec;
 use std::borrow::Cow;
-use std::{fmt, mem, ops};
 use std::cell::RefCell;
+use std::fmt::{self, Write};
+use std::mem;
+use std::ops;
 
 #[cfg(feature = "servo")] use cssparser::RGBA;
 use cssparser::{CowRcStr, Parser, TokenSerializationType, serialize_identifier};
 use cssparser::ParserInput;
 #[cfg(feature = "servo")] use euclid::SideOffsets2D;
 use context::QuirksMode;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")] use gecko_bindings::bindings;
@@ -41,16 +43,17 @@ use shared_lock::StylesheetGuards;
 use style_traits::{ParsingMode, ToCss, ParseError, StyleParseErrorKind};
 use stylesheets::{CssRuleType, Origin, UrlExtraData};
 #[cfg(feature = "servo")] use values::Either;
 use values::generics::text::LineHeight;
 use values::computed;
 use values::computed::NonNegativeLength;
 use rule_tree::{CascadeLevel, StrongRuleNode};
 use self::computed_value_flags::*;
+use str::{CssString, CssStringBorrow, CssStringWriter};
 use style_adjuster::StyleAdjuster;
 
 pub use self::declaration_block::*;
 
 #[cfg(feature = "gecko")]
 #[macro_export]
 macro_rules! property_name {
     ($s: tt) => { atom!($s) }
@@ -893,28 +896,28 @@ impl ShorthandId {
         let mut declarations3 = declarations.clone();
 
         let first_declaration = declarations2.next()?;
 
         // https://drafts.csswg.org/css-variables/#variables-in-shorthands
         if let Some(css) = first_declaration.with_variables_from_shorthand(self) {
             if declarations2.all(|d| d.with_variables_from_shorthand(self) == Some(css)) {
                return Some(AppendableValue::Css {
-                   css: css,
+                   css: CssStringBorrow::from(css),
                    with_variables: true,
                });
             }
             return None;
         }
 
         // Check whether they are all the same CSS-wide keyword.
         if let Some(keyword) = first_declaration.get_css_wide_keyword() {
             if declarations2.all(|d| d.get_css_wide_keyword() == Some(keyword)) {
                 return Some(AppendableValue::Css {
-                    css: keyword.to_str(),
+                    css: CssStringBorrow::from(keyword.to_str()),
                     with_variables: false,
                 });
             }
             return None;
         }
 
         // Check whether all declarations can be serialized as part of shorthand.
         if declarations3.all(|d| d.may_serialize_as_part_of_shorthand()) {
@@ -1458,24 +1461,31 @@ pub enum PropertyDeclaration {
         DeclaredValueOwned<Arc<::custom_properties::SpecifiedValue>>
     ),
 }
 
 impl fmt::Debug for PropertyDeclaration {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         self.id().to_css(f)?;
         f.write_str(": ")?;
-        self.to_css(f)
+
+        // Because PropertyDeclaration::to_css requires CssStringWriter, we can't write
+        // it directly to f, and need to allocate an intermediate string. This is
+        // fine for debug-only code.
+        let mut s = CssString::new();
+        self.to_css(&mut s)?;
+        write!(f, "{}", s)
     }
 }
 
-impl ToCss for PropertyDeclaration {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
-        where W: fmt::Write,
-    {
+impl PropertyDeclaration {
+    /// Like the method on ToCss, but without the type parameter to avoid
+    /// accidentally monomorphizing this large function multiple times for
+    /// different writers.
+    pub fn to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
         match *self {
             % for property in data.longhands:
                 PropertyDeclaration::${property.camel_case}(ref value) =>
                     value.to_css(dest),
             % endfor
             PropertyDeclaration::CSSWideKeyword(_, keyword) => keyword.to_css(dest),
             PropertyDeclaration::WithVariables(_, ref with_variables) => {
                 // https://drafts.csswg.org/css-variables/#variables-in-shorthands
--- a/servo/components/style/shared_lock.rs
+++ b/servo/components/style/shared_lock.rs
@@ -8,16 +8,17 @@
 use atomic_refcell::{AtomicRefCell, AtomicRef, AtomicRefMut};
 #[cfg(feature = "servo")]
 use parking_lot::RwLock;
 use servo_arc::Arc;
 use std::cell::UnsafeCell;
 use std::fmt;
 #[cfg(feature = "gecko")]
 use std::ptr;
+use str::{CssString, CssStringWriter};
 use stylesheets::Origin;
 
 /// A shared read/write lock that can protect multiple objects.
 ///
 /// In Gecko builds, we don't need the blocking behavior, just the safety. As
 /// such we implement this with an AtomicRefCell instead in Gecko builds,
 /// which is ~2x as fast, and panics (rather than deadlocking) when things go
 /// wrong (which is much easier to debug on CI).
@@ -214,28 +215,28 @@ mod compile_time_assert {
     impl<'a> Marker1 for SharedRwLockWriteGuard<'a> {}  // Assert SharedRwLockWriteGuard: !Clone
 
     trait Marker2 {}
     impl<T: Copy> Marker2 for T {}
     impl<'a> Marker2 for SharedRwLockReadGuard<'a> {}  // Assert SharedRwLockReadGuard: !Copy
     impl<'a> Marker2 for SharedRwLockWriteGuard<'a> {}  // Assert SharedRwLockWriteGuard: !Copy
 }
 
-/// Like ToCss, but with a lock guard given by the caller.
+/// Like ToCss, but with a lock guard given by the caller, and with the writer specified
+/// concretely rather than with a parameter.
 pub trait ToCssWithGuard {
     /// Serialize `self` in CSS syntax, writing to `dest`, using the given lock guard.
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write;
+    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result;
 
     /// Serialize `self` in CSS syntax using the given lock guard and return a string.
     ///
     /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
     #[inline]
-    fn to_css_string(&self, guard: &SharedRwLockReadGuard) -> String {
-        let mut s = String::new();
+    fn to_css_string(&self, guard: &SharedRwLockReadGuard) -> CssString {
+        let mut s = CssString::new();
         self.to_css(guard, &mut s).unwrap();
         s
     }
 }
 
 /// Parameters needed for deep clones.
 #[cfg(feature = "gecko")]
 pub struct DeepCloneParams {
--- a/servo/components/style/str.rs
+++ b/servo/components/style/str.rs
@@ -5,16 +5,17 @@
 //! String utils for attributes and similar stuff.
 
 #![deny(missing_docs)]
 
 use num_traits::ToPrimitive;
 #[allow(unused_imports)] use std::ascii::AsciiExt;
 use std::borrow::Cow;
 use std::convert::AsRef;
+use std::fmt::{self, Write};
 use std::iter::{Filter, Peekable};
 use std::str::Split;
 
 /// A static slice of characters.
 pub type StaticCharVec = &'static [char];
 
 /// A static slice of `str`s.
 pub type StaticStringVec = &'static [&'static str];
@@ -158,8 +159,106 @@ pub fn starts_with_ignore_ascii_case(str
 pub fn string_as_ascii_lowercase<'a>(input: &'a str) -> Cow<'a, str> {
     if input.bytes().any(|c| matches!(c, b'A'...b'Z')) {
         input.to_ascii_lowercase().into()
     } else {
         // Already ascii lowercase.
         Cow::Borrowed(input)
     }
 }
+
+/// To avoid accidentally instantiating multiple monomorphizations of large
+/// serialization routines, we define explicit concrete types and require
+/// them in those routines. This primarily avoids accidental mixing of UTF8
+/// with UTF16 serializations in Gecko.
+#[cfg(feature = "gecko")]
+pub type CssStringWriter = ::nsstring::nsAString;
+
+/// String type that coerces to CssStringWriter, used when serialization code
+/// needs to allocate a temporary string.
+#[cfg(feature = "gecko")]
+pub type CssString = ::nsstring::nsString;
+
+/// Certain serialization code needs to interact with borrowed strings, which
+/// are sometimes native UTF8 Rust strings, and other times serialized UTF16
+/// strings. This enum multiplexes the two cases.
+#[cfg(feature = "gecko")]
+pub enum CssStringBorrow<'a> {
+    /// A borrow of a UTF16 CssString.
+    UTF16(&'a ::nsstring::nsString),
+    /// A borrow of a regular Rust UTF8 string.
+    UTF8(&'a str),
+}
+
+#[cfg(feature = "gecko")]
+impl<'a> CssStringBorrow<'a> {
+    /// Writes the borrowed string to the provided writer.
+    pub fn append_to(&self, dest: &mut CssStringWriter) -> fmt::Result {
+        match *self {
+            CssStringBorrow::UTF16(s) => {
+                dest.append(s);
+                Ok(())
+            },
+            CssStringBorrow::UTF8(s) => dest.write_str(s),
+        }
+    }
+
+    /// Returns true of the borrowed string is empty.
+    pub fn is_empty(&self) -> bool {
+        match *self {
+            CssStringBorrow::UTF16(s) => s.is_empty(),
+            CssStringBorrow::UTF8(s) => s.is_empty(),
+        }
+    }
+}
+
+#[cfg(feature = "gecko")]
+impl<'a> From<&'a str> for CssStringBorrow<'a> {
+    fn from(s: &'a str) -> Self {
+        CssStringBorrow::UTF8(s)
+    }
+}
+
+#[cfg(feature = "gecko")]
+impl<'a> From<&'a ::nsstring::nsString> for CssStringBorrow<'a> {
+    fn from(s: &'a ::nsstring::nsString) -> Self {
+        CssStringBorrow::UTF16(s)
+    }
+}
+
+/// String. The comments for the Gecko types explain the need for this abstraction.
+#[cfg(feature = "servo")]
+pub type CssStringWriter = String;
+
+/// String. The comments for the Gecko types explain the need for this abstraction.
+#[cfg(feature = "servo")]
+pub type CssString = String;
+
+/// Borrowed string. The comments for the Gecko types explain the need for this abstraction.
+#[cfg(feature = "servo")]
+pub struct CssStringBorrow<'a>(&'a str);
+
+#[cfg(feature = "servo")]
+impl<'a> CssStringBorrow<'a> {
+    /// Appends the borrowed string to the given string.
+    pub fn append_to(&self, dest: &mut CssStringWriter) -> fmt::Result {
+        dest.write_str(self.0)
+    }
+
+    /// Returns true if the borrowed string is empty.
+    pub fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+}
+
+#[cfg(feature = "servo")]
+impl<'a> From<&'a str> for CssStringBorrow<'a> {
+    fn from(s: &'a str) -> Self {
+        CssStringBorrow(s)
+    }
+}
+
+#[cfg(feature = "servo")]
+impl<'a> From<&'a String> for CssStringBorrow<'a> {
+    fn from(s: &'a String) -> Self {
+        CssStringBorrow(&*s)
+    }
+}
--- a/servo/components/style/stylesheets/document_rule.rs
+++ b/servo/components/style/stylesheets/document_rule.rs
@@ -8,17 +8,18 @@
 
 use cssparser::{Parser, Token, SourceLocation};
 #[cfg(feature = "gecko")]
 use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
 use media_queries::Device;
 use parser::{Parse, ParserContext};
 use servo_arc::Arc;
 use shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
-use std::fmt;
+use std::fmt::{self, Write};
+use str::CssStringWriter;
 use style_traits::{ToCss, ParseError, StyleParseErrorKind};
 use stylesheets::CssRules;
 use values::specified::url::SpecifiedUrl;
 
 #[derive(Debug)]
 /// A @-moz-document rule
 pub struct DocumentRule {
     /// The parsed condition
@@ -35,18 +36,17 @@ impl DocumentRule {
     pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
         // Measurement of other fields may be added later.
         self.rules.unconditional_shallow_size_of(ops) +
             self.rules.read_with(guard).size_of(guard, ops)
     }
 }
 
 impl ToCssWithGuard for DocumentRule {
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
+    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
         dest.write_str("@-moz-document ")?;
         self.condition.to_css(dest)?;
         dest.write_str(" {")?;
         for rule in self.rules.read_with(guard).0.iter() {
             dest.write_str(" ")?;
             rule.to_css(guard, dest)?;
         }
         dest.write_str(" }")
--- a/servo/components/style/stylesheets/font_feature_values_rule.rs
+++ b/servo/components/style/stylesheets/font_feature_values_rule.rs
@@ -11,17 +11,18 @@ use cssparser::{AtRuleParser, AtRuleType
 use cssparser::{CowRcStr, RuleListParser, SourceLocation, QualifiedRuleParser, Token, serialize_identifier};
 use error_reporting::{ContextualParseError, ParseErrorReporter};
 #[cfg(feature = "gecko")]
 use gecko_bindings::bindings::Gecko_AppendFeatureValueHashEntry;
 #[cfg(feature = "gecko")]
 use gecko_bindings::structs::{self, gfxFontFeatureValueSet, nsTArray};
 use parser::{ParserContext, ParserErrorContext, Parse};
 use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
-use std::fmt;
+use std::fmt::{self, Write};
+use str::CssStringWriter;
 use style_traits::{ParseError, StyleParseErrorKind, ToCss};
 use stylesheets::CssRuleType;
 use values::computed::font::FamilyName;
 
 /// A @font-feature-values block declaration.
 /// It is `<ident>: <integer>+`.
 /// This struct can take 3 value types.
 /// - `SingleValue` is to keep just one unsigned integer value.
@@ -342,19 +343,17 @@ macro_rules! font_feature_values_blocks 
                             }
                         }
                     )*
                 }
             }
         }
 
         impl ToCssWithGuard for FontFeatureValuesRule {
-            fn to_css<W>(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-                where W: fmt::Write
-            {
+            fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
                 dest.write_str("@font-feature-values ")?;
                 self.font_family_to_css(dest)?;
                 dest.write_str(" {\n")?;
                 self.value_to_css(dest)?;
                 dest.write_str("}")
             }
         }
 
--- a/servo/components/style/stylesheets/import_rule.rs
+++ b/servo/components/style/stylesheets/import_rule.rs
@@ -4,17 +4,18 @@
 
 //! The [`@import`][import] at-rule.
 //!
 //! [import]: https://drafts.csswg.org/css-cascade-3/#at-import
 
 use cssparser::SourceLocation;
 use media_queries::MediaList;
 use shared_lock::{DeepCloneWithLock, DeepCloneParams, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
-use std::fmt;
+use std::fmt::{self, Write};
+use str::CssStringWriter;
 use style_traits::ToCss;
 use stylesheets::{StylesheetContents, StylesheetInDocument};
 use values::specified::url::SpecifiedUrl;
 
 /// A sheet that is held from an import rule.
 #[cfg(feature = "gecko")]
 #[derive(Debug)]
 pub struct ImportSheet(pub ::gecko::data::GeckoStyleSheet);
@@ -102,19 +103,17 @@ impl DeepCloneWithLock for ImportRule {
             url: self.url.clone(),
             stylesheet: self.stylesheet.deep_clone_with_lock(lock, guard, params),
             source_location: self.source_location.clone(),
         }
     }
 }
 
 impl ToCssWithGuard for ImportRule {
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-        where W: fmt::Write,
-    {
+    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
         dest.write_str("@import ")?;
         self.url.to_css(dest)?;
 
         match self.stylesheet.media(guard) {
             Some(media) if !media.is_empty() => {
                 dest.write_str(" ")?;
                 media.to_css(dest)?;
             }
--- a/servo/components/style/stylesheets/keyframes_rule.rs
+++ b/servo/components/style/stylesheets/keyframes_rule.rs
@@ -9,17 +9,18 @@ use cssparser::{DeclarationListParser, D
 use error_reporting::{NullReporter, ContextualParseError, ParseErrorReporter};
 use parser::{ParserContext, ParserErrorContext};
 use properties::{DeclarationSource, Importance, PropertyDeclaration, PropertyDeclarationBlock, PropertyId};
 use properties::{PropertyDeclarationId, LonghandId, SourcePropertyDeclaration};
 use properties::LonghandIdSet;
 use properties::longhands::transition_timing_function::single_value::SpecifiedValue as SpecifiedTimingFunction;
 use servo_arc::Arc;
 use shared_lock::{DeepCloneParams, DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard, Locked, ToCssWithGuard};
-use std::fmt;
+use std::fmt::{self, Write};
+use str::CssStringWriter;
 use style_traits::{ParsingMode, ToCss, ParseError, StyleParseErrorKind};
 use stylesheets::{CssRuleType, StylesheetContents};
 use stylesheets::rule_parser::VendorPrefix;
 use values::{KeyframesName, serialize_percentage};
 
 /// A [`@keyframes`][keyframes] rule.
 ///
 /// [keyframes]: https://drafts.csswg.org/css-animations/#keyframes
@@ -32,19 +33,17 @@ pub struct KeyframesRule {
     /// Vendor prefix type the @keyframes has.
     pub vendor_prefix: Option<VendorPrefix>,
     /// The line and column of the rule's source code.
     pub source_location: SourceLocation,
 }
 
 impl ToCssWithGuard for KeyframesRule {
     // Serialization of KeyframesRule is not specced.
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-        where W: fmt::Write,
-    {
+    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
         dest.write_str("@keyframes ")?;
         self.name.to_css(dest)?;
         dest.write_str(" {")?;
         let iter = self.keyframes.iter();
         for lock in iter {
             dest.write_str("\n")?;
             let keyframe = lock.read_with(&guard);
             keyframe.to_css(guard, dest)?;
@@ -189,18 +188,17 @@ pub struct Keyframe {
     /// `Arc` just for convenience.
     pub block: Arc<Locked<PropertyDeclarationBlock>>,
 
     /// The line and column of the rule's source code.
     pub source_location: SourceLocation,
 }
 
 impl ToCssWithGuard for Keyframe {
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
+    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
         self.selector.to_css(dest)?;
         dest.write_str(" { ")?;
         self.block.read_with(guard).to_css(dest)?;
         dest.write_str(" }")?;
         Ok(())
     }
 }
 
--- a/servo/components/style/stylesheets/media_rule.rs
+++ b/servo/components/style/stylesheets/media_rule.rs
@@ -7,17 +7,18 @@
 //! [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
 
 use cssparser::SourceLocation;
 #[cfg(feature = "gecko")]
 use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
 use media_queries::MediaList;
 use servo_arc::Arc;
 use shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
-use std::fmt;
+use std::fmt::{self, Write};
+use str::CssStringWriter;
 use style_traits::ToCss;
 use stylesheets::CssRules;
 
 /// An [`@media`][media] urle.
 ///
 /// [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
 #[derive(Debug)]
 pub struct MediaRule {
@@ -37,18 +38,17 @@ impl MediaRule {
         self.rules.unconditional_shallow_size_of(ops) +
             self.rules.read_with(guard).size_of(guard, ops)
     }
 }
 
 impl ToCssWithGuard for MediaRule {
     // Serialization of MediaRule is not specced.
     // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSMediaRule
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
+    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
         dest.write_str("@media ")?;
         self.media_queries.read_with(guard).to_css(dest)?;
         self.rules.read_with(guard).to_css_block(guard, dest)
     }
 }
 
 impl DeepCloneWithLock for MediaRule {
     fn deep_clone_with_lock(
--- a/servo/components/style/stylesheets/mod.rs
+++ b/servo/components/style/stylesheets/mod.rs
@@ -26,16 +26,17 @@ pub mod viewport_rule;
 use cssparser::{parse_one_rule, Parser, ParserInput};
 use error_reporting::NullReporter;
 #[cfg(feature = "gecko")]
 use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
 use parser::{ParserContext, ParserErrorContext};
 use servo_arc::Arc;
 use shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
 use std::fmt;
+use str::CssStringWriter;
 use style_traits::ParsingMode;
 
 pub use self::counter_style_rule::CounterStyleRule;
 pub use self::document_rule::DocumentRule;
 pub use self::font_face_rule::FontFaceRule;
 pub use self::font_feature_values_rule::FontFeatureValuesRule;
 pub use self::import_rule::ImportRule;
 pub use self::keyframes_rule::KeyframesRule;
@@ -342,18 +343,17 @@ impl DeepCloneWithLock for CssRule {
                     lock.wrap(rule.deep_clone_with_lock(lock, guard, params))))
             },
         }
     }
 }
 
 impl ToCssWithGuard for CssRule {
     // https://drafts.csswg.org/cssom/#serialize-a-css-rule
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
+    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
         match *self {
             CssRule::Namespace(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Import(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Style(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::FontFace(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::FontFeatureValues(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::CounterStyle(ref lock) => lock.read_with(guard).to_css(guard, dest),
             CssRule::Viewport(ref lock) => lock.read_with(guard).to_css(guard, dest),
--- a/servo/components/style/stylesheets/namespace_rule.rs
+++ b/servo/components/style/stylesheets/namespace_rule.rs
@@ -2,34 +2,34 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! The `@namespace` at-rule.
 
 use {Namespace, Prefix};
 use cssparser::SourceLocation;
 use shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
-use std::fmt;
+use std::fmt::{self, Write};
+use str::CssStringWriter;
 
 /// A `@namespace` rule.
 #[derive(Clone, Debug, PartialEq)]
 #[allow(missing_docs)]
 pub struct NamespaceRule {
     /// The namespace prefix, and `None` if it's the default Namespace
     pub prefix: Option<Prefix>,
     /// The actual namespace url.
     pub url: Namespace,
     /// The source location this rule was found at.
     pub source_location: SourceLocation,
 }
 
 impl ToCssWithGuard for NamespaceRule {
     // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSNamespaceRule
-    fn to_css<W>(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
+    fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
         dest.write_str("@namespace ")?;
         if let Some(ref prefix) = self.prefix {
             dest.write_str(&*prefix.to_string())?;
             dest.write_str(" ")?;
         }
 
         // FIXME(emilio): Pretty sure this needs some escaping, or something?
         dest.write_str("url(\"")?;
--- a/servo/components/style/stylesheets/page_rule.rs
+++ b/servo/components/style/stylesheets/page_rule.rs
@@ -7,18 +7,18 @@
 //! [page]: https://drafts.csswg.org/css2/page.html#page-box
 
 use cssparser::SourceLocation;
 #[cfg(feature = "gecko")]
 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
 use properties::PropertyDeclarationBlock;
 use servo_arc::Arc;
 use shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
-use std::fmt;
-use style_traits::ToCss;
+use std::fmt::{self, Write};
+use str::CssStringWriter;
 
 /// A [`@page`][page] rule.
 ///
 /// This implements only a limited subset of the CSS
 /// 2.2 syntax.
 ///
 /// In this subset, [page selectors][page-selectors] are not implemented.
 ///
@@ -39,19 +39,17 @@ impl PageRule {
         // Measurement of other fields may be added later.
         self.block.unconditional_shallow_size_of(ops) + self.block.read_with(guard).size_of(ops)
     }
 }
 
 impl ToCssWithGuard for PageRule {
     /// Serialization of PageRule is not specced, adapted from steps for
     /// StyleRule.
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-        where W: fmt::Write,
-    {
+    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
         dest.write_str("@page { ")?;
         let declaration_block = self.block.read_with(guard);
         declaration_block.to_css(dest)?;
         if !declaration_block.declarations().is_empty() {
             dest.write_str(" ")?;
         }
         dest.write_str("}")
     }
--- a/servo/components/style/stylesheets/rule_list.rs
+++ b/servo/components/style/stylesheets/rule_list.rs
@@ -4,17 +4,18 @@
 
 //! A list of CSS rules.
 
 #[cfg(feature = "gecko")]
 use malloc_size_of::{MallocShallowSizeOf, MallocSizeOfOps};
 use servo_arc::{Arc, RawOffsetArc};
 use shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
 use shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
-use std::fmt;
+use std::fmt::{self, Write};
+use str::CssStringWriter;
 use stylesheets::{CssRule, RulesMutateError};
 use stylesheets::loader::StylesheetLoader;
 use stylesheets::rule_parser::State;
 use stylesheets::stylesheet::StylesheetContents;
 
 /// A list of CSS rules.
 #[derive(Debug)]
 pub struct CssRules(pub Vec<CssRule>);
@@ -90,19 +91,17 @@ impl CssRules {
         self.0.remove(index);
         Ok(())
     }
 
     /// Serializes this CSSRules to CSS text as a block of rules.
     ///
     /// This should be speced into CSSOM spec at some point. See
     /// <https://github.com/w3c/csswg-drafts/issues/1985>
-    pub fn to_css_block<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W)
-        -> fmt::Result where W: fmt::Write
-    {
+    pub fn to_css_block(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
         dest.write_str(" {")?;
         for rule in self.0.iter() {
             dest.write_str("\n  ")?;
             rule.to_css(guard, dest)?;
         }
         dest.write_str("\n}")
     }
 }
--- a/servo/components/style/stylesheets/style_rule.rs
+++ b/servo/components/style/stylesheets/style_rule.rs
@@ -7,18 +7,18 @@
 use cssparser::SourceLocation;
 #[cfg(feature = "gecko")]
 use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
 use properties::PropertyDeclarationBlock;
 use selector_parser::SelectorImpl;
 use selectors::SelectorList;
 use servo_arc::Arc;
 use shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
-use std::fmt;
-use style_traits::ToCss;
+use std::fmt::{self, Write};
+use str::CssStringWriter;
 
 /// A style rule, with selectors and declarations.
 #[derive(Debug)]
 pub struct StyleRule {
     /// The list of selectors in this rule.
     pub selectors: SelectorList<SelectorImpl>,
     /// The declaration block with the properties it contains.
     pub block: Arc<Locked<PropertyDeclarationBlock>>,
@@ -62,19 +62,17 @@ impl StyleRule {
              self.block.read_with(guard).size_of(ops);
 
         n
     }
 }
 
 impl ToCssWithGuard for StyleRule {
     /// https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSStyleRule
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-        where W: fmt::Write,
-    {
+    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
         use cssparser::ToCss;
 
         // Step 1
         self.selectors.to_css(dest)?;
         // Step 2
         dest.write_str(" { ")?;
         // Step 3
         let declaration_block = self.block.read_with(guard);
--- a/servo/components/style/stylesheets/supports_rule.rs
+++ b/servo/components/style/stylesheets/supports_rule.rs
@@ -10,18 +10,19 @@ use cssparser::{ParseError as CssParseEr
 use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
 use parser::ParserContext;
 use properties::{PropertyId, PropertyDeclaration, SourcePropertyDeclaration};
 use selectors::parser::SelectorParseErrorKind;
 use servo_arc::Arc;
 use shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
 #[allow(unused_imports)] use std::ascii::AsciiExt;
 use std::ffi::{CStr, CString};
-use std::fmt;
+use std::fmt::{self, Write};
 use std::str;
+use str::CssStringWriter;
 use style_traits::{ToCss, ParseError};
 use stylesheets::{CssRuleType, CssRules};
 
 /// An [`@supports`][supports] rule.
 ///
 /// [supports]: https://drafts.csswg.org/css-conditional-3/#at-supports
 #[derive(Debug)]
 pub struct SupportsRule {
@@ -41,18 +42,17 @@ impl SupportsRule {
     pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
         // Measurement of other fields may be added later.
         self.rules.unconditional_shallow_size_of(ops) +
             self.rules.read_with(guard).size_of(guard, ops)
     }
 }
 
 impl ToCssWithGuard for SupportsRule {
-    fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
+    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
         dest.write_str("@supports ")?;
         self.condition.to_css(dest)?;
         self.rules.read_with(guard).to_css_block(guard, dest)
     }
 }
 
 impl DeepCloneWithLock for SupportsRule {
     fn deep_clone_with_lock(
--- a/servo/components/style/stylesheets/viewport_rule.rs
+++ b/servo/components/style/stylesheets/viewport_rule.rs
@@ -18,19 +18,20 @@ use media_queries::Device;
 use parser::{ParserContext, ParserErrorContext};
 use properties::StyleBuilder;
 use rule_cache::RuleCacheConditions;
 use selectors::parser::SelectorParseErrorKind;
 use shared_lock::{SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard};
 #[allow(unused_imports)] use std::ascii::AsciiExt;
 use std::borrow::Cow;
 use std::cell::RefCell;
-use std::fmt;
+use std::fmt::{self, Write};
 use std::iter::Enumerate;
 use std::str::Chars;
+use str::CssStringWriter;
 use style_traits::{PinchZoomFactor, ToCss, ParseError, StyleParseErrorKind};
 use style_traits::viewport::{Orientation, UserZoom, ViewportConstraints, Zoom};
 use stylesheets::{StylesheetInDocument, Origin};
 use values::computed::{Context, ToComputedValue};
 use values::specified::{NoCalcLength, LengthOrPercentageOrAuto, ViewportPercentageLength};
 
 /// Whether parsing and processing of `@viewport` rules is enabled.
 #[cfg(feature = "servo")]
@@ -515,18 +516,17 @@ impl ViewportRule {
         };
 
         Some((name, value))
     }
 }
 
 impl ToCssWithGuard for ViewportRule {
     // Serialization of ViewportRule is not specced.
-    fn to_css<W>(&self, _guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
-    where W: fmt::Write {
+    fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
         dest.write_str("@viewport { ")?;
         let mut iter = self.declarations.iter();
         iter.next().unwrap().to_css(dest)?;
         for declaration in iter {
             dest.write_str(" ")?;
             declaration.to_css(dest)?;
         }
         dest.write_str(" }")
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -700,24 +700,21 @@ macro_rules! get_property_id_from_nscssp
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_AnimationValue_Serialize(value: RawServoAnimationValueBorrowed,
                                                  property: nsCSSPropertyID,
                                                  buffer: *mut nsAString)
 {
     let uncomputed_value = AnimationValue::as_arc(&value).uncompute();
-    let mut string = String::new();
+    let buffer = unsafe { buffer.as_mut().unwrap() };
     let rv = PropertyDeclarationBlock::with_one(uncomputed_value, Importance::Normal)
-        .single_value_to_css(&get_property_id_from_nscsspropertyid!(property, ()), &mut string,
+        .single_value_to_css(&get_property_id_from_nscsspropertyid!(property, ()), buffer,
                              None, None /* No extra custom properties */);
     debug_assert!(rv.is_ok());
-
-    let buffer = unsafe { buffer.as_mut().unwrap() };
-    buffer.assign_utf8(&string);
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_Shorthand_AnimationValues_Serialize(shorthand_property: nsCSSPropertyID,
                                                             values: RawGeckoServoAnimationValueListBorrowed,
                                                             buffer: *mut nsAString)
 {
     let property_id = get_property_id_from_nscsspropertyid!(shorthand_property, ());
@@ -2672,26 +2669,21 @@ pub extern "C" fn Servo_DeclarationBlock
     custom_properties: RawServoDeclarationBlockBorrowedOrNull,
 ) {
     let property_id = get_property_id_from_nscsspropertyid!(property_id, ());
 
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let guard = global_style_data.shared_lock.read();
     let decls = Locked::<PropertyDeclarationBlock>::as_arc(&declarations).read_with(&guard);
 
-    let mut string = String::new();
-
     let custom_properties = Locked::<PropertyDeclarationBlock>::arc_from_borrowed(&custom_properties);
     let custom_properties = custom_properties.map(|block| block.read_with(&guard));
-    let rv = decls.single_value_to_css(
-        &property_id, &mut string, computed_values, custom_properties);
+    let buffer = unsafe { buffer.as_mut().unwrap() };
+    let rv = decls.single_value_to_css(&property_id, buffer, computed_values, custom_properties);
     debug_assert!(rv.is_ok());
-
-    let buffer = unsafe { buffer.as_mut().unwrap() };
-    buffer.assign_utf8(&string);
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_SerializeFontValueForCanvas(
     declarations: RawServoDeclarationBlockBorrowed,
     buffer: *mut nsAString,
 ) {
     use style::properties::shorthands::font;
--- a/servo/tests/unit/style/properties/serialization.rs
+++ b/servo/tests/unit/style/properties/serialization.rs
@@ -1,25 +1,38 @@
 /* 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 properties::{parse, parse_input};
 use style::computed_values::display::T as Display;
 use style::properties::{PropertyDeclaration, Importance};
+use style::properties::declaration_block::PropertyDeclarationBlock;
 use style::properties::parse_property_declaration_list;
 use style::values::{CustomIdent, RGBA};
 use style::values::generics::flex::FlexBasis;
 use style::values::specified::{BorderStyle, BorderSideWidth, Color};
 use style::values::specified::{Length, LengthOrPercentage, LengthOrPercentageOrAuto};
 use style::values::specified::NoCalcLength;
 use style::values::specified::url::SpecifiedUrl;
 use style_traits::ToCss;
 use stylesheets::block_from;
 
+trait ToCssString {
+    fn to_css_string(&self) -> String;
+}
+
+impl ToCssString for PropertyDeclarationBlock {
+    fn to_css_string(&self) -> String {
+        let mut css = String::new();
+        self.to_css(&mut css).unwrap();
+        css
+    }
+}
+
 #[test]
 fn property_declaration_block_should_serialize_correctly() {
     use style::properties::longhands::overflow_x::SpecifiedValue as OverflowValue;
 
     let declarations = vec![
         (PropertyDeclaration::Width(
             LengthOrPercentageOrAuto::Length(NoCalcLength::from_px(70f32))),
          Importance::Normal),