servo: Merge #14238 - Implement ToCss serialization for CSSRules (from canaltinova:cssom-tocss); r=Manishearth
authorNazım Can Altınova <canaltinova@gmail.com>
Fri, 18 Nov 2016 10:15:52 -0600
changeset 340173 2418cfba72c33c5623f6fb4c243c5203819c8240
parent 340172 a268aadd22882ca20d2b507216c50988021c1d92
child 340174 cbfe4c2e864d60e1ff3d9a75599038dacb9ab6c7
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersManishearth
servo: Merge #14238 - Implement ToCss serialization for CSSRules (from canaltinova:cssom-tocss); r=Manishearth <!-- Please describe your changes on the following line: --> Implementation of ToCss serialization for CSSRules. It requires #14190 to merge first to uncomment `CssRule::Style` branch in CssRule's ToCss implementation. r? @Manishearth --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #14195 (github issue number if applicable). - [X] These changes do not require tests because it's serialization changes and I'm not sure there is a test for that. <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: 731264f20e1f19687bf0d77fb364568bd1ac034e
servo/components/script/dom/cssfontfacerule.rs
servo/components/script/dom/csskeyframesrule.rs
servo/components/script/dom/cssmediarule.rs
servo/components/script/dom/cssnamespacerule.rs
servo/components/script/dom/cssviewportrule.rs
servo/components/style/font_face.rs
servo/components/style/keyframes.rs
servo/components/style/stylesheets.rs
servo/components/style/values/specified/url.rs
servo/components/style/viewport.rs
--- a/servo/components/script/dom/cssfontfacerule.rs
+++ b/servo/components/script/dom/cssfontfacerule.rs
@@ -7,16 +7,17 @@ use dom::bindings::js::Root;
 use dom::bindings::reflector::reflect_dom_object;
 use dom::bindings::str::DOMString;
 use dom::cssrule::{CSSRule, SpecificCSSRule};
 use dom::cssstylesheet::CSSStyleSheet;
 use dom::window::Window;
 use parking_lot::RwLock;
 use std::sync::Arc;
 use style::font_face::FontFaceRule;
+use style_traits::ToCss;
 
 #[dom_struct]
 pub struct CSSFontFaceRule {
     cssrule: CSSRule,
     #[ignore_heap_size_of = "Arc"]
     fontfacerule: Arc<RwLock<FontFaceRule>>,
 }
 
@@ -39,12 +40,11 @@ impl CSSFontFaceRule {
 
 impl SpecificCSSRule for CSSFontFaceRule {
     fn ty(&self) -> u16 {
         use dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants;
         CSSRuleConstants::FONT_FACE_RULE
     }
 
     fn get_css(&self) -> DOMString {
-        // self.fontfacerule.read().to_css_string().into()
-        "".into()
+        self.fontfacerule.read().to_css_string().into()
     }
 }
--- a/servo/components/script/dom/csskeyframesrule.rs
+++ b/servo/components/script/dom/csskeyframesrule.rs
@@ -7,16 +7,17 @@ use dom::bindings::js::Root;
 use dom::bindings::reflector::reflect_dom_object;
 use dom::bindings::str::DOMString;
 use dom::cssrule::{CSSRule, SpecificCSSRule};
 use dom::cssstylesheet::CSSStyleSheet;
 use dom::window::Window;
 use parking_lot::RwLock;
 use std::sync::Arc;
 use style::stylesheets::KeyframesRule;
+use style_traits::ToCss;
 
 #[dom_struct]
 pub struct CSSKeyframesRule {
     cssrule: CSSRule,
     #[ignore_heap_size_of = "Arc"]
     keyframesrule: Arc<RwLock<KeyframesRule>>,
 }
 
@@ -39,12 +40,11 @@ impl CSSKeyframesRule {
 
 impl SpecificCSSRule for CSSKeyframesRule {
     fn ty(&self) -> u16 {
         use dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants;
         CSSRuleConstants::KEYFRAMES_RULE
     }
 
     fn get_css(&self) -> DOMString {
-        // self.keyframesrule.read().to_css_string().into()
-        "".into()
+        self.keyframesrule.read().to_css_string().into()
     }
 }
--- a/servo/components/script/dom/cssmediarule.rs
+++ b/servo/components/script/dom/cssmediarule.rs
@@ -8,16 +8,17 @@ use dom::bindings::reflector::reflect_do
 use dom::bindings::str::DOMString;
 use dom::cssgroupingrule::CSSGroupingRule;
 use dom::cssrule::SpecificCSSRule;
 use dom::cssstylesheet::CSSStyleSheet;
 use dom::window::Window;
 use parking_lot::RwLock;
 use std::sync::Arc;
 use style::stylesheets::MediaRule;
+use style_traits::ToCss;
 
 #[dom_struct]
 pub struct CSSMediaRule {
     cssrule: CSSGroupingRule,
     #[ignore_heap_size_of = "Arc"]
     mediarule: Arc<RwLock<MediaRule>>,
 }
 
@@ -40,12 +41,11 @@ impl CSSMediaRule {
 
 impl SpecificCSSRule for CSSMediaRule {
     fn ty(&self) -> u16 {
         use dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants;
         CSSRuleConstants::MEDIA_RULE
     }
 
     fn get_css(&self) -> DOMString {
-        // self.mediarule.read().to_css_string().into()
-        "".into()
+        self.mediarule.read().to_css_string().into()
     }
 }
--- a/servo/components/script/dom/cssnamespacerule.rs
+++ b/servo/components/script/dom/cssnamespacerule.rs
@@ -7,16 +7,17 @@ use dom::bindings::js::Root;
 use dom::bindings::reflector::reflect_dom_object;
 use dom::bindings::str::DOMString;
 use dom::cssrule::{CSSRule, SpecificCSSRule};
 use dom::cssstylesheet::CSSStyleSheet;
 use dom::window::Window;
 use parking_lot::RwLock;
 use std::sync::Arc;
 use style::stylesheets::NamespaceRule;
+use style_traits::ToCss;
 
 #[dom_struct]
 pub struct CSSNamespaceRule {
     cssrule: CSSRule,
     #[ignore_heap_size_of = "Arc"]
     namespacerule: Arc<RwLock<NamespaceRule>>,
 }
 
@@ -39,12 +40,11 @@ impl CSSNamespaceRule {
 
 impl SpecificCSSRule for CSSNamespaceRule {
     fn ty(&self) -> u16 {
         use dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants;
         CSSRuleConstants::NAMESPACE_RULE
     }
 
     fn get_css(&self) -> DOMString {
-        // self.namespacerule.read().to_css_string().into()
-        "".into()
+        self.namespacerule.read().to_css_string().into()
     }
 }
--- a/servo/components/script/dom/cssviewportrule.rs
+++ b/servo/components/script/dom/cssviewportrule.rs
@@ -7,16 +7,17 @@ use dom::bindings::js::Root;
 use dom::bindings::reflector::reflect_dom_object;
 use dom::bindings::str::DOMString;
 use dom::cssrule::{CSSRule, SpecificCSSRule};
 use dom::cssstylesheet::CSSStyleSheet;
 use dom::window::Window;
 use parking_lot::RwLock;
 use std::sync::Arc;
 use style::viewport::ViewportRule;
+use style_traits::ToCss;
 
 #[dom_struct]
 pub struct CSSViewportRule {
     cssrule: CSSRule,
     #[ignore_heap_size_of = "Arc"]
     viewportrule: Arc<RwLock<ViewportRule>>,
 }
 
@@ -39,12 +40,11 @@ impl CSSViewportRule {
 
 impl SpecificCSSRule for CSSViewportRule {
     fn ty(&self) -> u16 {
         use dom::bindings::codegen::Bindings::CSSRuleBinding::CSSRuleConstants;
         CSSRuleConstants::VIEWPORT_RULE
     }
 
     fn get_css(&self) -> DOMString {
-        // self.viewportrule.read().to_css_string().into()
-        "".into()
+        self.viewportrule.read().to_css_string().into()
     }
 }
--- a/servo/components/style/font_face.rs
+++ b/servo/components/style/font_face.rs
@@ -5,40 +5,86 @@
 //! The [`@font-face`][ff] at-rule.
 //!
 //! [ff]: https://drafts.csswg.org/css-fonts/#at-font-face-rule
 
 use computed_values::font_family::FontFamily;
 use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser};
 use parser::{ParserContext, log_css_error};
 use properties::longhands::font_family::parse_one_family;
+use std::fmt;
 use std::iter;
+use style_traits::ToCss;
 use values::specified::url::SpecifiedUrl;
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
 pub enum Source {
     Url(UrlSource),
     Local(FontFamily),
 }
 
+impl ToCss for Source {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            Source::Url(ref url) => {
+                try!(dest.write_str("local(\""));
+                try!(url.to_css(dest));
+            },
+            Source::Local(ref family) => {
+                try!(dest.write_str("url(\""));
+                try!(family.to_css(dest));
+            },
+        }
+        dest.write_str("\")")
+    }
+}
+
 #[derive(Clone, Debug, PartialEq, Eq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf, Deserialize, Serialize))]
 pub struct UrlSource {
     pub url: SpecifiedUrl,
     pub format_hints: Vec<String>,
 }
 
+impl ToCss for UrlSource {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        dest.write_str(self.url.as_str())
+    }
+}
+
 #[derive(Debug, PartialEq, Eq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct FontFaceRule {
     pub family: FontFamily,
     pub sources: Vec<Source>,
 }
 
+impl ToCss for FontFaceRule {
+    // Serialization of FontFaceRule is not specced.
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        try!(dest.write_str("@font-face { font-family: "));
+        try!(self.family.to_css(dest));
+        try!(dest.write_str(";"));
+
+        if self.sources.len() > 0 {
+            try!(dest.write_str(" src: "));
+            let mut iter = self.sources.iter();
+            try!(iter.next().unwrap().to_css(dest));
+            for source in iter {
+                try!(dest.write_str(", "));
+                try!(source.to_css(dest));
+            }
+            try!(dest.write_str(";"));
+        }
+
+        dest.write_str(" }")
+    }
+}
+
 pub fn parse_font_face_block(context: &ParserContext, input: &mut Parser)
                              -> Result<FontFaceRule, ()> {
     let mut family = None;
     let mut src = None;
     let mut iter = DeclarationListParser::new(input, FontFaceRuleParser { context: context });
     while let Some(declaration) = iter.next() {
         match declaration {
             Err(range) => {
--- a/servo/components/style/keyframes.rs
+++ b/servo/components/style/keyframes.rs
@@ -4,17 +4,19 @@
 
 use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, RuleListParser};
 use cssparser::{DeclarationListParser, DeclarationParser};
 use parking_lot::RwLock;
 use parser::{ParserContext, log_css_error};
 use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock};
 use properties::PropertyDeclarationParseResult;
 use properties::animated_properties::TransitionProperty;
+use std::fmt;
 use std::sync::Arc;
+use style_traits::ToCss;
 
 /// A number from 1 to 100, indicating the percentage of the animation where
 /// this keyframe should run.
 #[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct KeyframePercentage(pub f32);
 
 impl ::std::cmp::Ord for KeyframePercentage {
@@ -77,16 +79,31 @@ pub struct Keyframe {
     /// `!important` is not allowed in keyframe declarations,
     /// so the second value of these tuples is always `Importance::Normal`.
     /// But including them enables `compute_style_for_animation_step` to create a `ApplicableDeclarationBlock`
     /// by cloning an `Arc<_>` (incrementing a reference count) rather than re-creating a `Vec<_>`.
     #[cfg_attr(feature = "servo", ignore_heap_size_of = "Arc")]
     pub block: Arc<RwLock<PropertyDeclarationBlock>>,
 }
 
+impl ToCss for Keyframe {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        let mut iter = self.selector.percentages().iter();
+        try!(write!(dest, "{}%", iter.next().unwrap().0));
+        for percentage in iter {
+            try!(write!(dest, ", "));
+            try!(write!(dest, "{}%", percentage.0));
+        }
+        try!(dest.write_str(" { "));
+        try!(self.block.read().to_css(dest));
+        try!(dest.write_str(" }"));
+        Ok(())
+    }
+}
+
 /// A keyframes step value. This can be a synthetised keyframes animation, that
 /// is, one autogenerated from the current computed values, or a list of
 /// declarations to apply.
 // TODO: Find a better name for this?
 #[derive(Debug, Clone)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum KeyframesStepValue {
     /// See `Keyframe::declarations`’s docs about the presence of `Importance`.
--- a/servo/components/style/stylesheets.rs
+++ b/servo/components/style/stylesheets.rs
@@ -102,36 +102,94 @@ impl CssRule {
                 let mq = media_rule.media_queries.read();
                 let rules = media_rule.rules.0.read();
                 f(&rules, Some(&mq))
             }
         }
     }
 }
 
+impl ToCss for CssRule {
+    // https://drafts.csswg.org/cssom/#serialize-a-css-rule
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            CssRule::Namespace(ref lock) => lock.read().to_css(dest),
+            CssRule::Style(ref lock) => lock.read().to_css(dest),
+            CssRule::FontFace(ref lock) => lock.read().to_css(dest),
+            CssRule::Viewport(ref lock) => lock.read().to_css(dest),
+            CssRule::Keyframes(ref lock) => lock.read().to_css(dest),
+            CssRule::Media(ref lock) => lock.read().to_css(dest),
+        }
+    }
+}
+
 #[derive(Debug, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct NamespaceRule {
     /// `None` for the default Namespace
     pub prefix: Option<Prefix>,
     pub url: Namespace,
 }
 
+impl ToCss for NamespaceRule {
+    // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSNamespaceRule
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        try!(dest.write_str("@namespace "));
+        if let Some(ref prefix) = self.prefix {
+            try!(dest.write_str(&*prefix.to_string()));
+            try!(dest.write_str(" "));
+        }
+        try!(dest.write_str("url(\""));
+        try!(dest.write_str(&*self.url.to_string()));
+        dest.write_str("\");")
+    }
+}
+
 #[derive(Debug)]
 pub struct KeyframesRule {
     pub name: Atom,
     pub keyframes: Vec<Arc<RwLock<Keyframe>>>,
 }
 
+impl ToCss for KeyframesRule {
+    // Serialization of KeyframesRule is not specced.
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        try!(dest.write_str("@keyframes "));
+        try!(dest.write_str(&*self.name.to_string()));
+        try!(dest.write_str(" { "));
+        let iter = self.keyframes.iter();
+        for lock in iter {
+            let keyframe = lock.read();
+            try!(keyframe.to_css(dest));
+        }
+        dest.write_str(" }")
+    }
+}
+
 #[derive(Debug)]
 pub struct MediaRule {
     pub media_queries: Arc<RwLock<MediaList>>,
     pub rules: CssRules,
 }
 
+impl ToCss for MediaRule {
+    // Serialization of MediaRule is not specced.
+    // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSMediaRule
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        try!(dest.write_str("@media ("));
+        try!(self.media_queries.read().to_css(dest));
+        try!(dest.write_str(") {"));
+        for rule in self.rules.0.read().iter() {
+            try!(dest.write_str(" "));
+            try!(rule.to_css(dest));
+        }
+        dest.write_str(" }")
+    }
+}
+
 #[derive(Debug)]
 pub struct StyleRule {
     pub selectors: Vec<Selector<TheSelectorImpl>>,
     pub block: Arc<RwLock<PropertyDeclarationBlock>>,
 }
 
 impl StyleRule {
     /// Serialize the group of selectors for this rule.
--- a/servo/components/style/values/specified/url.rs
+++ b/servo/components/style/values/specified/url.rs
@@ -99,16 +99,23 @@ impl SpecifiedUrl {
     pub fn extra_data(&self) -> &UrlExtraData {
         &self.extra_data
     }
 
     pub fn url(&self) -> Option<&ServoUrl> {
         self.resolved.as_ref()
     }
 
+    pub fn as_str(&self) -> &str {
+        match self.resolved {
+            Some(ref url) => url.as_str(),
+            None => "",
+        }
+    }
+
     /// Little helper for Gecko's ffi.
     pub fn as_slice_components(&self) -> (*const u8, usize) {
         match self.resolved {
             Some(ref url) => (url.as_str().as_ptr(), url.as_str().len()),
             None => (ptr::null(), 0),
         }
     }
 
--- a/servo/components/style/viewport.rs
+++ b/servo/components/style/viewport.rs
@@ -4,16 +4,17 @@
 
 //! The [`@viewport`][at] at-rule and [`meta`][meta] element.
 //!
 //! [at]: https://drafts.csswg.org/css-device-adapt/#atviewport-rule
 //! [meta]: https://drafts.csswg.org/css-device-adapt/#viewport-meta
 
 use app_units::Au;
 use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser, parse_important};
+use cssparser::ToCss as ParserToCss;
 use euclid::scale_factor::ScaleFactor;
 use euclid::size::{Size2D, TypedSize2D};
 use media_queries::Device;
 use parser::{ParserContext, log_css_error};
 use properties::ComputedValues;
 use std::ascii::AsciiExt;
 use std::borrow::Cow;
 use std::fmt;
@@ -21,42 +22,44 @@ use std::iter::Enumerate;
 use std::str::Chars;
 use style_traits::{ToCss, ViewportPx};
 use style_traits::viewport::{Orientation, UserZoom, ViewportConstraints, Zoom};
 use stylesheets::{Stylesheet, Origin};
 use values::computed::{Context, ToComputedValue};
 use values::specified::{Length, LengthOrPercentageOrAuto, ViewportPercentageLength};
 
 macro_rules! declare_viewport_descriptor {
-    ( $( $variant: ident($data: ident), )+ ) => {
-        declare_viewport_descriptor_inner!([] [ $( $variant($data), )+ ] 0);
+    ( $( $variant_name: expr => $variant: ident($data: ident), )+ ) => {
+         declare_viewport_descriptor_inner!([] [ $( $variant_name => $variant($data), )+ ] 0);
     };
 }
 
 macro_rules! declare_viewport_descriptor_inner {
     (
-        [ $( $assigned_variant: ident($assigned_data: ident) = $assigned_discriminant: expr, )* ]
+        [ $( $assigned_variant_name: expr =>
+             $assigned_variant: ident($assigned_data: ident) = $assigned_discriminant: expr, )* ]
         [
-            $next_variant: ident($next_data: ident),
-            $( $variant: ident($data: ident), )*
+            $next_variant_name: expr => $next_variant: ident($next_data: ident),
+            $( $variant_name: expr => $variant: ident($data: ident), )*
         ]
         $next_discriminant: expr
     ) => {
         declare_viewport_descriptor_inner! {
             [
-                $( $assigned_variant($assigned_data) = $assigned_discriminant, )*
-                $next_variant($next_data) = $next_discriminant,
+                $( $assigned_variant_name => $assigned_variant($assigned_data) = $assigned_discriminant, )*
+                $next_variant_name => $next_variant($next_data) = $next_discriminant,
             ]
-            [ $( $variant($data), )* ]
+            [ $( $variant_name => $variant($data), )* ]
             $next_discriminant + 1
         }
     };
 
     (
-        [ $( $assigned_variant: ident($assigned_data: ident) = $assigned_discriminant: expr, )* ]
+        [ $( $assigned_variant_name: expr =>
+             $assigned_variant: ident($assigned_data: ident) = $assigned_discriminant: expr, )* ]
         [ ]
         $number_of_variants: expr
     ) => {
         #[derive(Clone, Debug, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub enum ViewportDescriptor {
             $(
                 $assigned_variant($assigned_data),
@@ -69,32 +72,47 @@ macro_rules! declare_viewport_descriptor
             fn discriminant_value(&self) -> usize {
                 match *self {
                     $(
                         ViewportDescriptor::$assigned_variant(..) => $assigned_discriminant,
                     )*
                 }
             }
         }
+
+        impl ToCss for ViewportDescriptor {
+            fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+                match *self {
+                    $(
+                        ViewportDescriptor::$assigned_variant(val) => {
+                            try!(dest.write_str($assigned_variant_name));
+                            try!(dest.write_str(": "));
+                            try!(val.to_css(dest));
+                        },
+                    )*
+                }
+                dest.write_str(";")
+            }
+        }
     };
 }
 
 declare_viewport_descriptor! {
-    MinWidth(ViewportLength),
-    MaxWidth(ViewportLength),
+    "min-width" => MinWidth(ViewportLength),
+    "max-width" => MaxWidth(ViewportLength),
 
-    MinHeight(ViewportLength),
-    MaxHeight(ViewportLength),
+    "min-height" => MinHeight(ViewportLength),
+    "max-height" => MaxHeight(ViewportLength),
 
-    Zoom(Zoom),
-    MinZoom(Zoom),
-    MaxZoom(Zoom),
+    "zoom" => Zoom(Zoom),
+    "min-zoom" => MinZoom(Zoom),
+    "max-zoom" => MaxZoom(Zoom),
 
-    UserZoom(UserZoom),
-    Orientation(Orientation),
+    "user-zoom" => UserZoom(UserZoom),
+    "orientation" => Orientation(Orientation),
 }
 
 trait FromMeta: Sized {
     fn from_meta(value: &str) -> Option<Self>;
 }
 
 // ViewportLength is a length | percentage | auto | extend-to-zoom
 // See:
@@ -205,16 +223,26 @@ impl ViewportDescriptorDeclaration {
         ViewportDescriptorDeclaration {
             origin: origin,
             descriptor: descriptor,
             important: important
         }
     }
 }
 
+impl ToCss for ViewportDescriptorDeclaration {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        try!(self.descriptor.to_css(dest));
+        if self.important {
+            try!(dest.write_str(" !important"));
+        }
+        dest.write_str(";")
+    }
+}
+
 fn parse_shorthand(input: &mut Parser) -> Result<[ViewportLength; 2], ()> {
     let min = try!(ViewportLength::parse(input));
     match input.try(|input| ViewportLength::parse(input)) {
         Err(()) => Ok([min, min]),
         Ok(max) => Ok([min, max])
     }
 }
 
@@ -462,16 +490,30 @@ impl ViewportRule {
             Some((end, _)) => &content[start..end],
             _ => &content[start..]
         };
 
         Some((name, value))
     }
 }
 
+impl ToCss for ViewportRule {
+    // Serialization of ViewportRule is not specced.
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        try!(dest.write_str("@viewport { "));
+        let mut iter = self.declarations.iter();
+        try!(iter.next().unwrap().to_css(dest));
+        for declaration in iter {
+            try!(dest.write_str(" "));
+            try!(declaration.to_css(dest));
+        }
+        dest.write_str(" }")
+    }
+}
+
 /// Computes the cascade precedence as according to
 /// http://dev.w3.org/csswg/css-cascade/#cascade-origin
 fn cascade_precendence(origin: Origin, important: bool) -> u8 {
     match (origin, important) {
         (Origin::UserAgent, true) => 1,
         (Origin::User, true) => 2,
         (Origin::Author, true) => 3,
         (Origin::Author, false) => 4,