servo: Merge #16721 - Set vector properties via iterator in stylo (from Manishearth:vector-iter); r=emilio
authorManish Goregaokar <manishsmail@gmail.com>
Thu, 04 May 2017 11:23:05 -0500
changeset 356517 aaaf7747a289f3a587b4b5bcf58639561f1398b0
parent 356516 286184fa338ec0845a3e787806cbe6a2e7815ee8
child 356518 9983a4f7101f04451c21a93b8a573111b72b5bc9
push id31767
push usercbook@mozilla.com
push dateFri, 05 May 2017 13:15:58 +0000
treeherdermozilla-central@8872ad4d52b6 [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 #16721 - Set vector properties via iterator in stylo (from Manishearth:vector-iter); r=emilio https://bugzilla.mozilla.org/show_bug.cgi?id=1360882 Avoids transient allocations Source-Repo: https://github.com/servo/servo Source-Revision: e23c8f1f3cc326782ca025bc67b0e4003b1c5275
servo/components/style/properties/data.py
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/helpers.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/stylearc.rs
servo/components/style/values/computed/mod.rs
--- a/servo/components/style/properties/data.py
+++ b/servo/components/style/properties/data.py
@@ -135,17 +135,17 @@ def arg_to_bool(arg):
 
 
 class Longhand(object):
     def __init__(self, style_struct, name, spec=None, animation_value_type=None, derived_from=None, keyword=None,
                  predefined_type=None, custom_cascade=False, experimental=False, internal=False,
                  need_clone=False, need_index=False, gecko_ffi_name=None, depend_on_viewport_size=False,
                  allowed_in_keyframe_block=True, complex_color=False, cast_type='u8',
                  has_uncacheable_values=False, logical=False, alias=None, extra_prefixes=None, boxed=False,
-                 flags=None, allowed_in_page_rule=False, allow_quirks=False):
+                 flags=None, allowed_in_page_rule=False, allow_quirks=False, vector=False):
         self.name = name
         if not spec:
             raise TypeError("Spec should be specified for %s" % name)
         self.spec = spec
         self.keyword = keyword
         self.predefined_type = predefined_type
         self.ident = to_rust_ident(name)
         self.camel_case = to_camel_case(self.ident)
@@ -162,16 +162,17 @@ class Longhand(object):
         self.cast_type = cast_type
         self.logical = arg_to_bool(logical)
         self.alias = alias.split() if alias else []
         self.extra_prefixes = extra_prefixes.split() if extra_prefixes else []
         self.boxed = arg_to_bool(boxed)
         self.flags = flags.split() if flags else []
         self.allowed_in_page_rule = arg_to_bool(allowed_in_page_rule)
         self.allow_quirks = allow_quirks
+        self.is_vector = vector
 
         # 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.
         self.allowed_in_keyframe_block = allowed_in_keyframe_block \
             and allowed_in_keyframe_block != "False"
 
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -1692,45 +1692,53 @@ fn static_assert() {
     #[allow(non_snake_case)]
     pub fn ${type}_${ident}_count(&self) -> usize {
         self.gecko.m${type.capitalize()}${gecko_ffi_name}Count as usize
     }
 </%def>
 
 <%def name="impl_animation_or_transition_time_value(type, ident, gecko_ffi_name)">
     #[allow(non_snake_case)]
-    pub fn set_${type}_${ident}(&mut self, v: longhands::${type}_${ident}::computed_value::T) {
-        debug_assert!(!v.0.is_empty());
-        let input_len = v.0.len();
+    pub fn set_${type}_${ident}<I>(&mut self, v: I)
+        where I: IntoIterator<Item = longhands::${type}_${ident}::computed_value::single_value::T>,
+              I::IntoIter: ExactSizeIterator + Clone
+    {
+        let v = v.into_iter();
+        debug_assert!(v.len() != 0);
+        let input_len = v.len();
         unsafe { self.gecko.m${type.capitalize()}s.ensure_len(input_len) };
 
         self.gecko.m${type.capitalize()}${gecko_ffi_name}Count = input_len as u32;
-        for (i, gecko) in self.gecko.m${type.capitalize()}s.iter_mut().enumerate() {
-            gecko.m${gecko_ffi_name} = v.0[i % input_len].seconds() * 1000.;
+        for (gecko, servo) in self.gecko.m${type.capitalize()}s.iter_mut().zip(v.cycle()) {
+            gecko.m${gecko_ffi_name} = servo.seconds() * 1000.;
         }
     }
     #[allow(non_snake_case)]
     pub fn ${type}_${ident}_at(&self, index: usize)
         -> longhands::${type}_${ident}::computed_value::SingleComputedValue {
         use values::computed::Time;
         Time::from_seconds(self.gecko.m${type.capitalize()}s[index].m${gecko_ffi_name} / 1000.)
     }
     ${impl_animation_or_transition_count(type, ident, gecko_ffi_name)}
     ${impl_copy_animation_or_transition_value(type, ident, gecko_ffi_name)}
 </%def>
 
 <%def name="impl_animation_or_transition_timing_function(type)">
-    pub fn set_${type}_timing_function(&mut self, v: longhands::${type}_timing_function::computed_value::T) {
-        debug_assert!(!v.0.is_empty());
-        let input_len = v.0.len();
+    pub fn set_${type}_timing_function<I>(&mut self, v: I)
+        where I: IntoIterator<Item = longhands::${type}_timing_function::computed_value::single_value::T>,
+              I::IntoIter: ExactSizeIterator + Clone
+    {
+        let v = v.into_iter();
+        debug_assert!(v.len() != 0);
+        let input_len = v.len();
         unsafe { self.gecko.m${type.capitalize()}s.ensure_len(input_len) };
 
         self.gecko.m${type.capitalize()}TimingFunctionCount = input_len as u32;
-        for (i, gecko) in self.gecko.m${type.capitalize()}s.iter_mut().enumerate() {
-            gecko.mTimingFunction = v.0[i % input_len].into();
+        for (gecko, servo) in self.gecko.m${type.capitalize()}s.iter_mut().zip(v.cycle()) {
+            gecko.mTimingFunction = servo.into();
         }
     }
     ${impl_animation_or_transition_count(type, 'timing_function', 'TimingFunction')}
     ${impl_copy_animation_or_transition_value(type, 'timing_function', 'TimingFunction')}
     pub fn ${type}_timing_function_at(&self, index: usize)
         -> longhands::${type}_timing_function::computed_value::SingleComputedValue {
         self.gecko.m${type.capitalize()}s[index].mTimingFunction.into()
     }
@@ -1761,28 +1769,33 @@ fn static_assert() {
 </%def>
 
 <%def name="impl_animation_timing_function()">
     ${impl_animation_or_transition_timing_function('animation')}
 </%def>
 
 <%def name="impl_animation_keyword(ident, gecko_ffi_name, keyword, cast_type='u8')">
     #[allow(non_snake_case)]
-    pub fn set_animation_${ident}(&mut self, v: longhands::animation_${ident}::computed_value::T) {
+    pub fn set_animation_${ident}<I>(&mut self, v: I)
+        where I: IntoIterator<Item = longhands::animation_${ident}::computed_value::single_value::T>,
+              I::IntoIter: ExactSizeIterator + Clone
+    {
         use properties::longhands::animation_${ident}::single_value::computed_value::T as Keyword;
         use gecko_bindings::structs;
 
-        debug_assert!(!v.0.is_empty());
-        let input_len = v.0.len();
+        let v = v.into_iter();
+
+        debug_assert!(v.len() != 0);
+        let input_len = v.len();
         unsafe { self.gecko.mAnimations.ensure_len(input_len) };
 
         self.gecko.mAnimation${gecko_ffi_name}Count = input_len as u32;
 
-        for (i, gecko) in self.gecko.mAnimations.iter_mut().enumerate() {
-            let result = match v.0[i % input_len] {
+        for (gecko, servo) in self.gecko.mAnimations.iter_mut().zip(v.cycle()) {
+            let result = match servo {
                 % for value in keyword.gecko_values():
                     Keyword::${to_rust_ident(value)} =>
                         structs::${keyword.gecko_constant(value)} ${keyword.maybe_cast(cast_type)},
                 % endfor
             };
             gecko.m${gecko_ffi_name} = result;
         }
     }
@@ -1965,21 +1978,26 @@ fn static_assert() {
         match v.0 {
             None => self.gecko.mScrollSnapPointsY.set_value(CoordDataValue::None),
             Some(l) => l.to_gecko_style_coord(&mut self.gecko.mScrollSnapPointsY),
         };
     }
 
     ${impl_coord_copy('scroll_snap_points_y', 'mScrollSnapPointsY')}
 
-    pub fn set_scroll_snap_coordinate(&mut self, v: longhands::scroll_snap_coordinate::computed_value::T) {
-        unsafe { self.gecko.mScrollSnapCoordinate.set_len_pod(v.0.len() as u32); }
+    pub fn set_scroll_snap_coordinate<I>(&mut self, v: I)
+        where I: IntoIterator<Item = longhands::scroll_snap_coordinate::computed_value::single_value::T>,
+              I::IntoIter: ExactSizeIterator
+    {
+        let v = v.into_iter();
+
+        unsafe { self.gecko.mScrollSnapCoordinate.set_len_pod(v.len() as u32); }
         for (gecko, servo) in self.gecko.mScrollSnapCoordinate
                                .iter_mut()
-                               .zip(v.0.iter()) {
+                               .zip(v) {
             gecko.mXPosition = servo.horizontal.0.into();
             gecko.mYPosition = servo.vertical.0.into();
         }
     }
 
     pub fn copy_scroll_snap_coordinate_from(&mut self, other: &Self) {
         unsafe {
             self.gecko.mScrollSnapCoordinate
@@ -2160,23 +2178,28 @@ fn static_assert() {
     ${impl_transition_time_value('duration', 'Duration')}
     ${impl_transition_timing_function()}
 
     pub fn transition_combined_duration_at(&self, index: usize) -> f32 {
         // https://drafts.csswg.org/css-transitions/#transition-combined-duration
         self.gecko.mTransitions[index].mDuration.max(0.0) + self.gecko.mTransitions[index].mDelay
     }
 
-    pub fn set_transition_property(&mut self, v: longhands::transition_property::computed_value::T) {
+    pub fn set_transition_property<I>(&mut self, v: I)
+        where I: IntoIterator<Item = longhands::transition_property::computed_value::single_value::T>,
+              I::IntoIter: ExactSizeIterator
+    {
         use gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties;
 
-        if !v.0.is_empty() {
-            unsafe { self.gecko.mTransitions.ensure_len(v.0.len()) };
-            self.gecko.mTransitionPropertyCount = v.0.len() as u32;
-            for (servo, gecko) in v.0.into_iter().zip(self.gecko.mTransitions.iter_mut()) {
+        let v = v.into_iter();
+
+        if v.len() != 0 {
+            unsafe { self.gecko.mTransitions.ensure_len(v.len()) };
+            self.gecko.mTransitionPropertyCount = v.len() as u32;
+            for (servo, gecko) in v.zip(self.gecko.mTransitions.iter_mut()) {
                 match servo {
                     TransitionProperty::Unsupported(ref atom) => unsafe {
                         Gecko_StyleTransition_SetUnsupportedProperty(gecko, atom.as_ptr())
                     },
                     _ => gecko.mProperty = (&servo).into(),
                 }
             }
         } else {
@@ -2241,22 +2264,27 @@ fn static_assert() {
         }
     }
     ${impl_transition_count('property', 'Property')}
 
     pub fn animations_equals(&self, other: &Self) -> bool {
         unsafe { bindings::Gecko_StyleAnimationsEquals(&self.gecko.mAnimations, &other.gecko.mAnimations) }
     }
 
-    pub fn set_animation_name(&mut self, v: longhands::animation_name::computed_value::T) {
-        debug_assert!(!v.0.is_empty());
-        unsafe { self.gecko.mAnimations.ensure_len(v.0.len()) };
-
-        self.gecko.mAnimationNameCount = v.0.len() as u32;
-        for (servo, gecko) in v.0.into_iter().zip(self.gecko.mAnimations.iter_mut()) {
+    pub fn set_animation_name<I>(&mut self, v: I)
+        where I: IntoIterator<Item = longhands::animation_name::computed_value::single_value::T>,
+              I::IntoIter: ExactSizeIterator
+    {
+
+        let v = v.into_iter();
+        debug_assert!(v.len() != 0);
+        unsafe { self.gecko.mAnimations.ensure_len(v.len()) };
+
+        self.gecko.mAnimationNameCount = v.len() as u32;
+        for (servo, gecko) in v.zip(self.gecko.mAnimations.iter_mut()) {
             // TODO This is inefficient. We should fix this in bug 1329169.
             gecko.mName.assign(match servo.0 {
                 Some(ref name) => name.as_atom().as_slice(),
                 None => &[],  // Empty string for 'none'
             });
         }
     }
     pub fn animation_name_at(&self, index: usize)
@@ -2289,27 +2317,32 @@ fn static_assert() {
 
     ${impl_animation_keyword('direction', 'Direction',
                              data.longhands_by_name["animation-direction"].keyword)}
     ${impl_animation_keyword('fill_mode', 'FillMode',
                              data.longhands_by_name["animation-fill-mode"].keyword)}
     ${impl_animation_keyword('play_state', 'PlayState',
                              data.longhands_by_name["animation-play-state"].keyword)}
 
-    pub fn set_animation_iteration_count(&mut self, v: longhands::animation_iteration_count::computed_value::T) {
+    pub fn set_animation_iteration_count<I>(&mut self, v: I)
+        where I: IntoIterator<Item = longhands::animation_iteration_count::computed_value::single_value::T>,
+              I::IntoIter: ExactSizeIterator + Clone
+    {
         use std::f32;
         use properties::longhands::animation_iteration_count::single_value::SpecifiedValue as AnimationIterationCount;
 
-        debug_assert!(!v.0.is_empty());
-        let input_len = v.0.len();
+        let v = v.into_iter();
+
+        debug_assert!(v.len() != 0);
+        let input_len = v.len();
         unsafe { self.gecko.mAnimations.ensure_len(input_len) };
 
         self.gecko.mAnimationIterationCountCount = input_len as u32;
-        for (i, gecko) in self.gecko.mAnimations.iter_mut().enumerate() {
-            match v.0[i % input_len] {
+        for (gecko, servo) in self.gecko.mAnimations.iter_mut().zip(v.cycle()) {
+            match servo {
                 AnimationIterationCount::Number(n) => gecko.mIterationCount = n,
                 AnimationIterationCount::Infinite => gecko.mIterationCount = f32::INFINITY,
             }
         }
     }
     pub fn animation_iteration_count_at(&self, index: usize)
         -> longhands::animation_iteration_count::computed_value::SingleComputedValue {
         use properties::longhands::animation_iteration_count::single_value::computed_value::T
@@ -2553,28 +2586,31 @@ fn static_assert() {
                                   .take(other.gecko.${image_layers_field}
                                                    .${field_name}Count as usize) {
             layer.${field_name} = other.${field_name};
         }
         self.gecko.${image_layers_field}.${field_name}Count =
             other.gecko.${image_layers_field}.${field_name}Count;
     }
 
-    pub fn set_${shorthand}_${name}(&mut self,
-                                    v: longhands::${shorthand}_${name}::computed_value::T) {
+
+    pub fn set_${shorthand}_${name}<I>(&mut self, v: I)
+        where I: IntoIterator<Item=longhands::${shorthand}_${name}::computed_value::single_value::T>,
+              I::IntoIter: ExactSizeIterator
+    {
         use gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType;
+        let v = v.into_iter();
 
         unsafe {
-          Gecko_EnsureImageLayersLength(&mut self.gecko.${image_layers_field}, v.0.len(),
+          Gecko_EnsureImageLayersLength(&mut self.gecko.${image_layers_field}, v.len(),
                                         LayerType::${shorthand.title()});
         }
 
-        self.gecko.${image_layers_field}.${field_name}Count = v.0.len() as u32;
-        for (servo, geckolayer) in v.0.into_iter()
-                                    .zip(self.gecko.${image_layers_field}.mLayers.iter_mut()) {
+        self.gecko.${image_layers_field}.${field_name}Count = v.len() as u32;
+        for (servo, geckolayer) in v.zip(self.gecko.${image_layers_field}.mLayers.iter_mut()) {
             geckolayer.${field_name} = {
                 ${caller.body()}
             };
         }
     }
 </%def>
 <%def name="impl_common_image_layer_properties(shorthand)">
     <%
@@ -2670,27 +2706,33 @@ fn static_assert() {
         longhands::${shorthand}_position_${orientation[0]}::computed_value::T(
             self.gecko.${image_layers_field}.mLayers.iter()
                 .take(self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count as usize)
                 .map(|position| ${orientation[1]}Position(position.mPosition.m${orientation[0].upper()}Position.into()))
                 .collect()
         )
     }
 
-    pub fn set_${shorthand}_position_${orientation[0]}(&mut self,
-                                     v: longhands::${shorthand}_position_${orientation[0]}::computed_value::T) {
+    pub fn set_${shorthand}_position_${orientation[0]}<I>(&mut self,
+                                     v: I)
+        where I: IntoIterator<Item = longhands::${shorthand}_position_${orientation[0]}
+                                              ::computed_value::single_value::T>,
+              I::IntoIter: ExactSizeIterator
+    {
         use gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType;
 
+        let v = v.into_iter();
+
         unsafe {
-            Gecko_EnsureImageLayersLength(&mut self.gecko.${image_layers_field}, v.0.len(),
+            Gecko_EnsureImageLayersLength(&mut self.gecko.${image_layers_field}, v.len(),
                                         LayerType::${shorthand.capitalize()});
         }
 
-        self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count = v.0.len() as u32;
-        for (servo, geckolayer) in v.0.into_iter().zip(self.gecko.${image_layers_field}
+        self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count = v.len() as u32;
+        for (servo, geckolayer) in v.zip(self.gecko.${image_layers_field}
                                                            .mLayers.iter_mut()) {
             geckolayer.mPosition.m${orientation[0].upper()}Position = servo.0.into();
         }
     }
     % endfor
 
     <%self:simple_image_array_property name="size" shorthand="${shorthand}" field_name="mSize">
         use gecko_bindings::structs::nsStyleImageLayers_Size_Dimension;
@@ -2767,35 +2809,38 @@ fn static_assert() {
     pub fn copy_${shorthand}_image_from(&mut self, other: &Self) {
         unsafe {
             Gecko_CopyImageValueFrom(&mut self.gecko.${image_layers_field}.mLayers.mFirstElement.mImage,
                                      &other.gecko.${image_layers_field}.mLayers.mFirstElement.mImage);
         }
     }
 
     #[allow(unused_variables)]
-    pub fn set_${shorthand}_image(&mut self,
-                                  images: longhands::${shorthand}_image::computed_value::T,
-                                  cacheable: &mut bool) {
+    pub fn set_${shorthand}_image<I>(&mut self, images: I, cacheable: &mut bool)
+        where I: IntoIterator<Item = longhands::${shorthand}_image::computed_value::single_value::T>,
+              I::IntoIter: ExactSizeIterator
+    {
         use gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType;
 
+        let images = images.into_iter();
+
         unsafe {
             // Prevent leaking of the last elements we did set
             for image in &mut self.gecko.${image_layers_field}.mLayers {
                 Gecko_SetNullImageValue(&mut image.mImage)
             }
             // XXXManishearth clear mSourceURI for masks
-            Gecko_EnsureImageLayersLength(&mut self.gecko.${image_layers_field}, images.0.len(),
+            Gecko_EnsureImageLayersLength(&mut self.gecko.${image_layers_field}, images.len(),
                                           LayerType::${shorthand.title()});
         }
 
-        self.gecko.${image_layers_field}.mImageCount = images.0.len() as u32;
-
-        for (image, geckoimage) in images.0.into_iter().zip(self.gecko.${image_layers_field}
-                                                                .mLayers.iter_mut()) {
+        self.gecko.${image_layers_field}.mImageCount = images.len() as u32;
+
+        for (image, geckoimage) in images.zip(self.gecko.${image_layers_field}
+                                                  .mLayers.iter_mut()) {
             if let Some(image) = image.0 {
                 geckoimage.mImage.set(image, cacheable)
             }
         }
     }
 
     <%
         fill_fields = "mRepeat mClip mOrigin mPositionX mPositionY mImage mSize"
@@ -2981,22 +3026,25 @@ fn static_assert() {
         self.gecko.mSpan = v.0
     }
 
     ${impl_simple_copy('_x_span', 'mSpan')}
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="Effects"
                   skip_longhands="box-shadow clip filter">
-    pub fn set_box_shadow(&mut self, v: longhands::box_shadow::computed_value::T) {
-
-        self.gecko.mBoxShadow.replace_with_new(v.0.len() as u32);
-
-        for (servo, gecko_shadow) in v.0.into_iter()
-                                      .zip(self.gecko.mBoxShadow.iter_mut()) {
+    pub fn set_box_shadow<I>(&mut self, v: I)
+        where I: IntoIterator<Item = longhands::box_shadow::computed_value::single_value::T>,
+              I::IntoIter: ExactSizeIterator
+    {
+        let v = v.into_iter();
+
+        self.gecko.mBoxShadow.replace_with_new(v.len() as u32);
+
+        for (servo, gecko_shadow) in v.zip(self.gecko.mBoxShadow.iter_mut()) {
 
             gecko_shadow.mXOffset = servo.offset_x.0;
             gecko_shadow.mYOffset = servo.offset_y.0;
             gecko_shadow.mRadius = servo.blur_radius.0;
             gecko_shadow.mSpread = servo.spread_radius.0;
             gecko_shadow.mSpread = servo.spread_radius.0;
             gecko_shadow.mInset = servo.inset;
             gecko_shadow.mColor = match servo.color {
@@ -3785,22 +3833,26 @@ clip-path
             }
 
             self.gecko.mPaintOrder = order;
         }
     }
 
     ${impl_simple_copy('paint_order', 'mPaintOrder')}
 
-    pub fn set_stroke_dasharray(&mut self, v: longhands::stroke_dasharray::computed_value::T) {
+    pub fn set_stroke_dasharray<I>(&mut self, v: I)
+        where I: IntoIterator<Item = longhands::stroke_dasharray::computed_value::single_value::T>,
+              I::IntoIter: ExactSizeIterator
+    {
+        let v = v.into_iter();
         unsafe {
-            bindings::Gecko_nsStyleSVG_SetDashArrayLength(&mut self.gecko, v.0.len() as u32);
+            bindings::Gecko_nsStyleSVG_SetDashArrayLength(&mut self.gecko, v.len() as u32);
         }
 
-        for (mut gecko, servo) in self.gecko.mStrokeDasharray.iter_mut().zip(v.0.into_iter()) {
+        for (mut gecko, servo) in self.gecko.mStrokeDasharray.iter_mut().zip(v) {
             match servo {
                 Either::First(number) => gecko.set_value(CoordDataValue::Factor(number)),
                 Either::Second(lop) => gecko.set(lop),
             }
         }
     }
 
     pub fn copy_stroke_dasharray_from(&mut self, other: &Self) {
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -69,17 +69,17 @@
 
     Setting allow_empty to False allows for cases where the vector
     is empty. The grammar for these is usually "none | <thing> [ , <thing> ]*".
     We assume that the default/initial value is an empty vector for these.
     `initial_value` need not be defined for these.
 </%doc>
 <%def name="vector_longhand(name, gecko_only=False, allow_empty=False,
             delegate_animate=False, space_separated_allowed=False, **kwargs)">
-    <%call expr="longhand(name, **kwargs)">
+    <%call expr="longhand(name, vector=True, **kwargs)">
         % if not gecko_only:
             use smallvec::SmallVec;
             use std::fmt;
             use values::HasViewportPercentage;
             use style_traits::ToCss;
 
             impl HasViewportPercentage for SpecifiedValue {
                 fn has_viewport_percentage(&self) -> bool {
@@ -97,17 +97,19 @@
                 use values::{Auto, Either, None_, Normal};
                 ${caller.body()}
             }
 
             /// The definition of the computed value for ${name}.
             pub mod computed_value {
                 pub use super::single_value::computed_value as single_value;
                 pub use self::single_value::T as SingleComputedValue;
-                use smallvec::SmallVec;
+                use smallvec::{IntoIter, SmallVec};
+                use values::computed::ComputedVecIter;
+
                 /// The computed value, effectively a list of single values.
                 #[derive(Debug, Clone, PartialEq)]
                 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
                 pub struct T(pub SmallVec<[single_value::T; 1]>);
 
                 % if delegate_animate:
                     use properties::animated_properties::Interpolate;
                     impl Interpolate for T {
@@ -124,16 +126,26 @@
                         }
 
                         #[inline]
                         fn compute_squared_distance(&self, other: &Self) -> Result<f64, ()> {
                             self.0.compute_squared_distance(&other.0)
                         }
                     }
                 % endif
+
+                pub type Iter<'a, 'cx, 'cx_a> = ComputedVecIter<'a, 'cx, 'cx_a, super::single_value::SpecifiedValue>;
+
+                impl IntoIterator for T {
+                    type Item = single_value::T;
+                    type IntoIter = IntoIter<[single_value::T; 1]>;
+                    fn into_iter(self) -> Self::IntoIter {
+                        self.0.into_iter()
+                    }
+                }
             }
 
             impl ToCss for computed_value::T {
                 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
                     where W: fmt::Write,
                 {
                     let mut iter = self.0.iter();
                     if let Some(val) = iter.next() {
@@ -207,22 +219,29 @@
 
                 ${parse_func}(input, |parser| {
                     single_value::parse(context, parser)
                 }).map(SpecifiedValue)
             }
 
             pub use self::single_value::SpecifiedValue as SingleSpecifiedValue;
 
+            impl SpecifiedValue {
+                pub fn compute_iter<'a, 'cx, 'cx_a>(&'a self, context: &'cx Context<'cx_a>)
+                    -> computed_value::Iter<'a, 'cx, 'cx_a> {
+                    computed_value::Iter::new(context, &self.0)
+                }
+            }
+
             impl ToComputedValue for SpecifiedValue {
                 type ComputedValue = computed_value::T;
 
                 #[inline]
                 fn to_computed_value(&self, context: &Context) -> computed_value::T {
-                    computed_value::T(self.0.iter().map(|x| x.to_computed_value(context)).collect())
+                    computed_value::T(self.compute_iter(context).collect())
                 }
                 #[inline]
                 fn from_computed_value(computed: &computed_value::T) -> Self {
                     SpecifiedValue(computed.0.iter()
                                        .map(ToComputedValue::from_computed_value)
                                        .collect())
                 }
             }
@@ -289,37 +308,51 @@
                     |value| {
                         if let Some(ref mut cascade_info) = *cascade_info {
                             cascade_info.on_cascade_property(&declaration,
                                                              &value);
                         }
                         % if property.logical:
                             let wm = context.style.writing_mode;
                         % endif
-                        <% maybe_wm = ", wm" if property.logical else "" %>
+                        <%
+                            maybe_wm = ", wm" if property.logical else ""
+                            maybe_cacheable = ", cacheable" if property.has_uncacheable_values == "True" else ""
+                        %>
                         match *value {
                             DeclaredValue::Value(ref specified_value) => {
                                 % if property.ident in SYSTEM_FONT_LONGHANDS and product == "gecko":
                                     if let Some(sf) = specified_value.get_system() {
                                         longhands::system_font::resolve_system_font(sf, context);
                                     }
                                 % endif
-                                let computed = specified_value.to_computed_value(context);
-                                % if property.ident == "font_size":
-                                    longhands::font_size::cascade_specified_font_size(context,
-                                                                                      specified_value,
-                                                                                      computed,
-                                                                                      inherited_style.get_font());
+                                % if property.is_vector:
+                                    // In the case of a vector property we want to pass down
+                                    // an iterator so that this can be computed without allocation
+                                    //
+                                    // However, computing requires a context, but the style struct
+                                    // being mutated is on the context. We temporarily remove it,
+                                    // mutate it, and then put it back. Vector longhands cannot
+                                    // touch their own style struct whilst computing, else this will panic.
+                                    let mut s = context.mutate_style().take_${data.current_style_struct.name_lower}();
+                                    {
+                                        let iter = specified_value.compute_iter(context);
+                                        s.set_${property.ident}(iter ${maybe_cacheable});
+                                    }
+                                    context.mutate_style().put_${data.current_style_struct.name_lower}(s);
                                 % else:
-                                    % if property.has_uncacheable_values == "True":
-                                    context.mutate_style().mutate_${data.current_style_struct.name_lower}()
-                                                          .set_${property.ident}(computed, cacheable ${maybe_wm});
+                                    let computed = specified_value.to_computed_value(context);
+                                     % if property.ident == "font_size":
+                                         longhands::font_size::cascade_specified_font_size(context,
+                                                                                           specified_value,
+                                                                                           computed,
+                                                                                           inherited_style.get_font());
                                     % else:
-                                    context.mutate_style().mutate_${data.current_style_struct.name_lower}()
-                                                          .set_${property.ident}(computed ${maybe_wm});
+                                        context.mutate_style().mutate_${data.current_style_struct.name_lower}()
+                                               .set_${property.ident}(computed ${maybe_cacheable} ${maybe_wm});
                                     % endif
                                 % endif
                             }
                             DeclaredValue::WithVariables(_) => unreachable!(),
                             DeclaredValue::CSSWideKeyword(keyword) => match keyword {
                                 % if not data.current_style_struct.inherited:
                                 CSSWideKeyword::Unset |
                                 % endif
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -9,17 +9,17 @@
 // 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::ops::Deref;
-use stylearc::Arc;
+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;
 use computed_values;
 use context::QuirksMode;
@@ -1504,22 +1504,36 @@ pub mod style_structs {
         }
         % endif
 
         impl ${style_struct.name} {
             % for longhand in style_struct.longhands:
                 % if longhand.logical:
                     ${helpers.logical_setter(name=longhand.name)}
                 % else:
-                    /// Set ${longhand.name}.
-                    #[allow(non_snake_case)]
-                    #[inline]
-                    pub fn set_${longhand.ident}(&mut self, v: longhands::${longhand.ident}::computed_value::T) {
-                        self.${longhand.ident} = v;
-                    }
+                    % if longhand.is_vector:
+                        /// Set ${longhand.name}.
+                        #[allow(non_snake_case)]
+                        #[inline]
+                        pub fn set_${longhand.ident}<I>(&mut self, v: I)
+                            where I: IntoIterator<Item = longhands::${longhand.ident}
+                                                                  ::computed_value::single_value::T>,
+                                  I::IntoIter: ExactSizeIterator
+                        {
+                            self.${longhand.ident} = longhands::${longhand.ident}::computed_value
+                                                              ::T(v.into_iter().collect());
+                        }
+                    % else:
+                        /// Set ${longhand.name}.
+                        #[allow(non_snake_case)]
+                        #[inline]
+                        pub fn set_${longhand.ident}(&mut self, v: longhands::${longhand.ident}::computed_value::T) {
+                            self.${longhand.ident} = v;
+                        }
+                    % endif
                     /// Set ${longhand.name} from other struct.
                     #[allow(non_snake_case)]
                     #[inline]
                     pub fn copy_${longhand.ident}_from(&mut self, other: &Self) {
                         self.${longhand.ident} = other.${longhand.ident}.clone();
                     }
                     % if longhand.need_clone:
                         /// Get the computed value for ${longhand.name}.
@@ -2052,61 +2066,89 @@ pub fn get_writing_mode(inheritedbox_sty
     flags
 }
 
 /// A reference to a style struct of the parent, or our own style struct.
 pub enum StyleStructRef<'a, T: 'a> {
     /// A borrowed struct from the parent, for example, for inheriting style.
     Borrowed(&'a Arc<T>),
     /// An owned struct, that we've already mutated.
-    Owned(Arc<T>),
+    Owned(UniqueArc<T>),
+    /// Temporarily vacated, will panic if accessed
+    Vacated,
 }
 
 impl<'a, T: 'a> StyleStructRef<'a, T>
     where T: Clone,
 {
     /// Ensure a mutable reference of this value exists, either cloning the
     /// borrowed value, or returning the owned one.
     pub fn mutate(&mut self) -> &mut T {
         if let StyleStructRef::Borrowed(v) = *self {
-            *self = StyleStructRef::Owned(Arc::new((**v).clone()));
+            *self = StyleStructRef::Owned(UniqueArc::new((**v).clone()));
         }
 
         match *self {
-            StyleStructRef::Owned(ref mut v) => Arc::get_mut(v).unwrap(),
+            StyleStructRef::Owned(ref mut v) => v,
             StyleStructRef::Borrowed(..) => unreachable!(),
+            StyleStructRef::Vacated => panic!("Accessed vacated style struct")
         }
     }
 
+    /// Extract a unique Arc from this struct, vacating it.
+    ///
+    /// The vacated state is a transient one, please put the Arc back
+    /// when done via `put()`. This function is to be used to separate
+    /// the struct being mutated from the computed context
+    pub fn take(&mut self) -> UniqueArc<T> {
+        use std::mem::replace;
+        let inner = replace(self, StyleStructRef::Vacated);
+
+        match inner {
+            StyleStructRef::Owned(arc) => arc,
+            StyleStructRef::Borrowed(arc) => UniqueArc::new((**arc).clone()),
+            StyleStructRef::Vacated => panic!("Accessed vacated style struct"),
+        }
+    }
+
+    /// Replace vacated ref with an arc
+    pub fn put(&mut self, arc: UniqueArc<T>) {
+        debug_assert!(matches!(*self, StyleStructRef::Vacated));
+        *self = StyleStructRef::Owned(arc);
+    }
+
     /// Get a mutable reference to the owned struct, or `None` if the struct
     /// hasn't been mutated.
     pub fn get_if_mutated(&mut self) -> Option<<&mut T> {
         match *self {
-            StyleStructRef::Owned(ref mut v) => Some(Arc::get_mut(v).unwrap()),
+            StyleStructRef::Owned(ref mut v) => Some(v),
             StyleStructRef::Borrowed(..) => None,
+            StyleStructRef::Vacated => panic!("Accessed vacated style struct")
         }
     }
 
     /// Returns an `Arc` to the internal struct, constructing one if
     /// appropriate.
     pub fn build(self) -> Arc<T> {
         match self {
-            StyleStructRef::Owned(v) => v,
+            StyleStructRef::Owned(v) => v.shareable(),
             StyleStructRef::Borrowed(v) => v.clone(),
+            StyleStructRef::Vacated => panic!("Accessed vacated style struct")
         }
     }
 }
 
 impl<'a, T: 'a> Deref for StyleStructRef<'a, T> {
     type Target = T;
 
     fn deref(&self) -> &T {
         match *self {
             StyleStructRef::Owned(ref v) => &**v,
             StyleStructRef::Borrowed(v) => &**v,
+            StyleStructRef::Vacated => panic!("Accessed vacated style struct")
         }
     }
 }
 
 /// A type used to compute a struct with minimal overhead.
 ///
 /// This allows holding references to the parent/default computed values without
 /// actually cloning them, until we either build the style, or mutate the
@@ -2178,16 +2220,26 @@ impl<'a> StyleBuilder<'a> {
             &self.${style_struct.ident}
         }
 
         /// Gets a mutable view of the current `${style_struct.name}` style.
         pub fn mutate_${style_struct.name_lower}(&mut self) -> &mut style_structs::${style_struct.name} {
             self.${style_struct.ident}.mutate()
         }
 
+        /// Gets a mutable view of the current `${style_struct.name}` style.
+        pub fn take_${style_struct.name_lower}(&mut self) -> UniqueArc<style_structs::${style_struct.name}> {
+            self.${style_struct.ident}.take()
+        }
+
+        /// Gets a mutable view of the current `${style_struct.name}` style.
+        pub fn put_${style_struct.name_lower}(&mut self, s: UniqueArc<style_structs::${style_struct.name}>) {
+            self.${style_struct.ident}.put(s)
+        }
+
         /// Gets a mutable view of the current `${style_struct.name}` style,
         /// only if it's been mutated before.
         pub fn get_${style_struct.name_lower}_if_mutated(&mut self)
                                                          -> Option<<&mut style_structs::${style_struct.name}> {
             self.${style_struct.ident}.get_if_mutated()
         }
     % endfor
 
--- a/servo/components/style/stylearc.rs
+++ b/servo/components/style/stylearc.rs
@@ -26,17 +26,17 @@ use heapsize::HeapSizeOf;
 use serde::{Deserialize, Serialize};
 use std::{isize, usize};
 use std::borrow;
 use std::cmp::Ordering;
 use std::convert::From;
 use std::fmt;
 use std::hash::{Hash, Hasher};
 use std::mem;
-use std::ops::Deref;
+use std::ops::{Deref, DerefMut};
 use std::sync::atomic;
 use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
 
 // Private macro to get the offset of a struct field in bytes from the address of the struct.
 macro_rules! offset_of {
     ($container:path, $field:ident) => {{
         // Make sure the field actually exists. This line ensures that a compile-time error is
         // generated if $field is accessed through a Deref impl.
@@ -66,16 +66,50 @@ pub struct Arc<T: ?Sized> {
     //
     // If we need a compact Option<Arc<T>> beforehand, we can make a helper
     // class that wraps the result of Arc::into_raw.
     //
     // https://github.com/rust-lang/rust/issues/27730
     ptr: *mut ArcInner<T>,
 }
 
+/// An Arc that is known to be uniquely owned
+///
+/// This lets us build arcs that we can mutate before
+/// freezing, without needing to change the allocation
+pub struct UniqueArc<T: ?Sized>(Arc<T>);
+
+impl<T> UniqueArc<T> {
+    #[inline]
+    /// Construct a new UniqueArc
+    pub fn new(data: T) -> Self {
+        UniqueArc(Arc::new(data))
+    }
+
+    #[inline]
+    /// Convert to a shareable Arc<T> once we're done using it
+    pub fn shareable(self) -> Arc<T> {
+        self.0
+    }
+}
+
+impl<T> Deref for UniqueArc<T> {
+    type Target = T;
+    fn deref(&self) -> &T {
+        &*self.0
+    }
+}
+
+impl<T> DerefMut for UniqueArc<T> {
+    fn deref_mut(&mut self) -> &mut T {
+        // We know this to be uniquely owned
+        unsafe { &mut (*self.0.ptr).data }
+    }
+}
+
 unsafe impl<T: ?Sized + Sync + Send> Send for Arc<T> {}
 unsafe impl<T: ?Sized + Sync + Send> Sync for Arc<T> {}
 
 struct ArcInner<T: ?Sized> {
     count: atomic::AtomicUsize,
     data: T,
 }
 
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -95,16 +95,52 @@ impl<'a> Context<'a> {
     pub fn inherited_style(&self) -> &ComputedValues { &self.inherited_style }
     /// The current style. Note that only "eager" properties should be accessed
     /// from here, see the comment in the member.
     pub fn style(&self) -> &StyleBuilder { &self.style }
     /// A mutable reference to the current style.
     pub fn mutate_style(&mut self) -> &mut StyleBuilder<'a> { &mut self.style }
 }
 
+/// An iterator over a slice of computed values
+#[derive(Clone)]
+pub struct ComputedVecIter<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> {
+    cx: &'cx Context<'cx_a>,
+    values: &'a [S],
+}
+
+impl<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> ComputedVecIter<'a, 'cx, 'cx_a, S> {
+    /// Construct an iterator from a slice of specified values and a context
+    pub fn new(cx: &'cx Context<'cx_a>, values: &'a [S]) -> Self {
+        ComputedVecIter {
+            cx: cx,
+            values: values,
+        }
+    }
+}
+
+impl<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> ExactSizeIterator for ComputedVecIter<'a, 'cx, 'cx_a, S> {
+    fn len(&self) -> usize {
+        self.values.len()
+    }
+}
+
+impl<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> Iterator for ComputedVecIter<'a, 'cx, 'cx_a, S> {
+    type Item = S::ComputedValue;
+    fn next(&mut self) -> Option<Self::Item> {
+        if let Some((next, rest)) = self.values.split_first() {
+            let ret = next.to_computed_value(self.cx);
+            self.values = rest;
+            Some(ret)
+        } else {
+            None
+        }
+    }
+}
+
 /// A trait to represent the conversion between computed and specified values.
 pub trait ToComputedValue {
     /// The computed value type we're going to be converted to.
     type ComputedValue;
 
     /// Convert a specified value to a computed value, using itself and the data
     /// inside the `Context`.
     #[inline]