servo: Merge #16954 - Avoid returning / passing around a huge ParsedDeclaration type (from servo:arrayvec); r=emilio
authorSimon Sapin <simon.sapin@exyr.org>
Fri, 19 May 2017 18:37:14 -0500
changeset 359737 ffd4de6a7326a3a084f666dd0fe76c73958ddd48
parent 359736 1cd72f93f1550cb738fd280e73096911abdaddd6
child 359738 60f783ccb6b345cf0188bcbf4d38abc990fda71e
push id90501
push userarchaeopteryx@coole-files.de
push dateSat, 20 May 2017 16:55:20 +0000
treeherdermozilla-inbound@5b74bbf20e80 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #16954 - Avoid returning / passing around a huge ParsedDeclaration type (from servo:arrayvec); r=emilio This enum type used to contain the result of parsing one CSS source declaration (`name: value;`) and expanding shorthands. Enum types are as big as the biggest of their variant (plus discriminant), which was quite big because some shorthands expand to many longhand properties. This type was returned through many functions and methods, wrapped and rewrapped in `Result` with different error types. This presumably caused significant `memmove` traffic. Instead, we now allocate an `ArrayVec` on the stack and pass `&mut` references to it for various functions to push into it. This type is also very big, but we never move it. We still use an intermediate data structure because we sometimes decide after shorthand expansion that a declaration is invalid after all and that we’re gonna drop it. Only later do we push to a `PropertyDeclarationBlock`, with an entire `ArrayVec` or nothing. In future work we can try to avoid a large stack-allocated array, and instead writing directly to the heap allocation of the `Vec` inside `PropertyDeclarationBlock`. However this is tricky: we need to preserve this "all or nothing" aspect of parsing one source declaration, and at the same time we want to make it as little error-prone as possible for the various call sites. `PropertyDeclarationBlock` curently does property deduplication incrementally: as each `PropertyDeclaration` is pushed, we check if an existing declaration of the same property exists and if so overwrite it. To get rid of the stack allocated array we’d need to somehow deduplicate separately after pushing multiple `PropertyDeclaration`. Source-Repo: https://github.com/servo/servo Source-Revision: 60682cf81fe19a82c73dd98ba4c1eebc1dbbfcac
servo/Cargo.lock
servo/components/script/dom/cssstyledeclaration.rs
servo/components/style/Cargo.toml
servo/components/style/keyframes.rs
servo/components/style/lib.rs
servo/components/style/properties/declaration_block.rs
servo/components/style/properties/helpers.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/properties/shorthand/background.mako.rs
servo/components/style/properties/shorthand/border.mako.rs
servo/components/style/properties/shorthand/box.mako.rs
servo/components/style/properties/shorthand/column.mako.rs
servo/components/style/properties/shorthand/font.mako.rs
servo/components/style/properties/shorthand/inherited_svg.mako.rs
servo/components/style/properties/shorthand/inherited_text.mako.rs
servo/components/style/properties/shorthand/list.mako.rs
servo/components/style/properties/shorthand/mask.mako.rs
servo/components/style/properties/shorthand/outline.mako.rs
servo/components/style/properties/shorthand/position.mako.rs
servo/components/style/properties/shorthand/text.mako.rs
servo/components/style/supports.rs
servo/ports/geckolib/glue.rs
servo/python/servo/testing_commands.py
servo/tests/unit/style/parsing/border.rs
servo/tests/unit/style/size_of.rs
servo/tests/unit/stylo/size_of.rs
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -2819,16 +2819,17 @@ name = "strsim"
 version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "style"
 version = "0.0.1"
 dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "arrayvec 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bindgen 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.13.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
--- a/servo/components/script/dom/cssstyledeclaration.rs
+++ b/servo/components/script/dom/cssstyledeclaration.rs
@@ -14,17 +14,17 @@ use dom::element::Element;
 use dom::node::{Node, window_from_node, document_from_node};
 use dom::window::Window;
 use dom_struct::dom_struct;
 use servo_url::ServoUrl;
 use std::ascii::AsciiExt;
 use style::attr::AttrValue;
 use style::parser::PARSING_MODE_DEFAULT;
 use style::properties::{Importance, PropertyDeclarationBlock, PropertyId, LonghandId, ShorthandId};
-use style::properties::{parse_one_declaration, parse_style_attribute};
+use style::properties::{parse_one_declaration_into, parse_style_attribute, SourcePropertyDeclaration};
 use style::selector_parser::PseudoElement;
 use style::shared_lock::Locked;
 use style::stylearc::Arc;
 use style_traits::ToCss;
 
 // http://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface
 #[dom_struct]
 pub struct CSSStyleDeclaration {
@@ -234,52 +234,51 @@ impl CSSStyleDeclaration {
     fn set_property(&self, id: PropertyId, value: DOMString, priority: DOMString) -> ErrorResult {
         // Step 1
         if self.readonly {
             return Err(Error::NoModificationAllowed);
         }
 
         self.owner.mutate_associated_block(|ref mut pdb, mut changed| {
             if value.is_empty() {
-                // Step 4
+                // Step 3
                 *changed = pdb.remove_property(&id);
                 return Ok(());
             }
 
-            // Step 5
+            // Step 4
             let importance = match &*priority {
                 "" => Importance::Normal,
                 p if p.eq_ignore_ascii_case("important") => Importance::Important,
                 _ => {
                     *changed = false;
                     return Ok(());
                 }
             };
 
-            // Step 6
+            // Step 5
             let window = self.owner.window();
             let quirks_mode = window.Document().quirks_mode();
-            let result =
-                parse_one_declaration(id, &value, &self.owner.base_url(),
-                                      window.css_error_reporter(),
-                                      PARSING_MODE_DEFAULT,
-                                      quirks_mode);
+            let mut declarations = SourcePropertyDeclaration::new();
+            let result = parse_one_declaration_into(
+                &mut declarations, id, &value, &self.owner.base_url(),
+                window.css_error_reporter(), PARSING_MODE_DEFAULT, quirks_mode);
 
-            // Step 7
-            let parsed = match result {
-                Ok(parsed) => parsed,
+            // Step 6
+            match result {
+                Ok(()) => {},
                 Err(_) => {
                     *changed = false;
                     return Ok(());
                 }
-            };
+            }
 
+            // Step 7
             // Step 8
-            // Step 9
-            *changed = parsed.expand_set_into(pdb, importance);
+            *changed = pdb.extend_reset(declarations.drain(), importance);
 
             Ok(())
         })
     }
 }
 
 impl CSSStyleDeclarationMethods for CSSStyleDeclaration {
     // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-length
--- a/servo/components/style/Cargo.toml
+++ b/servo/components/style/Cargo.toml
@@ -16,22 +16,27 @@ path = "lib.rs"
 doctest = false
 
 [features]
 gecko = ["nsstring_vendor", "rayon/unstable", "num_cpus", "style_traits/gecko"]
 use_bindgen = ["bindgen", "regex", "toml"]
 servo = ["serde", "serde_derive", "heapsize", "heapsize_derive",
          "style_traits/servo", "servo_atoms", "servo_config", "html5ever",
          "cssparser/heapsize", "cssparser/serde", "encoding", "smallvec/heapsizeof",
+
+         # FIXME: Uncomment when https://github.com/servo/servo/pull/16953 has landed:
+         #"arrayvec/use_union"
+
          "rayon/unstable", "servo_url"]
 testing = []
 gecko_debug = ["nsstring_vendor/gecko_debug"]
 
 [dependencies]
 app_units = "0.4"
+arrayvec = "0.3.20"
 atomic_refcell = "0.1"
 bitflags = "0.7"
 bit-vec = "0.4.3"
 byteorder = "1.0"
 cfg-if = "0.1.0"
 cssparser = "0.13.5"
 encoding = {version = "0.2", optional = true}
 euclid = "0.11"
--- a/servo/components/style/keyframes.rs
+++ b/servo/components/style/keyframes.rs
@@ -6,17 +6,17 @@
 
 #![deny(missing_docs)]
 
 use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, RuleListParser};
 use cssparser::{DeclarationListParser, DeclarationParser, parse_one_rule};
 use error_reporting::NullReporter;
 use parser::{PARSING_MODE_DEFAULT, ParserContext, log_css_error};
 use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock, PropertyId};
-use properties::{PropertyDeclarationId, LonghandId, ParsedDeclaration};
+use properties::{PropertyDeclarationId, LonghandId, SourcePropertyDeclaration};
 use properties::LonghandIdSet;
 use properties::animated_properties::TransitionProperty;
 use properties::longhands::transition_timing_function::single_value::SpecifiedValue as SpecifiedTimingFunction;
 use shared_lock::{SharedRwLock, SharedRwLockReadGuard, Locked, ToCssWithGuard};
 use std::fmt;
 use style_traits::ToCss;
 use stylearc::Arc;
 use stylesheets::{CssRuleType, Stylesheet, VendorPrefix};
@@ -137,19 +137,21 @@ impl Keyframe {
         let context = ParserContext::new(parent_stylesheet.origin,
                                          &parent_stylesheet.url_data,
                                          &error_reporter,
                                          Some(CssRuleType::Keyframe),
                                          PARSING_MODE_DEFAULT,
                                          parent_stylesheet.quirks_mode);
         let mut input = Parser::new(css);
 
+        let mut declarations = SourcePropertyDeclaration::new();
         let mut rule_parser = KeyframeListParser {
             context: &context,
             shared_lock: &parent_stylesheet.shared_lock,
+            declarations: &mut declarations,
         };
         parse_one_rule(&mut input, &mut rule_parser)
     }
 }
 
 /// 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.
@@ -340,24 +342,27 @@ impl KeyframesAnimation {
 /// }
 ///
 /// 40%, 60%, 100% {
 ///     width: 100%;
 /// }
 struct KeyframeListParser<'a> {
     context: &'a ParserContext<'a>,
     shared_lock: &'a SharedRwLock,
+    declarations: &'a mut SourcePropertyDeclaration,
 }
 
 /// Parses a keyframe list from CSS input.
 pub fn parse_keyframe_list(context: &ParserContext, input: &mut Parser, shared_lock: &SharedRwLock)
                            -> Vec<Arc<Locked<Keyframe>>> {
+    let mut declarations = SourcePropertyDeclaration::new();
     RuleListParser::new_for_nested_rule(input, KeyframeListParser {
         context: context,
         shared_lock: shared_lock,
+        declarations: &mut declarations,
     }).filter_map(Result::ok).collect()
 }
 
 enum Void {}
 impl<'a> AtRuleParser for KeyframeListParser<'a> {
     type Prelude = Void;
     type AtRule = Arc<Locked<Keyframe>>;
 }
@@ -378,23 +383,27 @@ impl<'a> QualifiedRuleParser for Keyfram
         }
     }
 
     fn parse_block(&mut self, prelude: Self::Prelude, input: &mut Parser)
                    -> Result<Self::QualifiedRule, ()> {
         let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Keyframe));
         let parser = KeyframeDeclarationParser {
             context: &context,
+            declarations: self.declarations,
         };
         let mut iter = DeclarationListParser::new(input, parser);
         let mut block = PropertyDeclarationBlock::new();
         while let Some(declaration) = iter.next() {
             match declaration {
-                Ok(parsed) => parsed.expand_push_into(&mut block, Importance::Normal),
+                Ok(()) => {
+                    block.extend(iter.parser.declarations.drain(), Importance::Normal);
+                }
                 Err(range) => {
+                    iter.parser.declarations.clear();
                     let pos = range.start;
                     let message = format!("Unsupported keyframe property declaration: '{}'",
                                           iter.input.slice(range));
                     log_css_error(iter.input, pos, &*message, &context);
                 }
             }
             // `parse_important` is not called here, `!important` is not allowed in keyframe blocks.
         }
@@ -402,34 +411,31 @@ impl<'a> QualifiedRuleParser for Keyfram
             selector: prelude,
             block: Arc::new(self.shared_lock.wrap(block)),
         })))
     }
 }
 
 struct KeyframeDeclarationParser<'a, 'b: 'a> {
     context: &'a ParserContext<'b>,
+    declarations: &'a mut SourcePropertyDeclaration,
 }
 
 /// Default methods reject all at rules.
 impl<'a, 'b> AtRuleParser for KeyframeDeclarationParser<'a, 'b> {
     type Prelude = ();
-    type AtRule = ParsedDeclaration;
+    type AtRule = ();
 }
 
 impl<'a, 'b> DeclarationParser for KeyframeDeclarationParser<'a, 'b> {
-    type Declaration = ParsedDeclaration;
+    type Declaration = ();
 
-    fn parse_value(&mut self, name: &str, input: &mut Parser) -> Result<ParsedDeclaration, ()> {
+    fn parse_value(&mut self, name: &str, input: &mut Parser) -> Result<(), ()> {
         let id = try!(PropertyId::parse(name.into()));
-        match ParsedDeclaration::parse(id, self.context, input) {
-            Ok(parsed) => {
+        match PropertyDeclaration::parse_into(self.declarations, id, self.context, input) {
+            Ok(()) => {
                 // In case there is still unparsed text in the declaration, we should roll back.
-                if !input.is_exhausted() {
-                    Err(())
-                } else {
-                    Ok(parsed)
-                }
+                input.expect_exhausted()
             }
             Err(_) => Err(())
         }
     }
 }
--- a/servo/components/style/lib.rs
+++ b/servo/components/style/lib.rs
@@ -33,16 +33,17 @@
 //
 // [1] https://github.com/rust-lang/rust/issues/15701#issuecomment-251900615
 //#![deny(unsafe_code)]
 #![allow(unused_unsafe)]
 
 #![recursion_limit = "500"]  // For define_css_keyword_enum! in -moz-appearance
 
 extern crate app_units;
+extern crate arrayvec;
 extern crate atomic_refcell;
 extern crate bit_vec;
 #[macro_use]
 extern crate bitflags;
 #[allow(unused_extern_crates)] extern crate byteorder;
 #[cfg(feature = "gecko")] #[macro_use] #[no_link] extern crate cfg_if;
 #[macro_use] extern crate cssparser;
 extern crate euclid;
--- a/servo/components/style/properties/declaration_block.rs
+++ b/servo/components/style/properties/declaration_block.rs
@@ -197,24 +197,72 @@ impl PropertyDeclarationBlock {
             Err(longhand_or_custom) => {
                 // Step 3
                 self.get(longhand_or_custom).map_or(Importance::Normal, |&(_, importance)| importance)
             }
         }
     }
 
     /// Adds or overrides the declaration for a given property in this block,
-    /// except if an existing declaration for the same property is more important.
+    /// **except** if an existing declaration for the same property is more important.
+    pub fn extend(&mut self, drain: SourcePropertyDeclarationDrain, importance: Importance) {
+        self.extend_common(drain, importance, false);
+    }
+
+    /// Adds or overrides the declaration for a given property in this block,
+    /// **even** if an existing declaration for the same property is more important.
+    ///
+    /// Return whether anything changed.
+    pub fn extend_reset(&mut self, drain: SourcePropertyDeclarationDrain,
+                        importance: Importance) -> bool {
+        self.extend_common(drain, importance, true)
+    }
+
+    fn extend_common(&mut self, mut drain: SourcePropertyDeclarationDrain,
+                     importance: Importance, overwrite_more_important: bool) -> bool {
+        let all_shorthand_len = match drain.all_shorthand {
+            AllShorthand::NotSet => 0,
+            AllShorthand::CSSWideKeyword(_) |
+            AllShorthand::WithVariables(_) => ShorthandId::All.longhands().len()
+        };
+        let push_calls_count = drain.declarations.len() + all_shorthand_len;
+
+        // With deduplication the actual length increase may be less than this.
+        self.declarations.reserve(push_calls_count);
+
+        let mut changed = false;
+        for decl in &mut drain.declarations {
+            changed |= self.push_common(decl, importance, overwrite_more_important);
+        }
+        match drain.all_shorthand {
+            AllShorthand::NotSet => {}
+            AllShorthand::CSSWideKeyword(keyword) => {
+                for &id in ShorthandId::All.longhands() {
+                    let decl = PropertyDeclaration::CSSWideKeyword(id, keyword);
+                    changed |= self.push_common(decl, importance, overwrite_more_important);
+                }
+            }
+            AllShorthand::WithVariables(unparsed) => {
+                for &id in ShorthandId::All.longhands() {
+                    let decl = PropertyDeclaration::WithVariables(id, unparsed.clone());
+                    changed |= self.push_common(decl, importance, overwrite_more_important);
+                }
+            }
+        }
+        changed
+    }
+
+    /// Adds or overrides the declaration for a given property in this block,
+    /// **except** if an existing declaration for the same property is more important.
     pub fn push(&mut self, declaration: PropertyDeclaration, importance: Importance) {
         self.push_common(declaration, importance, false);
     }
 
-    /// Implementation detail of push and ParsedDeclaration::expand*
-    pub fn push_common(&mut self, declaration: PropertyDeclaration, importance: Importance,
-                       overwrite_more_important: bool) -> bool {
+    fn push_common(&mut self, declaration: PropertyDeclaration, importance: Importance,
+                   overwrite_more_important: bool) -> bool {
         let definitely_new = if let PropertyDeclarationId::Longhand(id) = declaration.id() {
             !self.longhands.contains(id)
         } else {
             false  // For custom properties, always scan
         };
 
         if !definitely_new {
             for slot in &mut *self.declarations {
@@ -652,87 +700,89 @@ pub fn parse_style_attribute(input: &str
                                      PARSING_MODE_DEFAULT,
                                      quirks_mode);
     parse_property_declaration_list(&context, &mut Parser::new(input))
 }
 
 /// Parse a given property declaration. Can result in multiple
 /// `PropertyDeclaration`s when expanding a shorthand, for example.
 ///
-/// The vector returned will not have the importance set;
-/// this does not attempt to parse !important at all
-pub fn parse_one_declaration(id: PropertyId,
-                             input: &str,
-                             url_data: &UrlExtraData,
-                             error_reporter: &ParseErrorReporter,
-                             parsing_mode: ParsingMode,
-                             quirks_mode: QuirksMode)
-                             -> Result<ParsedDeclaration, ()> {
+/// This does not attempt to parse !important at all.
+pub fn parse_one_declaration_into(declarations: &mut SourcePropertyDeclaration,
+                                  id: PropertyId,
+                                  input: &str,
+                                  url_data: &UrlExtraData,
+                                  error_reporter: &ParseErrorReporter,
+                                  parsing_mode: ParsingMode,
+                                  quirks_mode: QuirksMode)
+                                  -> Result<(), ()> {
     let context = ParserContext::new(Origin::Author,
                                      url_data,
                                      error_reporter,
                                      Some(CssRuleType::Style),
                                      parsing_mode,
                                      quirks_mode);
     Parser::new(input).parse_entirely(|parser| {
-        ParsedDeclaration::parse(id, &context, parser)
+        PropertyDeclaration::parse_into(declarations, id, &context, parser)
             .map_err(|_| ())
     })
 }
 
 /// A struct to parse property declarations.
 struct PropertyDeclarationParser<'a, 'b: 'a> {
     context: &'a ParserContext<'b>,
+    declarations: &'a mut SourcePropertyDeclaration,
 }
 
 
 /// Default methods reject all at rules.
 impl<'a, 'b> AtRuleParser for PropertyDeclarationParser<'a, 'b> {
     type Prelude = ();
-    type AtRule = (ParsedDeclaration, Importance);
+    type AtRule = Importance;
 }
 
-
 impl<'a, 'b> DeclarationParser for PropertyDeclarationParser<'a, 'b> {
-    type Declaration = (ParsedDeclaration, Importance);
+    type Declaration = Importance;
 
-    fn parse_value(&mut self, name: &str, input: &mut Parser)
-                   -> Result<(ParsedDeclaration, Importance), ()> {
+    fn parse_value(&mut self, name: &str, input: &mut Parser) -> Result<Importance, ()> {
         let id = try!(PropertyId::parse(name.into()));
-        let parsed = input.parse_until_before(Delimiter::Bang, |input| {
-            ParsedDeclaration::parse(id, self.context, input)
+        input.parse_until_before(Delimiter::Bang, |input| {
+            PropertyDeclaration::parse_into(self.declarations, id, self.context, input)
                 .map_err(|_| ())
         })?;
         let importance = match input.try(parse_important) {
             Ok(()) => Importance::Important,
             Err(()) => Importance::Normal,
         };
         // In case there is still unparsed text in the declaration, we should roll back.
-        if !input.is_exhausted() {
-            return Err(())
-        }
-        Ok((parsed, importance))
+        input.expect_exhausted()?;
+        Ok(importance)
     }
 }
 
 
 /// Parse a list of property declarations and return a property declaration
 /// block.
 pub fn parse_property_declaration_list(context: &ParserContext,
                                        input: &mut Parser)
                                        -> PropertyDeclarationBlock {
+    let mut declarations = SourcePropertyDeclaration::new();
     let mut block = PropertyDeclarationBlock::new();
     let parser = PropertyDeclarationParser {
         context: context,
+        declarations: &mut declarations,
     };
     let mut iter = DeclarationListParser::new(input, parser);
     while let Some(declaration) = iter.next() {
         match declaration {
-            Ok((parsed, importance)) => parsed.expand_push_into(&mut block, importance),
+            Ok(importance) => {
+                block.extend(iter.parser.declarations.drain(), importance);
+            }
             Err(range) => {
+                iter.parser.declarations.clear();
                 let pos = range.start;
                 let message = format!("Unsupported property declaration: '{}'",
                                       iter.input.slice(range));
                 log_css_error(iter.input, pos, &*message, &context);
             }
         }
     }
     block
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -743,25 +743,33 @@
                                        **kwargs)
 %>
     % if shorthand:
     /// ${shorthand.spec}
     pub mod ${shorthand.ident} {
         #[allow(unused_imports)]
         use cssparser::Parser;
         use parser::ParserContext;
-        use properties::{PropertyDeclaration, ParsedDeclaration};
-        use properties::{ShorthandId, UnparsedValue, longhands};
+        use properties::{PropertyDeclaration, SourcePropertyDeclaration, MaybeBoxed};
+        use properties::{ShorthandId, LonghandId, UnparsedValue, longhands};
         use std::fmt;
         use stylearc::Arc;
         use style_traits::ToCss;
 
         pub struct Longhands {
             % for sub_property in shorthand.sub_properties:
-                pub ${sub_property.ident}: longhands::${sub_property.ident}::SpecifiedValue,
+                pub ${sub_property.ident}:
+                    % if sub_property.boxed:
+                        Box<
+                    % endif
+                    longhands::${sub_property.ident}::SpecifiedValue
+                    % if sub_property.boxed:
+                        >
+                    % endif
+                    ,
             % endfor
         }
 
         /// Represents a serializable set of all of the longhand properties that
         /// correspond to a shorthand.
         pub struct LonghandsToSerialize<'a> {
             % for sub_property in shorthand.sub_properties:
                 pub ${sub_property.ident}:
@@ -811,36 +819,49 @@
                     }),
                     _ => Err(())
                 }
             }
         }
 
         /// Parse the given shorthand and fill the result into the
         /// `declarations` vector.
-        pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<ParsedDeclaration, ()> {
+        pub fn parse_into(declarations: &mut SourcePropertyDeclaration,
+                     context: &ParserContext, input: &mut Parser) -> Result<(), ()> {
             input.look_for_var_functions();
             let start = input.position();
             let value = input.parse_entirely(|input| parse_value(context, input));
             if value.is_err() {
                 while let Ok(_) = input.next() {}  // Look for var() after the error.
             }
             let var = input.seen_var_functions();
             if let Ok(value) = value {
-                Ok(ParsedDeclaration::${shorthand.camel_case}(value))
+                % for sub_property in shorthand.sub_properties:
+                    declarations.push(PropertyDeclaration::${sub_property.camel_case}(
+                        value.${sub_property.ident}
+                    ));
+                % endfor
+                Ok(())
             } else if var {
                 input.reset(start);
                 let (first_token_type, css) = try!(
                     ::custom_properties::parse_non_custom_with_var(input));
-                Ok(ParsedDeclaration::${shorthand.camel_case}WithVariables(Arc::new(UnparsedValue {
+                let unparsed = Arc::new(UnparsedValue {
                     css: css.into_owned(),
                     first_token_type: first_token_type,
                     url_data: context.url_data.clone(),
                     from_shorthand: Some(ShorthandId::${shorthand.camel_case}),
-                })))
+                });
+                % for sub_property in shorthand.sub_properties:
+                    declarations.push(PropertyDeclaration::WithVariables(
+                        LonghandId::${sub_property.camel_case},
+                        unparsed.clone()
+                    ));
+                % endfor
+                Ok(())
             } else {
                 Err(())
             }
         }
 
         ${caller.body()}
     }
     % endif
@@ -860,17 +881,17 @@
             % if allow_quirks:
                 try!(parse_four_sides(input, |i| ${parser_function}_quirky(context, i, specified::AllowQuirks::Yes)));
             % elif needs_context:
                 try!(parse_four_sides(input, |i| ${parser_function}(context, i)));
             % else:
                 try!(parse_four_sides(input, ${parser_function}));
                 let _unused = context;
             % endif
-            Ok(Longhands {
+            Ok(expanded! {
                 % for side in ["top", "right", "bottom", "left"]:
                     ${to_rust_ident(sub_property_pattern % side)}: ${side},
                 % endfor
             })
         }
 
         impl<'a> ToCss for LonghandsToSerialize<'a> {
             fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -8,16 +8,17 @@
 // For example, Vec<&Foo> will be mangled as Vec&Foo>. To work around these issues, the code
 // can be escaped. In the above example, Vec<<&Foo> or Vec< &Foo> achieves the desired result of Vec<&Foo>.
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 use std::borrow::Cow;
 use std::collections::HashSet;
 use std::fmt;
+use std::mem;
 use std::ops::Deref;
 use stylearc::{Arc, UniqueArc};
 
 use app_units::Au;
 #[cfg(feature = "servo")] use cssparser::{Color as CSSParserColor, RGBA};
 use cssparser::{Parser, TokenSerializationType, serialize_identifier};
 use error_reporting::ParseErrorReporter;
 #[cfg(feature = "servo")] use euclid::side_offsets::SideOffsets2D;
@@ -53,16 +54,45 @@ macro_rules! property_name {
 <%!
     from data import Method, Keyword, to_rust_ident, to_camel_case
     import os.path
 %>
 
 #[path="${repr(os.path.join(os.path.dirname(__file__), 'declaration_block.rs'))[1:-1]}"]
 pub mod declaration_block;
 
+/// Conversion with fewer impls than From/Into
+pub trait MaybeBoxed<Out> {
+    /// Convert
+    fn maybe_boxed(self) -> Out;
+}
+
+impl<T> MaybeBoxed<T> for T {
+    #[inline]
+    fn maybe_boxed(self) -> T { self }
+}
+
+impl<T> MaybeBoxed<Box<T>> for T {
+    #[inline]
+    fn maybe_boxed(self) -> Box<T> { Box::new(self) }
+}
+
+macro_rules! expanded {
+    ( $( $name: ident: $value: expr ),+ ) => {
+        expanded!( $( $name: $value, )+ )
+    };
+    ( $( $name: ident: $value: expr, )+ ) => {
+        Longhands {
+            $(
+                $name: MaybeBoxed::maybe_boxed($value),
+            )+
+        }
+    }
+}
+
 /// A module with all the code for longhand properties.
 #[allow(missing_docs)]
 pub mod longhands {
     use cssparser::Parser;
     use parser::{Parse, ParserContext};
     use values::specified;
 
     <%include file="/longhand/background.mako.rs" />
@@ -172,41 +202,43 @@ pub mod shorthands {
     // We don't defined the 'all' shorthand using the regular helpers:shorthand
     // mechanism, since it causes some very large types to be generated.
     <% data.declare_shorthand("all",
                               [p.name for p in data.longhands if p.name not in ['direction', 'unicode-bidi']],
                               spec="https://drafts.csswg.org/css-cascade-3/#all-shorthand") %>
     pub mod all {
         use cssparser::Parser;
         use parser::ParserContext;
-        use properties::{ParsedDeclaration, ShorthandId, UnparsedValue};
+        use properties::{SourcePropertyDeclaration, AllShorthand, ShorthandId, UnparsedValue};
         use stylearc::Arc;
 
-        pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<ParsedDeclaration, ()> {
+        pub fn parse_into(declarations: &mut SourcePropertyDeclaration,
+                          context: &ParserContext, input: &mut Parser) -> Result<(), ()> {
             // This function is like the parse() that is generated by
             // helpers:shorthand, but since the only values for the 'all'
             // shorthand when not just a single CSS-wide keyword is one
             // with variable references, we can make this function a
             // little simpler.
             //
             // FIXME(heycam) Try to share code with the helpers:shorthand
             // definition.
             input.look_for_var_functions();
             let start = input.position();
             while let Ok(_) = input.next() {}  // Look for var()
             if input.seen_var_functions() {
                 input.reset(start);
                 let (first_token_type, css) = try!(
                     ::custom_properties::parse_non_custom_with_var(input));
-                Ok(ParsedDeclaration::AllWithVariables(Arc::new(UnparsedValue {
+                declarations.all_shorthand = AllShorthand::WithVariables(Arc::new(UnparsedValue {
                     css: css.into_owned(),
                     first_token_type: first_token_type,
                     url_data: context.url_data.clone(),
                     from_shorthand: Some(ShorthandId::All),
-                })))
+                }));
+                Ok(())
             } else {
                 Err(())
             }
         }
     }
 }
 
 /// A module with all the code related to animated properties.
@@ -400,21 +432,17 @@ impl PropertyDeclarationIdSet {
                                 // keyword, after variable substitution.
                                 Err(())
                             }
                             % for shorthand in data.shorthands_except_all():
                                 % if property in shorthand.sub_properties:
                                     Some(ShorthandId::${shorthand.camel_case}) => {
                                         shorthands::${shorthand.ident}::parse_value(&context, input)
                                         .map(|result| {
-                                            % if property.boxed:
-                                                DeclaredValueOwned::Value(Box::new(result.${property.ident}))
-                                            % else:
-                                                DeclaredValueOwned::Value(result.${property.ident})
-                                            % endif
+                                            DeclaredValueOwned::Value(result.${property.ident})
                                         })
                                     }
                                 % endif
                             % endfor
                             _ => unreachable!()
                         }
                     })
                 })
@@ -971,230 +999,16 @@ impl PropertyId {
                 let mut s = String::new();
                 write!(&mut s, "--{}", name).unwrap();
                 s.into()
             }
         }
     }
 }
 
-/// Includes shorthands before expansion
-pub enum ParsedDeclaration {
-    % for shorthand in data.shorthands:
-        % if shorthand.name == "all":
-        // No need for an All(shorthands::all::Longhands) case, since we can
-        // never have any values for 'all' other than the CSS-wide keywords
-        // and values with variable references.
-        % else:
-        /// ${shorthand.name}
-        ${shorthand.camel_case}(shorthands::${shorthand.ident}::Longhands),
-        % endif
-
-        /// ${shorthand.name} with a CSS-wide keyword
-        ${shorthand.camel_case}CSSWideKeyword(CSSWideKeyword),
-
-        /// ${shorthand.name} with var() functions
-        ${shorthand.camel_case}WithVariables(Arc<UnparsedValue>),
-    % endfor
-
-    /// Not a shorthand
-    LonghandOrCustom(PropertyDeclaration),
-}
-
-impl ParsedDeclaration {
-    /// Transform this ParsedDeclaration into a sequence of PropertyDeclaration
-    /// by expanding shorthand declarations into their corresponding longhands
-    ///
-    /// Adds or overrides exsting declarations in the given block,
-    /// except if existing declarations are more important.
-    #[inline]
-    pub fn expand_push_into(self, block: &mut PropertyDeclarationBlock,
-                            importance: Importance) {
-        self.expand_into(block, importance, false);
-    }
-
-    /// Transform this ParsedDeclaration into a sequence of PropertyDeclaration
-    /// by expanding shorthand declarations into their corresponding longhands
-    ///
-    /// Add or override existing declarations in the given block.
-    /// Return whether anything changed.
-    #[inline]
-    pub fn expand_set_into(self, block: &mut PropertyDeclarationBlock,
-                           importance: Importance) -> bool {
-        self.expand_into(block, importance, true)
-    }
-
-    fn expand_into(self, block: &mut PropertyDeclarationBlock,
-                   importance: Importance,
-                   overwrite_more_important: bool) -> bool {
-        match self {
-            % for shorthand in data.shorthands:
-                % if shorthand.name != "all":
-                ParsedDeclaration::${shorthand.camel_case}(
-                    shorthands::${shorthand.ident}::Longhands {
-                        % for sub_property in shorthand.sub_properties:
-                            ${sub_property.ident},
-                        % endfor
-                    }
-                ) => {
-                    let mut changed = false;
-                    % for sub_property in shorthand.sub_properties:
-                        changed |= block.push_common(
-                            PropertyDeclaration::${sub_property.camel_case}(
-                                % if sub_property.boxed:
-                                    Box::new(${sub_property.ident})
-                                % else:
-                                    ${sub_property.ident}
-                                % endif
-                            ),
-                            importance,
-                            overwrite_more_important,
-                        );
-                    % endfor
-                    changed
-                },
-                % endif
-                ParsedDeclaration::${shorthand.camel_case}CSSWideKeyword(keyword) => {
-                    let mut changed = false;
-                    % for sub_property in shorthand.sub_properties:
-                        changed |= block.push_common(
-                            PropertyDeclaration::CSSWideKeyword(
-                                LonghandId::${sub_property.camel_case},
-                                keyword,
-                            ),
-                            importance,
-                            overwrite_more_important,
-                        );
-                    % endfor
-                    changed
-                },
-                ParsedDeclaration::${shorthand.camel_case}WithVariables(value) => {
-                    debug_assert_eq!(
-                        value.from_shorthand,
-                        Some(ShorthandId::${shorthand.camel_case})
-                    );
-                    let mut changed = false;
-                    % for sub_property in shorthand.sub_properties:
-                        changed |= block.push_common(
-                            PropertyDeclaration::WithVariables(
-                                LonghandId::${sub_property.camel_case},
-                                value.clone()
-                            ),
-                            importance,
-                            overwrite_more_important,
-                        );
-                    % endfor
-                    changed
-                }
-            % endfor
-            ParsedDeclaration::LonghandOrCustom(declaration) => {
-                block.push_common(declaration, importance, overwrite_more_important)
-            }
-        }
-    }
-
-    /// The `in_keyframe_block` parameter controls this:
-    ///
-    /// https://drafts.csswg.org/css-animations/#keyframes
-    /// > The <declaration-list> inside of <keyframe-block> accepts any CSS property
-    /// > except those defined in this specification,
-    /// > but does accept the `animation-play-state` property and interprets it specially.
-    ///
-    /// This will not actually parse Importance values, and will always set things
-    /// to Importance::Normal. Parsing Importance values is the job of PropertyDeclarationParser,
-    /// we only set them here so that we don't have to reallocate
-    pub fn parse(id: PropertyId, context: &ParserContext, input: &mut Parser)
-                 -> Result<ParsedDeclaration, PropertyDeclarationParseError> {
-        let rule_type = context.rule_type();
-        debug_assert!(rule_type == CssRuleType::Keyframe ||
-                      rule_type == CssRuleType::Page ||
-                      rule_type == CssRuleType::Style,
-                      "Declarations are only expected inside a keyframe, page, or style rule.");
-        match id {
-            PropertyId::Custom(name) => {
-                let value = match input.try(|i| CSSWideKeyword::parse(context, i)) {
-                    Ok(keyword) => DeclaredValueOwned::CSSWideKeyword(keyword),
-                    Err(()) => match ::custom_properties::SpecifiedValue::parse(context, input) {
-                        Ok(value) => DeclaredValueOwned::Value(value),
-                        Err(()) => return Err(PropertyDeclarationParseError::InvalidValue),
-                    }
-                };
-                Ok(ParsedDeclaration::LonghandOrCustom(PropertyDeclaration::Custom(name, value)))
-            }
-            PropertyId::Longhand(id) => match id {
-            % for property in data.longhands:
-                LonghandId::${property.camel_case} => {
-                    % if not property.derived_from:
-                        % if not property.allowed_in_keyframe_block:
-                            if rule_type == CssRuleType::Keyframe {
-                                return Err(PropertyDeclarationParseError::AnimationPropertyInKeyframeBlock)
-                            }
-                        % endif
-                        % if property.internal:
-                            if context.stylesheet_origin != Origin::UserAgent {
-                                return Err(PropertyDeclarationParseError::UnknownProperty)
-                            }
-                        % endif
-                        % if not property.allowed_in_page_rule:
-                            if rule_type == CssRuleType::Page {
-                                return Err(PropertyDeclarationParseError::NotAllowedInPageRule)
-                            }
-                        % endif
-
-                        ${property_pref_check(property)}
-
-                        match longhands::${property.ident}::parse_declared(context, input) {
-                            Ok(value) => {
-                                Ok(ParsedDeclaration::LonghandOrCustom(value))
-                            },
-                            Err(()) => Err(PropertyDeclarationParseError::InvalidValue),
-                        }
-                    % else:
-                        Err(PropertyDeclarationParseError::UnknownProperty)
-                    % endif
-                }
-            % endfor
-            },
-            PropertyId::Shorthand(id) => match id {
-            % for shorthand in data.shorthands:
-                ShorthandId::${shorthand.camel_case} => {
-                    % if not shorthand.allowed_in_keyframe_block:
-                        if rule_type == CssRuleType::Keyframe {
-                            return Err(PropertyDeclarationParseError::AnimationPropertyInKeyframeBlock)
-                        }
-                    % endif
-                    % if shorthand.internal:
-                        if context.stylesheet_origin != Origin::UserAgent {
-                            return Err(PropertyDeclarationParseError::UnknownProperty)
-                        }
-                    % endif
-                    % if not shorthand.allowed_in_page_rule:
-                        if rule_type == CssRuleType::Page {
-                            return Err(PropertyDeclarationParseError::NotAllowedInPageRule)
-                        }
-                    % endif
-
-                    ${property_pref_check(shorthand)}
-
-                    match input.try(|i| CSSWideKeyword::parse(context, i)) {
-                        Ok(keyword) => {
-                            Ok(ParsedDeclaration::${shorthand.camel_case}CSSWideKeyword(keyword))
-                        },
-                        Err(()) => {
-                            shorthands::${shorthand.ident}::parse(context, input)
-                                .map_err(|()| PropertyDeclarationParseError::InvalidValue)
-                        }
-                    }
-                }
-            % endfor
-            }
-        }
-    }
-}
-
 /// Servo's representation for a property declaration.
 #[derive(PartialEq, Clone)]
 pub enum PropertyDeclaration {
     % for property in data.longhands:
         /// ${property.name}
         % if property.boxed:
             ${property.camel_case}(Box<longhands::${property.ident}::SpecifiedValue>),
         % else:
@@ -1478,16 +1292,190 @@ impl PropertyDeclaration {
                         false
                     % endif
                 }
                 % endfor
             },
             PropertyDeclaration::Custom(..) => false,
         }
     }
+
+    /// The `context` parameter controls this:
+    ///
+    /// https://drafts.csswg.org/css-animations/#keyframes
+    /// > The <declaration-list> inside of <keyframe-block> accepts any CSS property
+    /// > except those defined in this specification,
+    /// > but does accept the `animation-play-state` property and interprets it specially.
+    ///
+    /// This will not actually parse Importance values, and will always set things
+    /// to Importance::Normal. Parsing Importance values is the job of PropertyDeclarationParser,
+    /// we only set them here so that we don't have to reallocate
+    pub fn parse_into(declarations: &mut SourcePropertyDeclaration,
+                      id: PropertyId, context: &ParserContext, input: &mut Parser)
+                      -> Result<(), PropertyDeclarationParseError> {
+        assert!(declarations.is_empty());
+        let rule_type = context.rule_type();
+        debug_assert!(rule_type == CssRuleType::Keyframe ||
+                      rule_type == CssRuleType::Page ||
+                      rule_type == CssRuleType::Style,
+                      "Declarations are only expected inside a keyframe, page, or style rule.");
+        match id {
+            PropertyId::Custom(name) => {
+                let value = match input.try(|i| CSSWideKeyword::parse(context, i)) {
+                    Ok(keyword) => DeclaredValueOwned::CSSWideKeyword(keyword),
+                    Err(()) => match ::custom_properties::SpecifiedValue::parse(context, input) {
+                        Ok(value) => DeclaredValueOwned::Value(value),
+                        Err(()) => return Err(PropertyDeclarationParseError::InvalidValue),
+                    }
+                };
+                declarations.push(PropertyDeclaration::Custom(name, value));
+                Ok(())
+            }
+            PropertyId::Longhand(id) => match id {
+            % for property in data.longhands:
+                LonghandId::${property.camel_case} => {
+                    % if not property.derived_from:
+                        % if not property.allowed_in_keyframe_block:
+                            if rule_type == CssRuleType::Keyframe {
+                                return Err(PropertyDeclarationParseError::AnimationPropertyInKeyframeBlock)
+                            }
+                        % endif
+                        % if property.internal:
+                            if context.stylesheet_origin != Origin::UserAgent {
+                                return Err(PropertyDeclarationParseError::UnknownProperty)
+                            }
+                        % endif
+                        % if not property.allowed_in_page_rule:
+                            if rule_type == CssRuleType::Page {
+                                return Err(PropertyDeclarationParseError::NotAllowedInPageRule)
+                            }
+                        % endif
+
+                        ${property_pref_check(property)}
+
+                        match longhands::${property.ident}::parse_declared(context, input) {
+                            Ok(value) => {
+                                declarations.push(value);
+                                Ok(())
+                            },
+                            Err(()) => Err(PropertyDeclarationParseError::InvalidValue),
+                        }
+                    % else:
+                        Err(PropertyDeclarationParseError::UnknownProperty)
+                    % endif
+                }
+            % endfor
+            },
+            PropertyId::Shorthand(id) => match id {
+            % for shorthand in data.shorthands:
+                ShorthandId::${shorthand.camel_case} => {
+                    % if not shorthand.allowed_in_keyframe_block:
+                        if rule_type == CssRuleType::Keyframe {
+                            return Err(PropertyDeclarationParseError::AnimationPropertyInKeyframeBlock)
+                        }
+                    % endif
+                    % if shorthand.internal:
+                        if context.stylesheet_origin != Origin::UserAgent {
+                            return Err(PropertyDeclarationParseError::UnknownProperty)
+                        }
+                    % endif
+                    % if not shorthand.allowed_in_page_rule:
+                        if rule_type == CssRuleType::Page {
+                            return Err(PropertyDeclarationParseError::NotAllowedInPageRule)
+                        }
+                    % endif
+
+                    ${property_pref_check(shorthand)}
+
+                    match input.try(|i| CSSWideKeyword::parse(context, i)) {
+                        Ok(keyword) => {
+                            % if shorthand.name == "all":
+                                declarations.all_shorthand = AllShorthand::CSSWideKeyword(keyword);
+                            % else:
+                                % for sub_property in shorthand.sub_properties:
+                                    declarations.push(PropertyDeclaration::CSSWideKeyword(
+                                        LonghandId::${sub_property.camel_case},
+                                        keyword,
+                                    ));
+                                % endfor
+                            % endif
+                            Ok(())
+                        },
+                        Err(()) => {
+                            shorthands::${shorthand.ident}::parse_into(declarations, context, input)
+                                .map_err(|()| PropertyDeclarationParseError::InvalidValue)
+                        }
+                    }
+                }
+            % endfor
+            }
+        }
+    }
+}
+
+const MAX_SUB_PROPERTIES_PER_SHORTHAND_EXCEPT_ALL: usize =
+    ${max(len(s.sub_properties) for s in data.shorthands_except_all())};
+
+type SourcePropertyDeclarationArray =
+    [PropertyDeclaration; MAX_SUB_PROPERTIES_PER_SHORTHAND_EXCEPT_ALL];
+
+/// A stack-allocated vector of `PropertyDeclaration`
+/// large enough to parse one CSS `key: value` declaration.
+/// (Shorthands expand to multiple `PropertyDeclaration`s.)
+pub struct SourcePropertyDeclaration {
+    declarations: ::arrayvec::ArrayVec<SourcePropertyDeclarationArray>,
+
+    /// Stored separately to keep MAX_SUB_PROPERTIES_PER_SHORTHAND_EXCEPT_ALL smaller.
+    all_shorthand: AllShorthand,
+}
+
+impl SourcePropertyDeclaration {
+    /// Create one. It’s big, try not to move it around.
+    #[inline]
+    pub fn new() -> Self {
+        SourcePropertyDeclaration {
+            declarations: ::arrayvec::ArrayVec::new(),
+            all_shorthand: AllShorthand::NotSet,
+        }
+    }
+
+    /// Similar to Vec::drain: leaves this empty when the return value is dropped.
+    pub fn drain(&mut self) -> SourcePropertyDeclarationDrain {
+        SourcePropertyDeclarationDrain {
+            declarations: self.declarations.drain(..),
+            all_shorthand: mem::replace(&mut self.all_shorthand, AllShorthand::NotSet),
+        }
+    }
+
+    /// Reset to initial state
+    pub fn clear(&mut self) {
+        self.declarations.clear();
+        self.all_shorthand = AllShorthand::NotSet;
+    }
+
+    fn is_empty(&self) -> bool {
+        self.declarations.is_empty() && matches!(self.all_shorthand, AllShorthand::NotSet)
+    }
+
+    fn push(&mut self, declaration: PropertyDeclaration) {
+        let over_capacity = self.declarations.push(declaration).is_some();
+        debug_assert!(!over_capacity);
+    }
+}
+
+/// Return type of SourcePropertyDeclaration::drain
+pub struct SourcePropertyDeclarationDrain<'a> {
+    declarations: ::arrayvec::Drain<'a, SourcePropertyDeclarationArray>,
+    all_shorthand: AllShorthand,
+}
+
+enum AllShorthand {
+    NotSet,
+    CSSWideKeyword(CSSWideKeyword),
+    WithVariables(Arc<UnparsedValue>)
 }
 
 #[cfg(feature = "gecko")]
 pub use gecko_properties::style_structs;
 
 /// The module where all the style structs are defined.
 #[cfg(feature = "servo")]
 pub mod style_structs {
--- a/servo/components/style/properties/shorthand/background.mako.rs
+++ b/servo/components/style/properties/shorthand/background.mako.rs
@@ -103,17 +103,17 @@
                     }
                 % endfor
                 Ok(())
             } else {
                 Err(())
             }
         }));
 
-        Ok(Longhands {
+        Ok(expanded! {
              background_color: background_color.unwrap_or(CSSColor::transparent()),
              background_image: background_image,
              background_position_x: background_position_x,
              background_position_y: background_position_y,
              background_repeat: background_repeat,
              background_attachment: background_attachment,
              background_size: background_size,
              background_origin: background_origin,
@@ -202,17 +202,17 @@
             position_y.0.push(value.vertical);
             any = true;
             Ok(())
         })?;
         if !any {
             return Err(());
         }
 
-        Ok(Longhands {
+        Ok(expanded! {
             background_position_x: position_x,
             background_position_y: position_y,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             let len = self.background_position_x.0.len();
--- a/servo/components/style/properties/shorthand/border.mako.rs
+++ b/servo/components/style/properties/shorthand/border.mako.rs
@@ -19,17 +19,17 @@
     spec="https://drafts.csswg.org/css-backgrounds/#border-width">
     use super::parse_four_sides;
     use values::specified::{AllowQuirks, BorderWidth};
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         let (top, right, bottom, left) = try!(parse_four_sides(input, |i| {
             BorderWidth::parse_quirky(context, i, AllowQuirks::Yes)
         }));
-        Ok(Longhands {
+        Ok(expanded! {
             % for side in PHYSICAL_SIDES:
                 ${to_rust_ident('border-%s-width' % side)}: ${side},
             % endfor
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
@@ -98,17 +98,17 @@ pub fn parse_border(context: &ParserCont
             'border-%s-%s' % (side, prop)
             for prop in ['color', 'style', 'width']
         )}"
         alias="${maybe_moz_logical_alias(product, (side, logical), '-moz-border-%s')}"
         spec="${spec}">
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         let (color, style, width) = try!(super::parse_border(context, input));
-        Ok(Longhands {
+        Ok(expanded! {
             border_${to_rust_ident(side)}_color: color,
             border_${to_rust_ident(side)}_style: style,
             border_${to_rust_ident(side)}_width: width
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
@@ -139,17 +139,17 @@ pub fn parse_border(context: &ParserCont
                                     _moz_border_bottom_colors, _moz_border_left_colors};
     % endif
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         use properties::longhands::{border_image_outset, border_image_repeat, border_image_slice};
         use properties::longhands::{border_image_source, border_image_width};
 
         let (color, style, width) = try!(super::parse_border(context, input));
-        Ok(Longhands {
+        Ok(expanded! {
             % for side in PHYSICAL_SIDES:
                 border_${side}_color: color.clone(),
                 border_${side}_style: style,
                 border_${side}_width: width.clone(),
                 % if product == "gecko":
                     _moz_border_${side}_colors: _moz_border_${side}_colors::get_initial_specified_value(),
                 % endif
             % endfor
@@ -206,17 +206,17 @@ pub fn parse_border(context: &ParserCont
      for corner in ['top-left', 'top-right', 'bottom-right', 'bottom-left']
 )}" extra_prefixes="webkit" spec="https://drafts.csswg.org/css-backgrounds/#border-radius">
     use values::generics::serialize_radius_values;
     use values::specified::basic_shape::BorderRadius;
     use parser::Parse;
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         let radii = try!(BorderRadius::parse(context, input));
-        Ok(Longhands {
+        Ok(expanded! {
             border_top_left_radius: radii.top_left,
             border_top_right_radius: radii.top_right,
             border_bottom_right_radius: radii.bottom_right,
             border_bottom_left_radius: radii.bottom_left,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
@@ -298,17 +298,17 @@ pub fn parse_border(context: &ParserCont
                     }
                 % endfor
                 Ok(())
             } else {
                 Err(())
             }
         }));
 
-        Ok(Longhands {
+        Ok(expanded! {
             % for name in "outset repeat slice source width".split():
                 border_image_${name}: border_image_${name},
             % endfor
          })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
--- a/servo/components/style/properties/shorthand/box.mako.rs
+++ b/servo/components/style/properties/shorthand/box.mako.rs
@@ -11,41 +11,41 @@
         use properties::longhands::overflow_x::SpecifiedValue;
     % endif
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         % if product == "gecko":
             let moz_kw_found = input.try(|i| match_ignore_ascii_case! {
                 &i.expect_ident()?,
                 "-moz-scrollbars-horizontal" => {
-                    Ok(Longhands {
+                    Ok(expanded! {
                         overflow_x: SpecifiedValue::scroll,
                         overflow_y: SpecifiedValue::hidden,
                     })
                 }
                 "-moz-scrollbars-vertical" => {
-                    Ok(Longhands {
+                    Ok(expanded! {
                         overflow_x: SpecifiedValue::hidden,
                         overflow_y: SpecifiedValue::scroll,
                     })
                 }
                 "-moz-scrollbars-none" => {
-                    Ok(Longhands {
+                    Ok(expanded! {
                         overflow_x: SpecifiedValue::hidden,
                         overflow_y: SpecifiedValue::hidden,
                     })
                 }
                 _ => Err(())
             });
             if moz_kw_found.is_ok() {
                 return moz_kw_found
             }
         % endif
         let overflow = try!(parse_overflow(context, input));
-        Ok(Longhands {
+        Ok(expanded! {
             overflow_x: overflow,
             overflow_y: overflow,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             if self.overflow_x == self.overflow_y {
@@ -143,17 +143,17 @@ macro_rules! try_parse_one {
             // a valid TransitionProperty.
             // durations, delays, and timing_functions are not allowed as empty, so before we convert them into
             // longhand properties, we need to put initial values for none transition.
             % for prop in "duration timing_function delay".split():
             ${prop}s.push(transition_${prop}::single_value::get_initial_specified_value());
             % endfor
         }
 
-        Ok(Longhands {
+        Ok(expanded! {
             % for prop in "property duration timing_function delay".split():
             transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s),
             % endfor
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
@@ -254,17 +254,17 @@ macro_rules! try_parse_one {
 
         let results = try!(input.parse_comma_separated(|i| parse_one_animation(context, i)));
         for result in results.into_iter() {
             % for prop in props:
             ${prop}s.push(result.animation_${prop});
             % endfor
         }
 
-        Ok(Longhands {
+        Ok(expanded! {
             % for prop in props:
             animation_${prop}: animation_${prop}::SpecifiedValue(${prop}s),
             % endfor
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
@@ -300,17 +300,17 @@ macro_rules! try_parse_one {
 
 <%helpers:shorthand name="scroll-snap-type" products="gecko"
                     sub_properties="scroll-snap-type-x scroll-snap-type-y"
                     spec="https://drafts.csswg.org/css-scroll-snap/#propdef-scroll-snap-type">
     use properties::longhands::scroll_snap_type_x;
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         let result = try!(scroll_snap_type_x::parse(context, input));
-        Ok(Longhands {
+        Ok(expanded! {
             scroll_snap_type_x: result,
             scroll_snap_type_y: result,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         // Serializes into the single keyword value if both scroll-snap-type and scroll-snap-type-y are same.
         // Otherwise into an empty string. This is done to match Gecko's behaviour.
@@ -327,17 +327,17 @@ macro_rules! try_parse_one {
 
 <%helpers:shorthand name="-moz-transform" products="gecko"
                     sub_properties="transform"
                     flags="SHORTHAND_ALIAS_PROPERTY"
                     spec="Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/transform">
     use properties::longhands::transform;
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
-        Ok(Longhands {
+        Ok(expanded! {
             transform: transform::parse_prefixed(context, input)?,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             self.transform.to_css(dest)
         }
--- a/servo/components/style/properties/shorthand/column.mako.rs
+++ b/servo/components/style/properties/shorthand/column.mako.rs
@@ -37,17 +37,17 @@
 
             break
         }
 
         let values = autos + column_count.iter().len() + column_width.iter().len();
         if values == 0 || values > 2 {
             Err(())
         } else {
-            Ok(Longhands {
+            Ok(expanded! {
                 column_count: unwrap_or_initial!(column_count),
                 column_width: unwrap_or_initial!(column_width),
             })
         }
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
@@ -81,17 +81,17 @@
                     continue
                 }
             }
             % endfor
 
             break
         }
         if any {
-            Ok(Longhands {
+            Ok(expanded! {
                 column_rule_width: unwrap_or_initial!(column_rule_width),
                 column_rule_style: unwrap_or_initial!(column_rule_style),
                 column_rule_color: unwrap_or_initial!(column_rule_color),
             })
         } else {
             Err(())
         }
     }
--- a/servo/components/style/properties/shorthand/font.mako.rs
+++ b/servo/components/style/properties/shorthand/font.mako.rs
@@ -39,17 +39,17 @@
         let mut nb_normals = 0;
         let mut style = None;
         let mut variant_caps = None;
         let mut weight = None;
         let mut stretch = None;
         let size;
         % if product == "gecko":
             if let Ok(sys) = input.try(SystemFont::parse) {
-                return Ok(Longhands {
+                return Ok(expanded! {
                      % for name in SYSTEM_FONT_LONGHANDS:
                          ${name}: ${name}::SpecifiedValue::system_font(sys),
                      % endfor
                      // line-height is just reset to initial
                      line_height: line_height::get_initial_specified_value(),
                  })
             }
         % endif
@@ -97,17 +97,17 @@
             return Err(())
         }
         let line_height = if input.try(|input| input.expect_delim('/')).is_ok() {
             Some(try!(line_height::parse(context, input)))
         } else {
             None
         };
         let family = FontFamily::parse(input)?;
-        Ok(Longhands {
+        Ok(expanded! {
             % for name in "style weight stretch size variant_caps".split():
                 font_${name}: unwrap_or_initial!(font_${name}, ${name}),
             % endfor
             line_height: unwrap_or_initial!(line_height),
             font_family: family,
             % if product == "gecko" or data.testing:
                 % for name in gecko_sub_properties:
                     font_${name}: font_${name}::get_initial_specified_value(),
@@ -231,17 +231,17 @@
         #[inline]
         fn count<T>(opt: &Option<T>) -> u8 {
             if opt.is_some() { 1 } else { 0 }
         }
         let count = count(&caps) + nb_normals;
         if count == 0 || count > 1 {
             return Err(())
         }
-        Ok(Longhands {
+        Ok(expanded! {
             font_variant_caps: unwrap_or_initial!(font_variant_caps, caps),
             // FIXME: Bug 1356134 - parse all sub properties.
             % if product == "gecko" or data.testing:
                 % for name in gecko_sub_properties:
                     font_variant_${name}: font_variant_${name}::get_initial_specified_value(),
                 % endfor
             % endif
         })
--- a/servo/components/style/properties/shorthand/inherited_svg.mako.rs
+++ b/servo/components/style/properties/shorthand/inherited_svg.mako.rs
@@ -8,17 +8,17 @@
     sub_properties="marker-start marker-end marker-mid"
     spec="https://www.w3.org/TR/SVG2/painting.html#MarkerShorthand">
     use values::specified::UrlOrNone;
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         use parser::Parse;
         let url = UrlOrNone::parse(context, input)?;
 
-        Ok(Longhands {
+        Ok(expanded! {
             marker_start: url.clone(),
             marker_mid: url.clone(),
             marker_end: url,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
--- a/servo/components/style/properties/shorthand/inherited_text.mako.rs
+++ b/servo/components/style/properties/shorthand/inherited_text.mako.rs
@@ -24,17 +24,17 @@
                 if let Ok(value) = input.try(|input| text_emphasis_style::parse(context, input)) {
                     style = Some(value);
                     continue
                 }
             }
             break
         }
         if color.is_some() || style.is_some() {
-            Ok(Longhands {
+            Ok(expanded! {
                 text_emphasis_color: unwrap_or_initial!(text_emphasis_color, color),
                 text_emphasis_style: unwrap_or_initial!(text_emphasis_style, style),
             })
         } else {
             Err(())
         }
     }
 
@@ -72,17 +72,17 @@
                     width = Some(value);
                     continue
                 }
             }
             break
         }
 
         if color.is_some() || width.is_some() {
-            Ok(Longhands {
+            Ok(expanded! {
                 _webkit_text_stroke_color: unwrap_or_initial!(_webkit_text_stroke_color, color),
                 _webkit_text_stroke_width: unwrap_or_initial!(_webkit_text_stroke_width, width),
             })
         } else {
             Err(())
         }
     }
 
--- a/servo/components/style/properties/shorthand/list.mako.rs
+++ b/servo/components/style/properties/shorthand/list.mako.rs
@@ -65,45 +65,45 @@
             % endif
         }
 
         // If there are two `none`s, then we can't have a type or image; if there is one `none`,
         // then we can't have both a type *and* an image; if there is no `none` then we're fine as
         // long as we parsed something.
         match (any, nones, list_style_type, image) {
             (true, 2, None, None) => {
-                Ok(Longhands {
+                Ok(expanded! {
                     list_style_position: position,
                     list_style_image: list_style_image::SpecifiedValue(Either::Second(None_)),
                     list_style_type: list_style_type_none(),
                 })
             }
             (true, 1, None, Some(image)) => {
-                Ok(Longhands {
+                Ok(expanded! {
                     list_style_position: position,
                     list_style_image: image,
                     list_style_type: list_style_type_none(),
                 })
             }
             (true, 1, Some(list_style_type), None) => {
-                Ok(Longhands {
+                Ok(expanded! {
                     list_style_position: position,
                     list_style_image: list_style_image::SpecifiedValue(Either::Second(None_)),
                     list_style_type: list_style_type,
                 })
             }
             (true, 1, None, None) => {
-                Ok(Longhands {
+                Ok(expanded! {
                     list_style_position: position,
                     list_style_image: list_style_image::SpecifiedValue(Either::Second(None_)),
                     list_style_type: list_style_type_none(),
                 })
             }
             (true, 0, list_style_type, image) => {
-                Ok(Longhands {
+                Ok(expanded! {
                     list_style_position: position,
                     list_style_image: unwrap_or_initial!(list_style_image, image),
                     list_style_type: unwrap_or_initial!(list_style_type),
                 })
             }
             _ => Err(()),
         }
     }
--- a/servo/components/style/properties/shorthand/mask.mako.rs
+++ b/servo/components/style/properties/shorthand/mask.mako.rs
@@ -102,17 +102,17 @@
                     }
                 % endfor
                 Ok(())
             } else {
                 Err(())
             }
         }));
 
-        Ok(Longhands {
+        Ok(expanded! {
             % for name in "image mode position_x position_y size repeat origin clip composite".split():
                 mask_${name}: mask_${name},
             % endfor
          })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
@@ -191,17 +191,17 @@
             position_y.0.push(value.vertical);
             any = true;
             Ok(())
         })?;
         if any == false {
             return Err(());
         }
 
-        Ok(Longhands {
+        Ok(expanded! {
             mask_position_x: position_x,
             mask_position_y: position_y,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             let len = self.mask_position_x.0.len();
--- a/servo/components/style/properties/shorthand/outline.mako.rs
+++ b/servo/components/style/properties/shorthand/outline.mako.rs
@@ -36,17 +36,17 @@
                     width = Some(value);
                     any = true;
                     continue
                 }
             }
             break
         }
         if any {
-            Ok(Longhands {
+            Ok(expanded! {
                 outline_color: unwrap_or_initial!(outline_color, color),
                 outline_style: unwrap_or_initial!(outline_style, style),
                 outline_width: unwrap_or_initial!(outline_width, width),
             })
         } else {
             Err(())
         }
     }
@@ -68,17 +68,17 @@
     for corner in ['topleft', 'topright', 'bottomright', 'bottomleft']
 )}" products="gecko" spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-outline-radius)">
     use properties::shorthands;
     use values::generics::serialize_radius_values;
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         // Re-use border-radius parsing.
         shorthands::border_radius::parse_value(context, input).map(|longhands| {
-            Longhands {
+            expanded! {
                 % for corner in ["top_left", "top_right", "bottom_right", "bottom_left"]:
                 _moz_outline_radius_${corner.replace("_", "")}: longhands.border_${corner}_radius,
                 % endfor
             }
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
--- a/servo/components/style/properties/shorthand/position.mako.rs
+++ b/servo/components/style/properties/shorthand/position.mako.rs
@@ -25,17 +25,17 @@
                 }
             }
             break
         }
 
         if direction.is_none() && wrap.is_none() {
             return Err(())
         }
-        Ok(Longhands {
+        Ok(expanded! {
             flex_direction: unwrap_or_initial!(flex_direction, direction),
             flex_wrap: unwrap_or_initial!(flex_wrap, wrap),
         })
     }
 
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
@@ -58,17 +58,17 @@
     }
 
     pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
         let mut grow = None;
         let mut shrink = None;
         let mut basis = None;
 
         if input.try(|input| input.expect_ident_matching("none")).is_ok() {
-            return Ok(Longhands {
+            return Ok(expanded! {
                 flex_grow: Number::new(0.0),
                 flex_shrink: Number::new(0.0),
                 flex_basis: longhands::flex_basis::SpecifiedValue::auto(),
             })
         }
         loop {
             if grow.is_none() {
                 if let Ok((flex_grow, flex_shrink)) = input.try(|i| parse_flexibility(context, i)) {
@@ -84,17 +84,17 @@
                 }
             }
             break
         }
 
         if grow.is_none() && basis.is_none() {
             return Err(())
         }
-        Ok(Longhands {
+        Ok(expanded! {
             flex_grow: grow.unwrap_or(Number::new(1.0)),
             flex_shrink: shrink.unwrap_or(Number::new(1.0)),
             flex_basis: basis.unwrap_or(longhands::flex_basis::SpecifiedValue::zero()),
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
@@ -113,17 +113,17 @@
                     spec="https://drafts.csswg.org/css-grid/#propdef-grid-gap"
                     products="gecko">
   use properties::longhands::{grid_row_gap, grid_column_gap};
 
   pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result<Longhands, ()> {
       let row_gap = grid_row_gap::parse(context, input)?;
       let column_gap = input.try(|input| grid_column_gap::parse(context, input)).unwrap_or(row_gap.clone());
 
-      Ok(Longhands {
+      Ok(expanded! {
         grid_row_gap: row_gap,
         grid_column_gap: column_gap,
       })
   }
 
   impl<'a> ToCss for LonghandsToSerialize<'a>  {
       fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
           if self.grid_row_gap == self.grid_column_gap {
@@ -156,17 +156,17 @@
             let mut line = GridLine::default();
             if start.line_num.is_none() && !start.is_span {
                 line.ident = start.ident.clone();       // ident from start value should be taken
             }
 
             line
         };
 
-        Ok(Longhands {
+        Ok(expanded! {
             grid_${kind}_start: start,
             grid_${kind}_end: end,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a> {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             self.grid_${kind}_start.to_css(dest)?;
@@ -214,17 +214,17 @@
             };
 
             (column_start, row_end, column_end)
         } else {        // only grid-row-start is given
             let line = line_with_ident_from(&row_start);
             (line.clone(), line.clone(), line)
         };
 
-        Ok(Longhands {
+        Ok(expanded! {
             grid_row_start: row_start,
             grid_row_end: row_end,
             grid_column_start: column_start,
             grid_column_end: column_end,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a> {
@@ -253,17 +253,17 @@
             return Err(());
         }
         let justify = input.try(|input| justify_content::parse(context, input))
                            .unwrap_or(justify_content::SpecifiedValue::from(align));
         if justify.has_extra_flags() {
             return Err(());
         }
 
-        Ok(Longhands {
+        Ok(expanded! {
             align_content: align,
             justify_content: justify,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a> {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             self.align_content.to_css(dest)?;
@@ -287,17 +287,17 @@
         if align.has_extra_flags() {
             return Err(());
         }
         let justify = input.try(|input| AlignJustifySelf::parse(context, input)).unwrap_or(align.clone());
         if justify.has_extra_flags() {
             return Err(());
         }
 
-        Ok(Longhands {
+        Ok(expanded! {
             align_self: align,
             justify_self: justify,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a> {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             if self.align_self == self.justify_self {
@@ -329,17 +329,17 @@
             return Err(());
         }
         let justify = input.try(|input| JustifyItems::parse(context, input))
                            .unwrap_or(JustifyItems::from(align));
         if justify.has_extra_flags() {
             return Err(());
         }
 
-        Ok(Longhands {
+        Ok(expanded! {
             align_items: align,
             justify_items: justify,
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a> {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             if self.align_items.0 == self.justify_items.0 {
--- a/servo/components/style/properties/shorthand/text.mako.rs
+++ b/servo/components/style/properties/shorthand/text.mako.rs
@@ -45,17 +45,17 @@
 
             break;
         }
 
         if !any {
             return Err(());
         }
 
-        Ok(Longhands {
+        Ok(expanded! {
             text_decoration_line: unwrap_or_initial!(text_decoration_line, line),
 
             % if product == "gecko" or data.testing:
                 text_decoration_style: unwrap_or_initial!(text_decoration_style, style),
                 text_decoration_color: unwrap_or_initial!(text_decoration_color, color),
             % endif
         })
     }
--- a/servo/components/style/supports.rs
+++ b/servo/components/style/supports.rs
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! [@supports rules](https://drafts.csswg.org/css-conditional-3/#at-supports)
 
 use cssparser::{parse_important, Parser, Token};
 use parser::ParserContext;
-use properties::{PropertyId, ParsedDeclaration};
+use properties::{PropertyId, PropertyDeclaration, SourcePropertyDeclaration};
 use std::fmt;
 use style_traits::ToCss;
 use stylesheets::CssRuleType;
 
 #[derive(Debug)]
 /// An @supports condition
 ///
 /// https://drafts.csswg.org/css-conditional-3/#at-supports
@@ -208,13 +208,14 @@ impl Declaration {
     pub fn eval(&self, cx: &ParserContext) -> bool {
         let id = if let Ok(id) = PropertyId::parse((&*self.prop).into()) {
             id
         } else {
             return false
         };
         let mut input = Parser::new(&self.val);
         let context = ParserContext::new_with_rule_type(cx, Some(CssRuleType::Style));
-        let res = ParsedDeclaration::parse(id, &context, &mut input);
+        let mut declarations = SourcePropertyDeclaration::new();
+        let res = PropertyDeclaration::parse_into(&mut declarations, id, &context, &mut input);
         let _ = input.try(parse_important);
         res.is_ok() && input.is_exhausted()
     }
 }
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -75,21 +75,21 @@ use style::gecko_bindings::structs::nsre
 use style::gecko_bindings::sugar::ownership::{FFIArcHelpers, HasFFI, HasArcFFI, HasBoxFFI};
 use style::gecko_bindings::sugar::ownership::{HasSimpleFFI, Strong};
 use style::gecko_bindings::sugar::refptr::RefPtr;
 use style::gecko_properties::{self, style_structs};
 use style::keyframes::{Keyframe, KeyframeSelector, KeyframesStepValue};
 use style::media_queries::{MediaList, parse_media_query_list};
 use style::parallel;
 use style::parser::{PARSING_MODE_DEFAULT, ParserContext};
-use style::properties::{CascadeFlags, ComputedValues, Importance, ParsedDeclaration, StyleBuilder};
-use style::properties::{LonghandIdSet, PropertyDeclarationBlock, PropertyId};
+use style::properties::{CascadeFlags, ComputedValues, Importance, SourcePropertyDeclaration};
+use style::properties::{LonghandIdSet, PropertyDeclarationBlock, PropertyId, StyleBuilder};
 use style::properties::SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP;
 use style::properties::animated_properties::{Animatable, AnimationValue, TransitionProperty};
-use style::properties::parse_one_declaration;
+use style::properties::parse_one_declaration_into;
 use style::restyle_hints::{self, RestyleHint};
 use style::rule_tree::StyleSource;
 use style::selector_parser::PseudoElementCascadeType;
 use style::sequential;
 use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard, Locked};
 use style::string_cache::Atom;
 use style::style_adjuster::StyleAdjuster;
 use style::stylearc::Arc;
@@ -1247,47 +1247,52 @@ pub extern "C" fn Servo_StyleSet_Clear(r
     data.clear_stylist();
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSet_Drop(data: RawServoStyleSetOwned) {
     let _ = data.into_box::<PerDocumentStyleData>();
 }
 
-fn parse_property(property_id: PropertyId,
-                  value: *const nsACString,
-                  data: *mut URLExtraData,
-                  parsing_mode: structs::ParsingMode,
-                  quirks_mode: QuirksMode) -> Result<ParsedDeclaration, ()> {
+fn parse_property_into(declarations: &mut SourcePropertyDeclaration,
+                       property_id: PropertyId,
+                       value: *const nsACString,
+                       data: *mut URLExtraData,
+                       parsing_mode: structs::ParsingMode,
+                       quirks_mode: QuirksMode) -> Result<(), ()> {
     use style::parser::ParsingMode;
     let value = unsafe { value.as_ref().unwrap().as_str_unchecked() };
     let url_data = unsafe { RefPtr::from_ptr_ref(&data) };
     let parsing_mode = ParsingMode::from_bits_truncate(parsing_mode);
 
-    parse_one_declaration(property_id,
-                          value,
-                          url_data,
-                          &RustLogReporter,
-                          parsing_mode,
-                          quirks_mode)
+    parse_one_declaration_into(
+        declarations,
+        property_id,
+        value,
+        url_data,
+        &RustLogReporter,
+        parsing_mode,
+        quirks_mode)
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ParseProperty(property: nsCSSPropertyID, value: *const nsACString,
                                       data: *mut URLExtraData,
                                       parsing_mode: structs::ParsingMode,
                                       quirks_mode: nsCompatibility)
                                       -> RawServoDeclarationBlockStrong {
     let id = get_property_id_from_nscsspropertyid!(property,
                                                    RawServoDeclarationBlockStrong::null());
-    match parse_property(id, value, data, parsing_mode, quirks_mode.into()) {
-        Ok(parsed) => {
+    let mut declarations = SourcePropertyDeclaration::new();
+    match parse_property_into(&mut declarations, id, value, data,
+                              parsing_mode, quirks_mode.into()) {
+        Ok(()) => {
             let global_style_data = &*GLOBAL_STYLE_DATA;
             let mut block = PropertyDeclarationBlock::new();
-            parsed.expand_push_into(&mut block, Importance::Normal);
+            block.extend(declarations.drain(), Importance::Normal);
             Arc::new(global_style_data.shared_lock.wrap(block)).into_strong()
         }
         Err(_) => RawServoDeclarationBlockStrong::null()
     }
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ParseEasing(easing: *const nsAString,
@@ -1434,21 +1439,23 @@ pub extern "C" fn Servo_DeclarationBlock
         decls.property_priority(&property_id).important()
     })
 }
 
 fn set_property(declarations: RawServoDeclarationBlockBorrowed, property_id: PropertyId,
                 value: *const nsACString, is_important: bool, data: *mut URLExtraData,
                 parsing_mode: structs::ParsingMode,
                 quirks_mode: QuirksMode) -> bool {
-    match parse_property(property_id, value, data, parsing_mode, quirks_mode) {
-        Ok(parsed) => {
+    let mut source_declarations = SourcePropertyDeclaration::new();
+    match parse_property_into(&mut source_declarations, property_id, value, data,
+                              parsing_mode, quirks_mode) {
+        Ok(()) => {
             let importance = if is_important { Importance::Important } else { Importance::Normal };
             write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
-                parsed.expand_set_into(decls, importance)
+                decls.extend_reset(source_declarations.drain(), importance)
             })
         },
         Err(_) => false,
     }
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_DeclarationBlock_SetProperty(declarations: RawServoDeclarationBlockBorrowed,
@@ -1963,21 +1970,25 @@ pub extern "C" fn Servo_DeclarationBlock
     })
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_CSSSupports2(property: *const nsACString,
                                      value: *const nsACString) -> bool {
     let id = get_property_id_from_property!(property, false);
 
-    parse_property(id,
-                   value,
-                   unsafe { DUMMY_URL_DATA },
-                   structs::ParsingMode_Default,
-                   QuirksMode::NoQuirks).is_ok()
+    let mut declarations = SourcePropertyDeclaration::new();
+    parse_property_into(
+        &mut declarations,
+        id,
+        value,
+        unsafe { DUMMY_URL_DATA },
+        structs::ParsingMode_Default,
+        QuirksMode::NoQuirks
+    ).is_ok()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_CSSSupports(cond: *const nsACString) -> bool {
     let condition = unsafe { cond.as_ref().unwrap().as_str_unchecked() };
     let mut input = Parser::new(&condition);
     let cond = parse_condition_or_declaration(&mut input);
     if let Ok(cond) = cond {
--- a/servo/python/servo/testing_commands.py
+++ b/servo/python/servo/testing_commands.py
@@ -283,29 +283,32 @@ class MachCommands(CommandBase):
 
             if nocapture:
                 args += ["--", "--nocapture"]
             return call(args, env=env, cwd=self.servo_crate())
 
     @Command('test-stylo',
              description='Run stylo unit tests',
              category='testing')
+    @CommandArgument('test_name', nargs=argparse.REMAINDER,
+                     help="Only run tests that match this pattern or file path")
     @CommandArgument('--release', default=False, action="store_true",
                      help="Run with a release build of servo")
-    def test_stylo(self, release=False):
+    def test_stylo(self, release=False, test_name=None):
         self.set_use_stable_rust()
         self.ensure_bootstrapped()
 
         env = self.build_env()
         env["RUST_BACKTRACE"] = "1"
         env["CARGO_TARGET_DIR"] = path.join(self.context.topdir, "target", "geckolib").encode("UTF-8")
 
-        release = ["--release"] if release else []
+        args = (["cargo", "test", "-p", "stylo_tests", "--features", "testing"] +
+                (["--release"] if release else []) + (test_name or []))
         with cd(path.join("ports", "geckolib")):
-            return call(["cargo", "test", "-p", "stylo_tests", "--features", "testing"] + release, env=env)
+            return call(args, env=env)
 
     @Command('test-compiletest',
              description='Run compiletests',
              category='testing')
     @CommandArgument('--package', '-p', default=None, help="Specific package to test")
     @CommandArgument('test_name', nargs=argparse.REMAINDER,
                      help="Only run tests that match this pattern or file path")
     @CommandArgument('--release', default=False, action="store_true",
--- a/servo/tests/unit/style/parsing/border.rs
+++ b/servo/tests/unit/style/parsing/border.rs
@@ -1,81 +1,89 @@
 /* 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 parsing::parse;
 use style::parser::Parse;
+use style::properties::MaybeBoxed;
 use style::properties::longhands::{border_image_outset, border_image_repeat, border_image_slice};
 use style::properties::longhands::{border_image_source, border_image_width};
 use style::properties::shorthands::border_image;
 use style_traits::ToCss;
 
+macro_rules! assert_longhand {
+    ($parsed_shorthand: expr, $prop: ident, $value_string: expr) => {
+        assert_eq!($parsed_shorthand.$prop, parse_longhand!($prop, $value_string).maybe_boxed())
+    }
+}
+
+macro_rules! assert_initial {
+    ($parsed_shorthand: expr, $prop: ident) => {
+        assert_eq!($parsed_shorthand.$prop, $prop::get_initial_specified_value().maybe_boxed())
+    }
+}
+
 #[test]
 fn border_image_shorthand_should_parse_when_all_properties_specified() {
     let input = "linear-gradient(red, blue) 30 30% 45 fill / 20px 40px / 10px round stretch";
     let result = parse(border_image::parse_value, input).unwrap();
 
-    assert_eq!(result.border_image_source,
-               parse_longhand!(border_image_source, "linear-gradient(red, blue)"));
-    assert_eq!(result.border_image_slice, parse_longhand!(border_image_slice, "30 30% 45 fill"));
-    assert_eq!(result.border_image_width, parse_longhand!(border_image_width, "20px 40px"));
-    assert_eq!(result.border_image_outset, parse_longhand!(border_image_outset, "10px"));
-    assert_eq!(result.border_image_repeat, parse_longhand!(border_image_repeat, "round stretch"));
+    assert_longhand!(result, border_image_source, "linear-gradient(red, blue)");
+    assert_longhand!(result, border_image_slice, "30 30% 45 fill");
+    assert_longhand!(result, border_image_width, "20px 40px");
+    assert_longhand!(result, border_image_outset, "10px");
+    assert_longhand!(result, border_image_repeat, "round stretch");
 }
 
 #[test]
 fn border_image_shorthand_should_parse_without_width() {
     let input = "linear-gradient(red, blue) 30 30% 45 fill / / 10px round stretch";
     let result = parse(border_image::parse_value, input).unwrap();
 
-    assert_eq!(result.border_image_source,
-               parse_longhand!(border_image_source, "linear-gradient(red, blue)"));
-    assert_eq!(result.border_image_slice, parse_longhand!(border_image_slice, "30 30% 45 fill"));
-    assert_eq!(result.border_image_outset, parse_longhand!(border_image_outset, "10px"));
-    assert_eq!(result.border_image_repeat, parse_longhand!(border_image_repeat, "round stretch"));
-    assert_eq!(result.border_image_width, border_image_width::get_initial_specified_value());
+    assert_longhand!(result, border_image_source, "linear-gradient(red, blue)");
+    assert_longhand!(result, border_image_slice, "30 30% 45 fill");
+    assert_longhand!(result, border_image_outset, "10px");
+    assert_longhand!(result, border_image_repeat, "round stretch");
+    assert_initial!(result, border_image_width);
 }
 
 #[test]
 fn border_image_shorthand_should_parse_without_outset() {
     let input = "linear-gradient(red, blue) 30 30% 45 fill / 20px 40px round";
     let result = parse(border_image::parse_value, input).unwrap();
 
-    assert_eq!(result.border_image_source,
-               parse_longhand!(border_image_source, "linear-gradient(red, blue)"));
-    assert_eq!(result.border_image_slice, parse_longhand!(border_image_slice, "30 30% 45 fill"));
-    assert_eq!(result.border_image_width, parse_longhand!(border_image_width, "20px 40px"));
-    assert_eq!(result.border_image_repeat, parse_longhand!(border_image_repeat, "round"));
-    assert_eq!(result.border_image_outset, border_image_outset::get_initial_specified_value());
+    assert_longhand!(result, border_image_source, "linear-gradient(red, blue)");
+    assert_longhand!(result, border_image_slice, "30 30% 45 fill");
+    assert_longhand!(result, border_image_width, "20px 40px");
+    assert_longhand!(result, border_image_repeat, "round");
+    assert_initial!(result, border_image_outset);
 }
 
 #[test]
 fn border_image_shorthand_should_parse_without_width_or_outset() {
     let input = "linear-gradient(red, blue) 30 30% 45 fill round";
     let result = parse(border_image::parse_value, input).unwrap();
 
-    assert_eq!(result.border_image_source,
-               parse_longhand!(border_image_source, "linear-gradient(red, blue)"));
-    assert_eq!(result.border_image_slice, parse_longhand!(border_image_slice, "30 30% 45 fill"));
-    assert_eq!(result.border_image_repeat, parse_longhand!(border_image_repeat, "round"));
-    assert_eq!(result.border_image_width, border_image_width::get_initial_specified_value());
-    assert_eq!(result.border_image_outset, border_image_outset::get_initial_specified_value());
+    assert_longhand!(result, border_image_source, "linear-gradient(red, blue)");
+    assert_longhand!(result, border_image_slice, "30 30% 45 fill");
+    assert_longhand!(result, border_image_repeat, "round");
+    assert_initial!(result, border_image_width);
+    assert_initial!(result, border_image_outset);
 }
 
 #[test]
 fn border_image_shorthand_should_parse_with_just_source() {
     let result = parse(border_image::parse_value, "linear-gradient(red, blue)").unwrap();
 
-    assert_eq!(result.border_image_source,
-               parse_longhand!(border_image_source, "linear-gradient(red, blue)"));
-    assert_eq!(result.border_image_slice, border_image_slice::get_initial_specified_value());
-    assert_eq!(result.border_image_width, border_image_width::get_initial_specified_value());
-    assert_eq!(result.border_image_outset, border_image_outset::get_initial_specified_value());
-    assert_eq!(result.border_image_repeat, border_image_repeat::get_initial_specified_value());
+    assert_longhand!(result, border_image_source, "linear-gradient(red, blue)");
+    assert_initial!(result, border_image_slice);
+    assert_initial!(result, border_image_width);
+    assert_initial!(result, border_image_outset);
+    assert_initial!(result, border_image_repeat);
 }
 
 #[test]
 fn border_image_outset_should_error_on_negative_length() {
     let result = parse(border_image_outset::parse, "-1em");
     assert_eq!(result, Err(()));
 }
 
--- a/servo/tests/unit/style/size_of.rs
+++ b/servo/tests/unit/style/size_of.rs
@@ -1,12 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use style::properties;
 
 size_of_test!(test_size_of_property_declaration, properties::PropertyDeclaration, 32);
 
+// This is huge, but we allocate it on the stack and then never move it,
+// we only pass `&mut SourcePropertyDeclaration` references around.
+size_of_test!(test_size_of_parsed_declaration, properties::SourcePropertyDeclaration, 576);
+
 #[test]
 fn size_of_specified_values() {
     ::style::properties::test_size_of_specified_values();
 }
--- a/servo/tests/unit/stylo/size_of.rs
+++ b/servo/tests/unit/stylo/size_of.rs
@@ -16,12 +16,16 @@ fn size_of_selectors_dummy_types() {
     assert_eq!(align_of::<dummies::PseudoElement>(), align_of::<real::PseudoElement>());
 
     assert_eq!(size_of::<dummies::Atom>(), size_of::<style::Atom>());
     assert_eq!(align_of::<dummies::Atom>(), align_of::<style::Atom>());
 }
 
 size_of_test!(test_size_of_property_declaration, style::properties::PropertyDeclaration, 32);
 
+// This is huge, but we allocate it on the stack and then never move it,
+// we only pass `&mut SourcePropertyDeclaration` references around.
+size_of_test!(test_size_of_parsed_declaration, style::properties::SourcePropertyDeclaration, 704);
+
 #[test]
 fn size_of_specified_values() {
     ::style::properties::test_size_of_specified_values();
 }