servo/components/style/properties/gecko.mako.rs
author Emilio Cobos Álvarez <emilio@crisal.io>
Thu, 16 May 2019 23:21:37 +0000
changeset 474253 7321594dab268ea3f11722178c98d6412f1db078
parent 474097 283b94c196a15367f287fd7f2bb6c9b4bcad0b45
child 474255 6524a34864c39e0957cc10d124d61a6d795b5894
permissions -rw-r--r--
Bug 1549593 - Use OwnedSlice in the specified and computed values of most vector properties. r=heycam This is just a refactor in the right direction. Eventual goal is: * All inherited properties use ArcSlice<>. * All reset properties use OwnedSlice<> (or ThinVec<>). No conversion happens at all, so we can remove all that glue, and also compute_iter and co. Of course there's work to do, but this is a step towards that. Differential Revision: https://phabricator.services.mozilla.com/D30127

/* 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 https://mozilla.org/MPL/2.0/. */

// `data` comes from components/style/properties.mako.rs; see build.rs for more details.

<%!
    from data import to_camel_case, to_camel_case_lower
    from data import Keyword
%>
<%namespace name="helpers" file="/helpers.mako.rs" />

use crate::Atom;
use app_units::Au;
use crate::custom_properties::CustomPropertiesMap;
use crate::gecko_bindings::bindings;
% for style_struct in data.style_structs:
use crate::gecko_bindings::structs::${style_struct.gecko_ffi_name};
use crate::gecko_bindings::bindings::Gecko_Construct_Default_${style_struct.gecko_ffi_name};
use crate::gecko_bindings::bindings::Gecko_CopyConstruct_${style_struct.gecko_ffi_name};
use crate::gecko_bindings::bindings::Gecko_Destroy_${style_struct.gecko_ffi_name};
% endfor
use crate::gecko_bindings::bindings::Gecko_CopyCounterStyle;
use crate::gecko_bindings::bindings::Gecko_CopyCursorArrayFrom;
use crate::gecko_bindings::bindings::Gecko_CopyFontFamilyFrom;
use crate::gecko_bindings::bindings::Gecko_CopyImageValueFrom;
use crate::gecko_bindings::bindings::Gecko_CopyListStyleImageFrom;
use crate::gecko_bindings::bindings::Gecko_EnsureImageLayersLength;
use crate::gecko_bindings::bindings::Gecko_SetCursorArrayLength;
use crate::gecko_bindings::bindings::Gecko_SetCursorImageValue;
use crate::gecko_bindings::bindings::Gecko_NewCSSShadowArray;
use crate::gecko_bindings::bindings::Gecko_nsStyleFont_SetLang;
use crate::gecko_bindings::bindings::Gecko_nsStyleFont_CopyLangFrom;
use crate::gecko_bindings::bindings::Gecko_SetListStyleImageNone;
use crate::gecko_bindings::bindings::Gecko_SetListStyleImageImageValue;
use crate::gecko_bindings::bindings::Gecko_SetNullImageValue;
use crate::gecko_bindings::bindings::{Gecko_ResetFilters, Gecko_CopyFiltersFrom};
use crate::gecko_bindings::structs;
use crate::gecko_bindings::structs::nsCSSPropertyID;
use crate::gecko_bindings::structs::mozilla::PseudoStyleType;
use crate::gecko_bindings::sugar::ns_style_coord::{CoordDataValue, CoordDataMut};
use crate::gecko_bindings::sugar::refptr::RefPtr;
use crate::gecko::values::GeckoStyleCoordConvertible;
use crate::gecko::values::round_border_to_device_pixels;
use crate::logical_geometry::WritingMode;
use crate::media_queries::Device;
use crate::properties::computed_value_flags::*;
use crate::properties::longhands;
use crate::rule_tree::StrongRuleNode;
use crate::selector_parser::PseudoElement;
use servo_arc::{Arc, RawOffsetArc};
use std::marker::PhantomData;
use std::mem::{forget, uninitialized, zeroed, ManuallyDrop};
use std::{cmp, ops, ptr};
use crate::values::{self, CustomIdent, Either, KeyframesName, None_};
use crate::values::computed::{NonNegativeLength, Percentage, TransitionProperty};
use crate::values::computed::BorderStyle;
use crate::values::computed::font::FontSize;
use crate::values::computed::effects::{BoxShadow, Filter, SimpleShadow};
use crate::values::generics::column::ColumnCount;
use crate::values::generics::transform::TransformStyle;
use crate::values::generics::url::UrlOrNone;

pub mod style_structs {
    % for style_struct in data.style_structs:
    pub use super::${style_struct.gecko_struct_name} as ${style_struct.name};

    unsafe impl Send for ${style_struct.name} {}
    unsafe impl Sync for ${style_struct.name} {}
    % endfor

}

/// FIXME(emilio): This is completely duplicated with the other properties code.
pub type ComputedValuesInner = structs::ServoComputedData;

#[repr(C)]
pub struct ComputedValues(structs::mozilla::ComputedStyle);

impl ComputedValues {
    #[inline]
    pub (crate) fn as_gecko_computed_style(&self) -> &structs::ComputedStyle {
        &self.0
    }

    pub fn new(
        pseudo: Option<<&PseudoElement>,
        custom_properties: Option<Arc<CustomPropertiesMap>>,
        writing_mode: WritingMode,
        flags: ComputedValueFlags,
        rules: Option<StrongRuleNode>,
        visited_style: Option<Arc<ComputedValues>>,
        % for style_struct in data.style_structs:
        ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
        % endfor
    ) -> Arc<Self> {
        ComputedValuesInner::new(
            custom_properties,
            writing_mode,
            flags,
            rules,
            visited_style,
            % for style_struct in data.style_structs:
            ${style_struct.ident},
            % endfor
        ).to_outer(pseudo)
    }

    pub fn default_values(doc: &structs::Document) -> Arc<Self> {
        ComputedValuesInner::new(
            /* custom_properties = */ None,
            /* writing_mode = */ WritingMode::empty(), // FIXME(bz): This seems dubious
            ComputedValueFlags::empty(),
            /* rules = */ None,
            /* visited_style = */ None,
            % for style_struct in data.style_structs:
            style_structs::${style_struct.name}::default(doc),
            % endfor
        ).to_outer(None)
    }

    #[inline]
    pub fn pseudo(&self) -> Option<PseudoElement> {
        if self.0.mPseudoType == PseudoStyleType::NotPseudo {
            return None;
        }
        PseudoElement::from_pseudo_type(self.0.mPseudoType)
    }

    #[inline]
    pub fn is_first_line_style(&self) -> bool {
        self.pseudo() == Some(PseudoElement::FirstLine)
    }

    /// Returns true if the display property is changed from 'none' to others.
    pub fn is_display_property_changed_from_none(
        &self,
        old_values: Option<<&ComputedValues>
    ) -> bool {
        use crate::properties::longhands::display::computed_value::T as Display;

        old_values.map_or(false, |old| {
            let old_display_style = old.get_box().clone_display();
            let new_display_style = self.get_box().clone_display();
            old_display_style == Display::None &&
            new_display_style != Display::None
        })
    }

}

impl Drop for ComputedValues {
    fn drop(&mut self) {
        unsafe {
            bindings::Gecko_ComputedStyle_Destroy(&mut self.0);
        }
    }
}

unsafe impl Sync for ComputedValues {}
unsafe impl Send for ComputedValues {}

impl Clone for ComputedValues {
    fn clone(&self) -> Self {
        unreachable!()
    }
}

impl Clone for ComputedValuesInner {
    fn clone(&self) -> Self {
        ComputedValuesInner {
            % for style_struct in data.style_structs:
                ${style_struct.gecko_name}: self.${style_struct.gecko_name}.clone(),
            % endfor
            custom_properties: self.custom_properties.clone(),
            writing_mode: self.writing_mode.clone(),
            flags: self.flags.clone(),
            rules: self.rules.clone(),
            visited_style: self.visited_style.clone(),
        }
    }
}

impl ComputedValuesInner {
    pub fn new(
        custom_properties: Option<Arc<CustomPropertiesMap>>,
        writing_mode: WritingMode,
        flags: ComputedValueFlags,
        rules: Option<StrongRuleNode>,
        visited_style: Option<Arc<ComputedValues>>,
        % for style_struct in data.style_structs:
        ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
        % endfor
    ) -> Self {
        Self {
            custom_properties,
            writing_mode,
            rules,
            visited_style: visited_style.map(Arc::into_raw_offset),
            flags,
            % for style_struct in data.style_structs:
            ${style_struct.gecko_name}: Arc::into_raw_offset(${style_struct.ident}),
            % endfor
        }
    }

    fn to_outer(
        self,
        pseudo: Option<<&PseudoElement>,
    ) -> Arc<ComputedValues> {
        let pseudo_ty = match pseudo {
            Some(p) => p.pseudo_type(),
            None => structs::PseudoStyleType::NotPseudo,
        };
        let arc = unsafe {
            let arc: Arc<ComputedValues> = Arc::new(uninitialized());
            bindings::Gecko_ComputedStyle_Init(
                &arc.0 as *const _ as *mut _,
                &self,
                pseudo_ty,
            );
            // We're simulating a move by having C++ do a memcpy and then forgetting
            // it on this end.
            forget(self);
            arc
        };
        arc
    }
}

impl ops::Deref for ComputedValues {
    type Target = ComputedValuesInner;
    fn deref(&self) -> &ComputedValuesInner {
        &self.0.mSource
    }
}

impl ops::DerefMut for ComputedValues {
    fn deref_mut(&mut self) -> &mut ComputedValuesInner {
        &mut self.0.mSource
    }
}

impl ComputedValuesInner {
    /// Returns true if the value of the `content` property would make a
    /// pseudo-element not rendered.
    #[inline]
    pub fn ineffective_content_property(&self) -> bool {
        self.get_counters().ineffective_content_property()
    }

    % for style_struct in data.style_structs:
    #[inline]
    pub fn clone_${style_struct.name_lower}(&self) -> Arc<style_structs::${style_struct.name}> {
        Arc::from_raw_offset(self.${style_struct.gecko_name}.clone())
    }
    #[inline]
    pub fn get_${style_struct.name_lower}(&self) -> &style_structs::${style_struct.name} {
        &self.${style_struct.gecko_name}
    }


    pub fn ${style_struct.name_lower}_arc(&self) -> &RawOffsetArc<style_structs::${style_struct.name}> {
        &self.${style_struct.gecko_name}
    }

    #[inline]
    pub fn mutate_${style_struct.name_lower}(&mut self) -> &mut style_structs::${style_struct.name} {
        RawOffsetArc::make_mut(&mut self.${style_struct.gecko_name})
    }
    % endfor

    /// Gets the raw visited style. Useful for memory reporting.
    pub fn get_raw_visited_style(&self) -> &Option<RawOffsetArc<ComputedValues>> {
        &self.visited_style
    }

    #[allow(non_snake_case)]
    pub fn has_moz_binding(&self) -> bool {
        !self.get_box().gecko.mBinding.mRawPtr.is_null()
    }
}

<%def name="declare_style_struct(style_struct)">
pub use crate::gecko_bindings::structs::mozilla::Gecko${style_struct.gecko_name} as ${style_struct.gecko_struct_name};
impl ${style_struct.gecko_struct_name} {
    pub fn gecko(&self) -> &${style_struct.gecko_ffi_name} {
        &self.gecko
    }
    pub fn gecko_mut(&mut self) -> &mut ${style_struct.gecko_ffi_name} {
        &mut self.gecko
    }
}
</%def>

<%def name="impl_simple_setter(ident, gecko_ffi_name)">
    #[allow(non_snake_case)]
    pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
        ${set_gecko_property(gecko_ffi_name, "From::from(v)")}
    }
</%def>

<%def name="impl_simple_clone(ident, gecko_ffi_name)">
    #[allow(non_snake_case)]
    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
        From::from(self.gecko.${gecko_ffi_name})
    }
</%def>

<%def name="impl_simple_copy(ident, gecko_ffi_name, *kwargs)">
    #[allow(non_snake_case)]
    pub fn copy_${ident}_from(&mut self, other: &Self) {
        self.gecko.${gecko_ffi_name} = other.gecko.${gecko_ffi_name};
    }

    #[allow(non_snake_case)]
    pub fn reset_${ident}(&mut self, other: &Self) {
        self.copy_${ident}_from(other)
    }
</%def>

<%def name="impl_coord_copy(ident, gecko_ffi_name)">
    #[allow(non_snake_case)]
    pub fn copy_${ident}_from(&mut self, other: &Self) {
        self.gecko.${gecko_ffi_name}.copy_from(&other.gecko.${gecko_ffi_name});
    }

    #[allow(non_snake_case)]
    pub fn reset_${ident}(&mut self, other: &Self) {
        self.copy_${ident}_from(other)
    }
</%def>

<%!
def get_gecko_property(ffi_name, self_param = "self"):
    return "%s.gecko.%s" % (self_param, ffi_name)

def set_gecko_property(ffi_name, expr):
    return "self.gecko.%s = %s;" % (ffi_name, expr)
%>

<%def name="impl_keyword_setter(ident, gecko_ffi_name, keyword, cast_type='u8')">
    #[allow(non_snake_case)]
    pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
        use crate::properties::longhands::${ident}::computed_value::T as Keyword;
        // FIXME(bholley): Align binary representations and ditch |match| for cast + static_asserts
        let result = match v {
            % for value in keyword.values_for('gecko'):
                Keyword::${to_camel_case(value)} =>
                    structs::${keyword.gecko_constant(value)} ${keyword.maybe_cast(cast_type)},
            % endfor
        };
        ${set_gecko_property(gecko_ffi_name, "result")}
    }
</%def>

<%def name="impl_keyword_clone(ident, gecko_ffi_name, keyword, cast_type='u8')">
    // FIXME: We introduced non_upper_case_globals for -moz-appearance only
    //        since the prefix of Gecko value starts with ThemeWidgetType_NS_THEME.
    //        We should remove this after fix bug 1371809.
    #[allow(non_snake_case, non_upper_case_globals)]
    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
        use crate::properties::longhands::${ident}::computed_value::T as Keyword;
        // FIXME(bholley): Align binary representations and ditch |match| for cast + static_asserts

        // Some constant macros in the gecko are defined as negative integer(e.g. font-stretch).
        // And they are convert to signed integer in Rust bindings. We need to cast then
        // as signed type when we have both signed/unsigned integer in order to use them
        // as match's arms.
        // Also, to use same implementation here we use casted constant if we have only singed values.
        % if keyword.gecko_enum_prefix is None:
        % for value in keyword.values_for('gecko'):
        const ${keyword.casted_constant_name(value, cast_type)} : ${cast_type} =
            structs::${keyword.gecko_constant(value)} as ${cast_type};
        % endfor

        match ${get_gecko_property(gecko_ffi_name)} as ${cast_type} {
            % for value in keyword.values_for('gecko'):
            ${keyword.casted_constant_name(value, cast_type)} => Keyword::${to_camel_case(value)},
            % endfor
            % if keyword.gecko_inexhaustive:
            _ => panic!("Found unexpected value in style struct for ${ident} property"),
            % endif
        }
        % else:
        match ${get_gecko_property(gecko_ffi_name)} {
            % for value in keyword.values_for('gecko'):
            structs::${keyword.gecko_constant(value)} => Keyword::${to_camel_case(value)},
            % endfor
            % if keyword.gecko_inexhaustive:
            _ => panic!("Found unexpected value in style struct for ${ident} property"),
            % endif
        }
        % endif
    }
</%def>

<%def name="impl_keyword(ident, gecko_ffi_name, keyword, cast_type='u8', **kwargs)">
<%call expr="impl_keyword_setter(ident, gecko_ffi_name, keyword, cast_type, **kwargs)"></%call>
<%call expr="impl_simple_copy(ident, gecko_ffi_name, **kwargs)"></%call>
<%call expr="impl_keyword_clone(ident, gecko_ffi_name, keyword, cast_type)"></%call>
</%def>

<%def name="impl_simple(ident, gecko_ffi_name)">
<%call expr="impl_simple_setter(ident, gecko_ffi_name)"></%call>
<%call expr="impl_simple_copy(ident, gecko_ffi_name)"></%call>
<%call expr="impl_simple_clone(ident, gecko_ffi_name)"></%call>
</%def>

<%def name="impl_absolute_length(ident, gecko_ffi_name)">
    #[allow(non_snake_case)]
    pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
        ${set_gecko_property(gecko_ffi_name, "v.to_i32_au()")}
    }
    <%call expr="impl_simple_copy(ident, gecko_ffi_name)"></%call>
    #[allow(non_snake_case)]
    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
        Au(self.gecko.${gecko_ffi_name}).into()
    }
</%def>

<%def name="impl_svg_length(ident, gecko_ffi_name)">
    // When context-value is used on an SVG length, the corresponding flag is
    // set on mContextFlags, and the length field is set to the initial value.

    pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
        use crate::values::generics::svg::SVGLength;
        use crate::gecko_bindings::structs::nsStyleSVG_${ident.upper()}_CONTEXT as CONTEXT_VALUE;
        let length = match v {
            SVGLength::LengthPercentage(length) => {
                self.gecko.mContextFlags &= !CONTEXT_VALUE;
                length
            }
            SVGLength::ContextValue => {
                self.gecko.mContextFlags |= CONTEXT_VALUE;
                match longhands::${ident}::get_initial_value() {
                    SVGLength::LengthPercentage(length) => length,
                    _ => unreachable!("Initial value should not be context-value"),
                }
            }
        };
        self.gecko.${gecko_ffi_name} = length;
    }

    pub fn copy_${ident}_from(&mut self, other: &Self) {
        use crate::gecko_bindings::structs::nsStyleSVG_${ident.upper()}_CONTEXT as CONTEXT_VALUE;
        self.gecko.${gecko_ffi_name} = other.gecko.${gecko_ffi_name};
        self.gecko.mContextFlags =
            (self.gecko.mContextFlags & !CONTEXT_VALUE) |
            (other.gecko.mContextFlags & CONTEXT_VALUE);
    }

    pub fn reset_${ident}(&mut self, other: &Self) {
        self.copy_${ident}_from(other)
    }

    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
        use crate::values::generics::svg::SVGLength;
        use crate::gecko_bindings::structs::nsStyleSVG_${ident.upper()}_CONTEXT as CONTEXT_VALUE;
        if (self.gecko.mContextFlags & CONTEXT_VALUE) != 0 {
            return SVGLength::ContextValue;
        }
        SVGLength::LengthPercentage(self.gecko.${gecko_ffi_name})
    }
</%def>

<%def name="impl_svg_opacity(ident, gecko_ffi_name)">
    <% source_prefix = ident.split("_")[0].upper() + "_OPACITY_SOURCE" %>

    pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
        use crate::gecko_bindings::structs::nsStyleSVG_${source_prefix}_MASK as MASK;
        use crate::gecko_bindings::structs::nsStyleSVG_${source_prefix}_SHIFT as SHIFT;
        use crate::gecko_bindings::structs::nsStyleSVGOpacitySource::*;
        use crate::values::generics::svg::SVGOpacity;
        self.gecko.mContextFlags &= !MASK;
        match v {
            SVGOpacity::Opacity(opacity) => {
                self.gecko.mContextFlags |=
                    (eStyleSVGOpacitySource_Normal as u8) << SHIFT;
                self.gecko.${gecko_ffi_name} = opacity;
            }
            SVGOpacity::ContextFillOpacity => {
                self.gecko.mContextFlags |=
                    (eStyleSVGOpacitySource_ContextFillOpacity as u8) << SHIFT;
                self.gecko.${gecko_ffi_name} = 1.;
            }
            SVGOpacity::ContextStrokeOpacity => {
                self.gecko.mContextFlags |=
                    (eStyleSVGOpacitySource_ContextStrokeOpacity as u8) << SHIFT;
                self.gecko.${gecko_ffi_name} = 1.;
            }
        }
    }

    pub fn copy_${ident}_from(&mut self, other: &Self) {
        use crate::gecko_bindings::structs::nsStyleSVG_${source_prefix}_MASK as MASK;
        self.gecko.${gecko_ffi_name} = other.gecko.${gecko_ffi_name};
        self.gecko.mContextFlags =
            (self.gecko.mContextFlags & !MASK) |
            (other.gecko.mContextFlags & MASK);
    }

    pub fn reset_${ident}(&mut self, other: &Self) {
        self.copy_${ident}_from(other)
    }

    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
        use crate::gecko_bindings::structs::nsStyleSVG_${source_prefix}_MASK as MASK;
        use crate::gecko_bindings::structs::nsStyleSVG_${source_prefix}_SHIFT as SHIFT;
        use crate::gecko_bindings::structs::nsStyleSVGOpacitySource::*;
        use crate::values::generics::svg::SVGOpacity;

        let source = (self.gecko.mContextFlags & MASK) >> SHIFT;
        if source == eStyleSVGOpacitySource_Normal as u8 {
            return SVGOpacity::Opacity(self.gecko.${gecko_ffi_name});
        } else {
            debug_assert_eq!(self.gecko.${gecko_ffi_name}, 1.0);
            if source == eStyleSVGOpacitySource_ContextFillOpacity as u8 {
                SVGOpacity::ContextFillOpacity
            } else {
                debug_assert_eq!(source, eStyleSVGOpacitySource_ContextStrokeOpacity as u8);
                SVGOpacity::ContextStrokeOpacity
            }
        }
    }
</%def>

<%def name="impl_svg_paint(ident, gecko_ffi_name)">
    #[allow(non_snake_case)]
    pub fn set_${ident}(&mut self, mut v: longhands::${ident}::computed_value::T) {
        use crate::values::generics::svg::SVGPaintKind;
        use self::structs::nsStyleSVGPaintType;
        use self::structs::nsStyleSVGFallbackType;

        let ref mut paint = ${get_gecko_property(gecko_ffi_name)};
        unsafe {
            bindings::Gecko_nsStyleSVGPaint_Reset(paint);
        }
        let fallback = v.fallback.take();
        match v.kind {
            SVGPaintKind::None => return,
            SVGPaintKind::ContextFill => {
                paint.mType = nsStyleSVGPaintType::eStyleSVGPaintType_ContextFill;
            }
            SVGPaintKind::ContextStroke => {
                paint.mType = nsStyleSVGPaintType::eStyleSVGPaintType_ContextStroke;
            }
            SVGPaintKind::PaintServer(url) => {
                unsafe {
                    bindings::Gecko_nsStyleSVGPaint_SetURLValue(
                        paint,
                        url.url_value_ptr(),
                    )
                }
            }
            SVGPaintKind::Color(color) => {
                paint.mType = nsStyleSVGPaintType::eStyleSVGPaintType_Color;
                unsafe {
                    *paint.mPaint.mColor.as_mut() = color.into();
                }
            }
        }

        paint.mFallbackType = match fallback {
            Some(Either::First(color)) => {
                paint.mFallbackColor = color.into();
                nsStyleSVGFallbackType::eStyleSVGFallbackType_Color
            },
            Some(Either::Second(_)) => {
                nsStyleSVGFallbackType::eStyleSVGFallbackType_None
            },
            None => nsStyleSVGFallbackType::eStyleSVGFallbackType_NotSet
        };
    }

    #[allow(non_snake_case)]
    pub fn copy_${ident}_from(&mut self, other: &Self) {
        unsafe {
            bindings::Gecko_nsStyleSVGPaint_CopyFrom(
                &mut ${get_gecko_property(gecko_ffi_name)},
                & ${get_gecko_property(gecko_ffi_name, "other")}
            );
        }
    }

    #[allow(non_snake_case)]
    pub fn reset_${ident}(&mut self, other: &Self) {
        self.copy_${ident}_from(other)
    }

    #[allow(non_snake_case)]
    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
        use crate::values::computed::url::ComputedUrl;
        use crate::values::generics::svg::{SVGPaint, SVGPaintKind};
        use self::structs::nsStyleSVGPaintType;
        use self::structs::nsStyleSVGFallbackType;
        let ref paint = ${get_gecko_property(gecko_ffi_name)};

        let fallback = match paint.mFallbackType {
            nsStyleSVGFallbackType::eStyleSVGFallbackType_Color => {
                Some(Either::First(paint.mFallbackColor.into()))
            },
            nsStyleSVGFallbackType::eStyleSVGFallbackType_None => {
                Some(Either::Second(None_))
            },
            nsStyleSVGFallbackType::eStyleSVGFallbackType_NotSet => None,
        };

        let kind = match paint.mType {
            nsStyleSVGPaintType::eStyleSVGPaintType_None => SVGPaintKind::None,
            nsStyleSVGPaintType::eStyleSVGPaintType_ContextFill => SVGPaintKind::ContextFill,
            nsStyleSVGPaintType::eStyleSVGPaintType_ContextStroke => SVGPaintKind::ContextStroke,
            nsStyleSVGPaintType::eStyleSVGPaintType_Server => {
                SVGPaintKind::PaintServer(unsafe {
                    let url = RefPtr::new(*paint.mPaint.mPaintServer.as_ref());
                    ComputedUrl::from_url_value(url)
                })
            }
            nsStyleSVGPaintType::eStyleSVGPaintType_Color => {
                let col = unsafe { *paint.mPaint.mColor.as_ref() };
                SVGPaintKind::Color(col.into())
            }
        };
        SVGPaint {
            kind: kind,
            fallback: fallback,
        }
    }
</%def>

<%def name="impl_non_negative_length(ident, gecko_ffi_name, inherit_from=None,
                                     round_to_pixels=False)">
    #[allow(non_snake_case)]
    pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
        let value = {
            % if round_to_pixels:
            let au_per_device_px = Au(self.gecko.mTwipsPerPixel);
            round_border_to_device_pixels(Au::from(v), au_per_device_px).0
            % else:
            v.0.to_i32_au()
            % endif
        };

        % if inherit_from:
        self.gecko.${inherit_from} = value;
        % endif
        self.gecko.${gecko_ffi_name} = value;
    }

    #[allow(non_snake_case)]
    pub fn copy_${ident}_from(&mut self, other: &Self) {
        % if inherit_from:
        self.gecko.${inherit_from} = other.gecko.${inherit_from};
        // NOTE: This is needed to easily handle the `unset` and `initial`
        // keywords, which are implemented calling this function.
        //
        // In practice, this means that we may have an incorrect value here, but
        // we'll adjust that properly in the style fixup phase.
        //
        // FIXME(emilio): We could clean this up a bit special-casing the reset_
        // function below.
        self.gecko.${gecko_ffi_name} = other.gecko.${inherit_from};
        % else:
        self.gecko.${gecko_ffi_name} = other.gecko.${gecko_ffi_name};
        % endif
    }

    #[allow(non_snake_case)]
    pub fn reset_${ident}(&mut self, other: &Self) {
        self.copy_${ident}_from(other)
    }

    #[allow(non_snake_case)]
    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
        Au(self.gecko.${gecko_ffi_name}).into()
    }
</%def>

<%def name="impl_split_style_coord(ident, gecko_ffi_name, index)">
    #[allow(non_snake_case)]
    pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
        self.gecko.${gecko_ffi_name}.${index} = v;
    }
    #[allow(non_snake_case)]
    pub fn copy_${ident}_from(&mut self, other: &Self) {
        self.gecko.${gecko_ffi_name}.${index} =
            other.gecko.${gecko_ffi_name}.${index};
    }
    #[allow(non_snake_case)]
    pub fn reset_${ident}(&mut self, other: &Self) {
        self.copy_${ident}_from(other)
    }

    #[allow(non_snake_case)]
    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
        self.gecko.${gecko_ffi_name}.${index}
    }
</%def>

<%def name="impl_style_coord(ident, gecko_ffi_name)">
    #[allow(non_snake_case)]
    pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
        v.to_gecko_style_coord(&mut self.gecko.${gecko_ffi_name});
    }
    #[allow(non_snake_case)]
    pub fn copy_${ident}_from(&mut self, other: &Self) {
        self.gecko.${gecko_ffi_name}.copy_from(&other.gecko.${gecko_ffi_name});
    }
    #[allow(non_snake_case)]
    pub fn reset_${ident}(&mut self, other: &Self) {
        self.copy_${ident}_from(other)
    }

    #[allow(non_snake_case)]
    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
        use crate::properties::longhands::${ident}::computed_value::T;
        T::from_gecko_style_coord(&self.gecko.${gecko_ffi_name})
            .expect("clone for ${ident} failed")
    }
</%def>

<%def name="impl_style_sides(ident)">
    <% gecko_ffi_name = "m" + to_camel_case(ident) %>

    #[allow(non_snake_case)]
    pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
        v.to_gecko_rect(&mut self.gecko.${gecko_ffi_name});
    }

    <%self:copy_sides_style_coord ident="${ident}"></%self:copy_sides_style_coord>

    #[allow(non_snake_case)]
    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
        longhands::${ident}::computed_value::T::from_gecko_rect(&self.gecko.${gecko_ffi_name})
            .expect("clone for ${ident} failed")
    }
</%def>

<%def name="copy_sides_style_coord(ident)">
    <% gecko_ffi_name = "m" + to_camel_case(ident) %>
    #[allow(non_snake_case)]
    pub fn copy_${ident}_from(&mut self, other: &Self) {
        % for side in SIDES:
            self.gecko.${gecko_ffi_name}.data_at_mut(${side.index})
                .copy_from(&other.gecko.${gecko_ffi_name}.data_at(${side.index}));
        % endfor
        ${ caller.body() }
    }

    #[allow(non_snake_case)]
    pub fn reset_${ident}(&mut self, other: &Self) {
        self.copy_${ident}_from(other)
    }
</%def>

<%def name="impl_corner_style_coord(ident, gecko_ffi_name, corner)">
    #[allow(non_snake_case)]
    pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
        self.gecko.${gecko_ffi_name}.${corner} = v;
    }
    #[allow(non_snake_case)]
    pub fn copy_${ident}_from(&mut self, other: &Self) {
        self.gecko.${gecko_ffi_name}.${corner} =
            other.gecko.${gecko_ffi_name}.${corner};
    }
    #[allow(non_snake_case)]
    pub fn reset_${ident}(&mut self, other: &Self) {
        self.copy_${ident}_from(other)
    }
    #[allow(non_snake_case)]
    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
        self.gecko.${gecko_ffi_name}.${corner}
    }
</%def>

<%def name="impl_css_url(ident, gecko_ffi_name)">
    #[allow(non_snake_case)]
    pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
        match v {
            UrlOrNone::Url(ref url) => {
                self.gecko.${gecko_ffi_name}.set_move(url.clone_url_value())
            }
            UrlOrNone::None => {
                unsafe {
                    self.gecko.${gecko_ffi_name}.clear();
                }
            }
        }
    }
    #[allow(non_snake_case)]
    pub fn copy_${ident}_from(&mut self, other: &Self) {
        unsafe {
            self.gecko.${gecko_ffi_name}.set(&other.gecko.${gecko_ffi_name});
        }
    }
    #[allow(non_snake_case)]
    pub fn reset_${ident}(&mut self, other: &Self) {
        self.copy_${ident}_from(other)
    }

    #[allow(non_snake_case)]
    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
        use crate::values::computed::url::ComputedUrl;

        if self.gecko.${gecko_ffi_name}.mRawPtr.is_null() {
            return UrlOrNone::none()
        }

        UrlOrNone::Url(unsafe {
            ComputedUrl::from_url_value(self.gecko.${gecko_ffi_name}.to_safe())
        })
    }
</%def>

<%
transform_functions = [
    ("Matrix3D", "matrix3d", ["number"] * 16),
    ("Matrix", "matrix", ["number"] * 6),
    ("Translate", "translate", ["lp", "lp"]),
    ("Translate3D", "translate3d", ["lp", "lp", "length"]),
    ("TranslateX", "translatex", ["lp"]),
    ("TranslateY", "translatey", ["lp"]),
    ("TranslateZ", "translatez", ["length"]),
    ("Scale3D", "scale3d", ["number"] * 3),
    ("Scale", "scale", ["number", "number"]),
    ("ScaleX", "scalex", ["number"]),
    ("ScaleY", "scaley", ["number"]),
    ("ScaleZ", "scalez", ["number"]),
    ("Rotate", "rotate", ["angle"]),
    ("Rotate3D", "rotate3d", ["number"] * 3 + ["angle"]),
    ("RotateX", "rotatex", ["angle"]),
    ("RotateY", "rotatey", ["angle"]),
    ("RotateZ", "rotatez", ["angle"]),
    ("Skew", "skew", ["angle", "angle"]),
    ("SkewX", "skewx", ["angle"]),
    ("SkewY", "skewy", ["angle"]),
    ("Perspective", "perspective", ["length"]),
    ("InterpolateMatrix", "interpolatematrix", ["list"] * 2 + ["percentage"]),
    ("AccumulateMatrix", "accumulatematrix", ["list"] * 2 + ["integer_to_percentage"])
]
%>

<%def name="transform_function_arm(name, keyword, items)">
    <%
        pattern = None
        if keyword == "matrix3d":
            # m11: number1, m12: number2, ..
            single_patterns = ["m%s: %s" % (str(a / 4 + 1) + str(a % 4 + 1), b + str(a + 1)) for (a, b)
                                in enumerate(items)]
            pattern = "(Matrix3D { %s })" % ", ".join(single_patterns)
        elif keyword == "matrix":
            # a: number1, b: number2, ..
            single_patterns = ["%s: %s" % (chr(ord('a') + a), b + str(a + 1)) for (a, b)
                                in enumerate(items)]
            pattern = "(Matrix { %s })" % ", ".join(single_patterns)
        elif keyword == "interpolatematrix":
            pattern = " { from_list: ref list1, to_list: ref list2, progress: percentage3 }"
        elif keyword == "accumulatematrix":
            pattern = " { from_list: ref list1, to_list: ref list2, count: integer_to_percentage3 }"
        else:
            # Generate contents of pattern from items
            pattern = "(%s)" % ", ".join([b + str(a+1) for (a,b) in enumerate(items)])

        # First %s substituted with the call to GetArrayItem, the second
        # %s substituted with the corresponding variable
        css_value_setters = {
            "length" : "bindings::Gecko_CSSValue_SetPixelLength(%s, %s.px())",
            "percentage" : "bindings::Gecko_CSSValue_SetPercentage(%s, %s.0)",
            # Note: This is an integer type, but we use it as a percentage value in Gecko, so
            #       need to cast it to f32.
            "integer_to_percentage" : "bindings::Gecko_CSSValue_SetPercentage(%s, %s as f32)",
            "lp" : "%s.set_length_percentage(%s)",
            "angle" : "%s.set_angle(%s)",
            "number" : "bindings::Gecko_CSSValue_SetNumber(%s, %s)",
            # Note: We use nsCSSValueSharedList here, instead of nsCSSValueList_heap
            #       because this function is not called on the main thread and
            #       nsCSSValueList_heap is not thread safe.
            "list" : "%s.set_shared_list(%s.0.iter().map(&convert_to_ns_css_value));",
        }
    %>
    crate::values::generics::transform::TransformOperation::${name}${pattern} => {
        let len = ${len(items) + 1};
        bindings::Gecko_CSSValue_SetFunction(gecko_value, len);
        bindings::Gecko_CSSValue_SetKeyword(
            bindings::Gecko_CSSValue_GetArrayItem(gecko_value, 0),
            structs::nsCSSKeyword::eCSSKeyword_${keyword}
        );
        % for index, item in enumerate(items):
            % if item == "list":
                debug_assert!(!${item}${index + 1}.0.is_empty());
            % endif
            ${css_value_setters[item] % (
                "(&mut *bindings::Gecko_CSSValue_GetArrayItem(gecko_value, %d))" % (index + 1),
                item + str(index + 1)
            )};
        % endfor
    }
</%def>

<%def name="computed_operation_arm(name, keyword, items)">
    <%
        # %s is substituted with the call to GetArrayItem.
        css_value_getters = {
            "length" : "Length::new(bindings::Gecko_CSSValue_GetNumber(%s))",
            "lp" : "%s.get_length_percentage()",
            "angle" : "%s.get_angle()",
            "number" : "bindings::Gecko_CSSValue_GetNumber(%s)",
            "percentage" : "Percentage(bindings::Gecko_CSSValue_GetPercentage(%s))",
            "integer_to_percentage" : "bindings::Gecko_CSSValue_GetPercentage(%s) as i32",
            "list" : "Transform(convert_shared_list_to_operations(%s))",
        }
        pre_symbols = "("
        post_symbols = ")"
        if keyword == "interpolatematrix" or keyword == "accumulatematrix":
            # We generate this like: "TransformOperation::InterpolateMatrix {", so the space is
            # between "InterpolateMatrix"/"AccumulateMatrix" and '{'
            pre_symbols = " {"
            post_symbols = "}"
        elif keyword == "matrix3d":
            pre_symbols = "(Matrix3D {"
            post_symbols = "})"
        elif keyword == "matrix":
            pre_symbols = "(Matrix {"
            post_symbols = "})"
        field_names = None
        if keyword == "interpolatematrix":
            field_names = ["from_list", "to_list", "progress"]
        elif keyword == "accumulatematrix":
            field_names = ["from_list", "to_list", "count"]

    %>
    structs::nsCSSKeyword::eCSSKeyword_${keyword} => {
        crate::values::generics::transform::TransformOperation::${name}${pre_symbols}
        % for index, item in enumerate(items):
            % if keyword == "matrix3d":
                m${index / 4 + 1}${index % 4 + 1}:
            % elif keyword == "matrix":
                ${chr(ord('a') + index)}:
            % elif keyword == "interpolatematrix" or keyword == "accumulatematrix":
                ${field_names[index]}:
            % endif
            <%
                getter = css_value_getters[item] % (
                    "(&*bindings::Gecko_CSSValue_GetArrayItemConst(gecko_value, %d))" % (index + 1)
                )
            %>
            ${getter},
        % endfor
        ${post_symbols}
    },
</%def>

#[allow(unused_parens)]
fn set_single_transform_function(
    servo_value: &values::computed::TransformOperation,
    gecko_value: &mut structs::nsCSSValue /* output */
) {
    use crate::values::computed::TransformOperation;
    use crate::values::generics::transform::{Matrix, Matrix3D};

    let convert_to_ns_css_value = |item: &TransformOperation| -> structs::nsCSSValue {
        let mut value = structs::nsCSSValue::null();
        set_single_transform_function(item, &mut value);
        value
    };

    unsafe {
        match *servo_value {
            % for servo, gecko, format in transform_functions:
                ${transform_function_arm(servo, gecko, format)}
            % endfor
        }
    }
}

pub fn convert_transform(
    input: &[values::computed::TransformOperation],
    output: &mut structs::root::RefPtr<structs::root::nsCSSValueSharedList>
) {
    use crate::gecko_bindings::sugar::refptr::RefPtr;

    unsafe { output.clear() };

    let list = unsafe {
        RefPtr::from_addrefed(bindings::Gecko_NewCSSValueSharedList(input.len() as u32))
    };
    let value_list = unsafe { list.mHead.as_mut() };
    if let Some(value_list) = value_list {
        for (gecko, servo) in value_list.into_iter().zip(input.into_iter()) {
            set_single_transform_function(servo, gecko);
        }
    }
    output.set_move(list);
}

#[allow(unused_parens)]
fn clone_single_transform_function(
    gecko_value: &structs::nsCSSValue
) -> values::computed::TransformOperation {
    use crate::values::computed::{Length, Percentage, TransformOperation};
    use crate::values::generics::transform::{Matrix, Matrix3D};
    use crate::values::generics::transform::Transform;

    let convert_shared_list_to_operations = |value: &structs::nsCSSValue|
                                            -> Vec<TransformOperation> {
        debug_assert_eq!(value.mUnit, structs::nsCSSUnit::eCSSUnit_SharedList);
        let value_list = unsafe {
            value.mValue.mSharedList.as_ref()
                    .as_mut().expect("List pointer should be non-null").mHead.as_ref()
        };
        debug_assert!(value_list.is_some(), "An empty shared list is not allowed");
        value_list.unwrap().into_iter()
                            .map(|item| clone_single_transform_function(item))
                            .collect()
    };

    let transform_function = unsafe {
        bindings::Gecko_CSSValue_GetKeyword(bindings::Gecko_CSSValue_GetArrayItemConst(gecko_value, 0))
    };

    unsafe {
        match transform_function {
            % for servo, gecko, format in transform_functions:
                ${computed_operation_arm(servo, gecko, format)}
            % endfor
            _ => panic!("unacceptable transform function"),
        }
    }
}

pub fn clone_transform_from_list(
    list: Option< &structs::root::nsCSSValueList>
) -> values::computed::Transform {
    use crate::values::generics::transform::Transform;

    let result = match list {
        Some(list) => {
            list.into_iter()
                .filter_map(|value| {
                    // Handle none transform.
                    if value.is_none() {
                        None
                    } else {
                        Some(clone_single_transform_function(value))
                    }
                })
                .collect::<Vec<_>>()
        },
        _ => vec![],
    };
    Transform(result)
}

<%def name="impl_transform(ident, gecko_ffi_name)">
    #[allow(non_snake_case)]
    pub fn set_${ident}(&mut self, other: values::computed::Transform) {
        use crate::gecko_properties::convert_transform;
        if other.0.is_empty() {
            unsafe {
                self.gecko.${gecko_ffi_name}.clear();
            }
            return;
        };
        convert_transform(&other.0, &mut self.gecko.${gecko_ffi_name});
    }

    #[allow(non_snake_case)]
    pub fn copy_${ident}_from(&mut self, other: &Self) {
        unsafe { self.gecko.${gecko_ffi_name}.set(&other.gecko.${gecko_ffi_name}); }
    }

    #[allow(non_snake_case)]
    pub fn reset_${ident}(&mut self, other: &Self) {
        self.copy_${ident}_from(other)
    }

    #[allow(non_snake_case)]
    pub fn clone_${ident}(&self) -> values::computed::Transform {
        use crate::gecko_properties::clone_transform_from_list;
        use crate::values::generics::transform::Transform;

        if self.gecko.${gecko_ffi_name}.mRawPtr.is_null() {
            return Transform(vec!());
        }
        let list = unsafe { (*self.gecko.${gecko_ffi_name}.to_safe().get()).mHead.as_ref() };
        clone_transform_from_list(list)
    }
</%def>

<%def name="impl_logical(name, **kwargs)">
    ${helpers.logical_setter(name)}
</%def>

<%def name="impl_style_struct(style_struct)">
impl ${style_struct.gecko_struct_name} {
    #[allow(dead_code, unused_variables)]
    pub fn default(document: &structs::Document) -> Arc<Self> {
        let mut result = Arc::new(${style_struct.gecko_struct_name} { gecko: ManuallyDrop::new(unsafe { zeroed() }) });
        unsafe {
            Gecko_Construct_Default_${style_struct.gecko_ffi_name}(
                &mut *Arc::get_mut(&mut result).unwrap().gecko,
                document,
            );
        }
        result
    }
}
impl Drop for ${style_struct.gecko_struct_name} {
    fn drop(&mut self) {
        unsafe {
            Gecko_Destroy_${style_struct.gecko_ffi_name}(&mut *self.gecko);
        }
    }
}
impl Clone for ${style_struct.gecko_struct_name} {
    fn clone(&self) -> Self {
        unsafe {
            let mut result = ${style_struct.gecko_struct_name} { gecko: ManuallyDrop::new(zeroed()) };
            Gecko_CopyConstruct_${style_struct.gecko_ffi_name}(&mut *result.gecko, &*self.gecko);
            result
        }
    }
}

</%def>

<%def name="impl_simple_type_with_conversion(ident, gecko_ffi_name=None)">
    <%
    if gecko_ffi_name is None:
        gecko_ffi_name = "m" + to_camel_case(ident)
    %>

    #[allow(non_snake_case)]
    pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
        self.gecko.${gecko_ffi_name} = From::from(v)
    }

    <% impl_simple_copy(ident, gecko_ffi_name) %>

    #[allow(non_snake_case)]
    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
        From::from(self.gecko.${gecko_ffi_name})
    }
</%def>

<%def name="impl_font_settings(ident, gecko_type, tag_type, value_type, gecko_value_type)">
    <%
    gecko_ffi_name = to_camel_case_lower(ident)
    %>

    pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
        let iter = v.0.iter().map(|other| structs::${gecko_type} {
            mTag: other.tag.0,
            mValue: other.value as ${gecko_value_type},
        });
        self.gecko.mFont.${gecko_ffi_name}.assign_from_iter_pod(iter);
    }

    pub fn copy_${ident}_from(&mut self, other: &Self) {
        let iter = other.gecko.mFont.${gecko_ffi_name}.iter().map(|s| *s);
        self.gecko.mFont.${gecko_ffi_name}.assign_from_iter_pod(iter);
    }

    pub fn reset_${ident}(&mut self, other: &Self) {
        self.copy_${ident}_from(other)
    }

    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
        use crate::values::generics::font::{FontSettings, FontTag, ${tag_type}};

        FontSettings(
            self.gecko.mFont.${gecko_ffi_name}.iter().map(|gecko_font_setting| {
                ${tag_type} {
                    tag: FontTag(gecko_font_setting.mTag),
                    value: gecko_font_setting.mValue as ${value_type},
                }
            }).collect::<Vec<_>>().into_boxed_slice()
        )
    }
</%def>

<%def name="impl_trait(style_struct_name, skip_longhands='')">
<%
    style_struct = next(x for x in data.style_structs if x.name == style_struct_name)
    longhands = [x for x in style_struct.longhands
                if not (skip_longhands == "*" or x.name in skip_longhands.split())]

    # Types used with predefined_type()-defined properties that we can auto-generate.
    predefined_types = {
        "MozScriptMinSize": impl_absolute_length,
        "SVGLength": impl_svg_length,
        "SVGOpacity": impl_svg_opacity,
        "SVGPaint": impl_svg_paint,
        "SVGWidth": impl_svg_length,
        "Transform": impl_transform,
        "url::UrlOrNone": impl_css_url,
    }

    def longhand_method(longhand):
        args = dict(ident=longhand.ident, gecko_ffi_name=longhand.gecko_ffi_name)

        # get the method and pass additional keyword or type-specific arguments
        if longhand.logical:
            method = impl_logical
            args.update(name=longhand.name)
        elif longhand.keyword:
            method = impl_keyword
            args.update(keyword=longhand.keyword)
            if "font" in longhand.ident:
                args.update(cast_type=longhand.cast_type)
        elif longhand.predefined_type in predefined_types:
            method = predefined_types[longhand.predefined_type]
        else:
            method = impl_simple

        method(**args)
%>
impl ${style_struct.gecko_struct_name} {
    /*
     * Manually-Implemented Methods.
     */
    ${caller.body().strip()}

    /*
     * Auto-Generated Methods.
     */
    <%
    for longhand in longhands:
        longhand_method(longhand)
    %>
}
</%def>

<%!
class Side(object):
    def __init__(self, name, index):
        self.name = name
        self.ident = name.lower()
        self.index = index

class GridLine(object):
    def __init__(self, name):
        self.ident = "grid-" + name.lower()
        self.name = self.ident.replace('-', '_')
        self.gecko = "m" + to_camel_case(self.ident)

SIDES = [Side("Top", 0), Side("Right", 1), Side("Bottom", 2), Side("Left", 3)]
CORNERS = ["top_left", "top_right", "bottom_right", "bottom_left"]
GRID_LINES = map(GridLine, ["row-start", "row-end", "column-start", "column-end"])
%>

#[allow(dead_code)]
fn static_assert() {
    // Note: using the above technique with an enum hits a rust bug when |structs| is in a different crate.
    % for side in SIDES:
    { const DETAIL: u32 = [0][(structs::Side::eSide${side.name} as usize != ${side.index}) as usize]; let _ = DETAIL; }
    % endfor
}


<% skip_border_longhands = " ".join(["border-{0}-{1}".format(x.ident, y)
                                     for x in SIDES
                                     for y in ["color", "style", "width"]] +
                                    ["border-{0}-radius".format(x.replace("_", "-"))
                                     for x in CORNERS]) %>

<%self:impl_trait style_struct_name="Border"
                  skip_longhands="${skip_border_longhands} border-image-source
                                  border-image-repeat border-image-width">
    % for side in SIDES:
    pub fn set_border_${side.ident}_style(&mut self, v: BorderStyle) {
        self.gecko.mBorderStyle[${side.index}] = v;

        // This is needed because the initial mComputedBorder value is set to
        // zero.
        //
        // In order to compute stuff, we start from the initial struct, and keep
        // going down the tree applying properties.
        //
        // That means, effectively, that when we set border-style to something
        // non-hidden, we should use the initial border instead.
        //
        // Servo stores the initial border-width in the initial struct, and then
        // adjusts as needed in the fixup phase. This means that the initial
        // struct is technically not valid without fixups, and that you lose
        // pretty much any sharing of the initial struct, which is kind of
        // unfortunate.
        //
        // Gecko has two fields for this, one that stores the "specified"
        // border, and other that stores the actual computed one. That means
        // that when we set border-style, border-width may change and we need to
        // sync back to the specified one. This is what this function does.
        //
        // Note that this doesn't impose any dependency in the order of
        // computation of the properties. This is only relevant if border-style
        // is specified, but border-width isn't. If border-width is specified at
        // some point, the two mBorder and mComputedBorder fields would be the
        // same already.
        //
        // Once we're here, we know that we'll run style fixups, so it's fine to
        // just copy the specified border here, we'll adjust it if it's
        // incorrect later.
        self.gecko.mComputedBorder.${side.ident} = self.gecko.mBorder.${side.ident};
    }

    pub fn copy_border_${side.ident}_style_from(&mut self, other: &Self) {
        self.gecko.mBorderStyle[${side.index}] = other.gecko.mBorderStyle[${side.index}];
        self.gecko.mComputedBorder.${side.ident} = self.gecko.mBorder.${side.ident};
    }

    pub fn reset_border_${side.ident}_style(&mut self, other: &Self) {
        self.copy_border_${side.ident}_style_from(other);
    }

    #[inline]
    pub fn clone_border_${side.ident}_style(&self) -> BorderStyle {
        self.gecko.mBorderStyle[${side.index}]
    }

    <% impl_simple("border_%s_color" % side.ident, "mBorder%sColor" % side.name) %>

    <% impl_non_negative_length("border_%s_width" % side.ident,
                                "mComputedBorder.%s" % side.ident,
                                inherit_from="mBorder.%s" % side.ident,
                                round_to_pixels=True) %>

    pub fn border_${side.ident}_has_nonzero_width(&self) -> bool {
        self.gecko.mComputedBorder.${side.ident} != 0
    }
    % endfor

    % for corner in CORNERS:
    <% impl_corner_style_coord("border_%s_radius" % corner,
                               "mBorderRadius",
                               corner) %>
    % endfor

    pub fn set_border_image_source(&mut self, image: longhands::border_image_source::computed_value::T) {
        unsafe {
            // Prevent leaking of the last elements we did set
            Gecko_SetNullImageValue(&mut self.gecko.mBorderImageSource);
        }

        if let Either::Second(image) = image {
            self.gecko.mBorderImageSource.set(image);
        }
    }

    pub fn copy_border_image_source_from(&mut self, other: &Self) {
        unsafe {
            Gecko_CopyImageValueFrom(&mut self.gecko.mBorderImageSource,
                                     &other.gecko.mBorderImageSource);
        }
    }

    pub fn reset_border_image_source(&mut self, other: &Self) {
        self.copy_border_image_source_from(other)
    }

    pub fn clone_border_image_source(&self) -> longhands::border_image_source::computed_value::T {
        use crate::values::None_;

        match unsafe { self.gecko.mBorderImageSource.into_image() } {
            Some(image) => Either::Second(image),
            None => Either::First(None_),
        }
    }

    <%
    border_image_repeat_keywords = ["Stretch", "Repeat", "Round", "Space"]
    %>

    pub fn set_border_image_repeat(&mut self, v: longhands::border_image_repeat::computed_value::T) {
        use crate::values::specified::border::BorderImageRepeatKeyword;
        use crate::gecko_bindings::structs::StyleBorderImageRepeat;

        % for i, side in enumerate(["H", "V"]):
            self.gecko.mBorderImageRepeat${side} = match v.${i} {
                % for keyword in border_image_repeat_keywords:
                BorderImageRepeatKeyword::${keyword} => StyleBorderImageRepeat::${keyword},
                % endfor
            };
        % endfor
    }

    pub fn copy_border_image_repeat_from(&mut self, other: &Self) {
        self.gecko.mBorderImageRepeatH = other.gecko.mBorderImageRepeatH;
        self.gecko.mBorderImageRepeatV = other.gecko.mBorderImageRepeatV;
    }

    pub fn reset_border_image_repeat(&mut self, other: &Self) {
        self.copy_border_image_repeat_from(other)
    }

    pub fn clone_border_image_repeat(&self) -> longhands::border_image_repeat::computed_value::T {
        use crate::values::specified::border::BorderImageRepeatKeyword;
        use crate::gecko_bindings::structs::StyleBorderImageRepeat;

        % for side in ["H", "V"]:
        let servo_${side.lower()} = match self.gecko.mBorderImageRepeat${side} {
            % for keyword in border_image_repeat_keywords:
            StyleBorderImageRepeat::${keyword} => BorderImageRepeatKeyword::${keyword},
            % endfor
        };
        % endfor
        longhands::border_image_repeat::computed_value::T(servo_h, servo_v)
    }

    <% impl_style_sides("border_image_width") %>
</%self:impl_trait>

<% skip_scroll_margin_longhands = " ".join(["scroll-margin-%s" % x.ident for x in SIDES]) %>
<% skip_margin_longhands = " ".join(["margin-%s" % x.ident for x in SIDES]) %>
<%self:impl_trait style_struct_name="Margin"
                  skip_longhands="${skip_margin_longhands}
                                  ${skip_scroll_margin_longhands}">

    % for side in SIDES:
    <% impl_split_style_coord("margin_%s" % side.ident,
                              "mMargin",
                              side.index) %>
    <% impl_split_style_coord("scroll_margin_%s" % side.ident,
                              "mScrollMargin",
                              side.index) %>
    % endfor
</%self:impl_trait>

<% skip_scroll_padding_longhands = " ".join(["scroll-padding-%s" % x.ident for x in SIDES]) %>
<% skip_padding_longhands = " ".join(["padding-%s" % x.ident for x in SIDES]) %>
<%self:impl_trait style_struct_name="Padding"
                  skip_longhands="${skip_padding_longhands}
                                  ${skip_scroll_padding_longhands}">

    % for side in SIDES:
    <% impl_split_style_coord("padding_%s" % side.ident,
                              "mPadding",
                              side.index) %>
    <% impl_split_style_coord("scroll_padding_%s" % side.ident, "mScrollPadding", side.index) %>
    % endfor
</%self:impl_trait>

<% skip_position_longhands = " ".join(x.ident for x in SIDES + GRID_LINES) %>
<%self:impl_trait style_struct_name="Position"
                  skip_longhands="${skip_position_longhands} order
                                  align-content justify-content align-self
                                  justify-self align-items justify-items
                                  grid-auto-rows grid-auto-columns
                                  grid-auto-flow grid-template-areas
                                  grid-template-rows grid-template-columns">
    % for side in SIDES:
    <% impl_split_style_coord(side.ident, "mOffset", side.index) %>
    % endfor

    % for kind in ["align", "justify"]:
    ${impl_simple_type_with_conversion(kind + "_content")}
    ${impl_simple_type_with_conversion(kind + "_self")}
    % endfor
    ${impl_simple_type_with_conversion("align_items")}

    pub fn set_justify_items(&mut self, v: longhands::justify_items::computed_value::T) {
        self.gecko.mSpecifiedJustifyItems = v.specified.into();
        self.set_computed_justify_items(v.computed);
    }

    pub fn set_computed_justify_items(&mut self, v: values::specified::JustifyItems) {
        debug_assert_ne!(v.0, crate::values::specified::align::AlignFlags::LEGACY);
        self.gecko.mJustifyItems = v.into();
    }

    pub fn reset_justify_items(&mut self, reset_style: &Self) {
        self.gecko.mJustifyItems = reset_style.gecko.mJustifyItems;
        self.gecko.mSpecifiedJustifyItems = reset_style.gecko.mSpecifiedJustifyItems;
    }

    pub fn copy_justify_items_from(&mut self, other: &Self) {
        self.gecko.mJustifyItems = other.gecko.mJustifyItems;
        self.gecko.mSpecifiedJustifyItems = other.gecko.mJustifyItems;
    }

    pub fn clone_justify_items(&self) -> longhands::justify_items::computed_value::T {
        longhands::justify_items::computed_value::T {
            computed: self.gecko.mJustifyItems.into(),
            specified: self.gecko.mSpecifiedJustifyItems.into(),
        }
    }

    pub fn set_order(&mut self, v: longhands::order::computed_value::T) {
        self.gecko.mOrder = v;
    }

    pub fn clone_order(&self) -> longhands::order::computed_value::T {
        self.gecko.mOrder
    }

    ${impl_simple_copy('order', 'mOrder')}

    % for value in GRID_LINES:
    pub fn set_${value.name}(&mut self, v: longhands::${value.name}::computed_value::T) {
        use crate::gecko_bindings::structs::{nsStyleGridLine_kMinLine, nsStyleGridLine_kMaxLine};

        let ident = v.ident.as_ref().map_or(&[] as &[_], |ident| ident.0.as_slice());
        self.gecko.${value.gecko}.mLineName.assign(ident);
        self.gecko.${value.gecko}.mHasSpan = v.is_span;
        if let Some(integer) = v.line_num {
            // clamping the integer between a range
            self.gecko.${value.gecko}.mInteger = cmp::max(nsStyleGridLine_kMinLine,
                cmp::min(integer, nsStyleGridLine_kMaxLine));
        }
    }

    pub fn copy_${value.name}_from(&mut self, other: &Self) {
        self.gecko.${value.gecko}.mHasSpan = other.gecko.${value.gecko}.mHasSpan;
        self.gecko.${value.gecko}.mInteger = other.gecko.${value.gecko}.mInteger;
        self.gecko.${value.gecko}.mLineName.assign(&*other.gecko.${value.gecko}.mLineName);
    }

    pub fn reset_${value.name}(&mut self, other: &Self) {
        self.copy_${value.name}_from(other)
    }

    pub fn clone_${value.name}(&self) -> longhands::${value.name}::computed_value::T {
        use crate::gecko_bindings::structs::{nsStyleGridLine_kMinLine, nsStyleGridLine_kMaxLine};

        longhands::${value.name}::computed_value::T {
            is_span: self.gecko.${value.gecko}.mHasSpan,
            ident: {
                let name = self.gecko.${value.gecko}.mLineName.to_string();
                if name.len() == 0 {
                    None
                } else {
                    Some(CustomIdent(Atom::from(name)))
                }
            },
            line_num:
                if self.gecko.${value.gecko}.mInteger == 0 {
                    None
                } else {
                    debug_assert!(nsStyleGridLine_kMinLine <= self.gecko.${value.gecko}.mInteger);
                    debug_assert!(self.gecko.${value.gecko}.mInteger <= nsStyleGridLine_kMaxLine);
                    Some(self.gecko.${value.gecko}.mInteger)
                },
        }
    }
    % endfor

    % for kind in ["rows", "columns"]:
    pub fn set_grid_auto_${kind}(&mut self, v: longhands::grid_auto_${kind}::computed_value::T) {
        let gecko = &mut *self.gecko;
        v.to_gecko_style_coords(&mut gecko.mGridAuto${kind.title()}Min,
                                &mut gecko.mGridAuto${kind.title()}Max)
    }

    pub fn copy_grid_auto_${kind}_from(&mut self, other: &Self) {
        self.gecko.mGridAuto${kind.title()}Min.copy_from(&other.gecko.mGridAuto${kind.title()}Min);
        self.gecko.mGridAuto${kind.title()}Max.copy_from(&other.gecko.mGridAuto${kind.title()}Max);
    }

    pub fn reset_grid_auto_${kind}(&mut self, other: &Self) {
        self.copy_grid_auto_${kind}_from(other)
    }

    pub fn clone_grid_auto_${kind}(&self) -> longhands::grid_auto_${kind}::computed_value::T {
        crate::values::generics::grid::TrackSize::from_gecko_style_coords(&self.gecko.mGridAuto${kind.title()}Min,
                                                                     &self.gecko.mGridAuto${kind.title()}Max)
    }

    pub fn set_grid_template_${kind}(&mut self, v: longhands::grid_template_${kind}::computed_value::T) {
        <% self_grid = "self.gecko.mGridTemplate%s" % kind.title() %>
        use crate::gecko_bindings::structs::{nsTArray, nsStyleGridLine_kMaxLine};
        use nsstring::nsString;
        use std::usize;
        use crate::values::CustomIdent;
        use crate::values::generics::grid::TrackListType::Auto;
        use crate::values::generics::grid::{GridTemplateComponent, RepeatCount};

        #[inline]
        fn set_line_names(servo_names: &[CustomIdent], gecko_names: &mut nsTArray<nsString>) {
            unsafe {
                bindings::Gecko_ResizeTArrayForStrings(gecko_names, servo_names.len() as u32);
            }

            for (servo_name, gecko_name) in servo_names.iter().zip(gecko_names.iter_mut()) {
                gecko_name.assign(servo_name.0.as_slice());
            }
        }

        let max_lines = nsStyleGridLine_kMaxLine as usize - 1;      // for accounting the final <line-names>

        let result = match v {
            GridTemplateComponent::None => ptr::null_mut(),
            GridTemplateComponent::TrackList(track) => {
                let mut num_values = track.values.len();
                if let Auto(_) = track.list_type {
                    num_values += 1;
                }

                num_values = cmp::min(num_values, max_lines);
                let value = unsafe {
                    bindings::Gecko_CreateStyleGridTemplate(num_values as u32,
                                                            (num_values + 1) as u32).as_mut().unwrap()
                };

                let mut auto_idx = usize::MAX;
                let mut auto_track_size = None;
                if let Auto(idx) = track.list_type {
                    auto_idx = idx as usize;
                    let auto_repeat = track.auto_repeat.as_ref().expect("expected <auto-track-repeat> value");

                    if auto_repeat.count == RepeatCount::AutoFill {
                        value.set_mIsAutoFill(true);
                    }

                    value.mRepeatAutoIndex = idx as i16;
                    // NOTE: Gecko supports only one set of values in <auto-repeat>
                    // i.e., it can only take repeat(auto-fill, [a] 10px [b]), and no more.
                    set_line_names(&auto_repeat.line_names[0], &mut value.mRepeatAutoLineNameListBefore);
                    set_line_names(&auto_repeat.line_names[1], &mut value.mRepeatAutoLineNameListAfter);
                    auto_track_size = Some(auto_repeat.track_sizes.get(0).unwrap().clone());
                } else {
                    unsafe {
                        bindings::Gecko_ResizeTArrayForStrings(
                            &mut value.mRepeatAutoLineNameListBefore, 0);
                        bindings::Gecko_ResizeTArrayForStrings(
                            &mut value.mRepeatAutoLineNameListAfter, 0);
                    }
                }

                let mut line_names = track.line_names.into_iter();
                let mut values_iter = track.values.into_iter();
                {
                    let min_max_iter = value.mMinTrackSizingFunctions.iter_mut()
                                            .zip(value.mMaxTrackSizingFunctions.iter_mut());
                    for (i, (gecko_min, gecko_max)) in min_max_iter.enumerate().take(max_lines) {
                        let name_list = line_names.next().expect("expected line-names");
                        set_line_names(&name_list, &mut value.mLineNameLists[i]);
                        if i == auto_idx {
                            let track_size = auto_track_size.take()
                                .expect("expected <track-size> for <auto-track-repeat>");
                            track_size.to_gecko_style_coords(gecko_min, gecko_max);
                            continue
                        }

                        let track_size = values_iter.next().expect("expected <track-size> value");
                        track_size.to_gecko_style_coords(gecko_min, gecko_max);
                    }
                }

                let final_names = line_names.next().unwrap();
                set_line_names(&final_names, value.mLineNameLists.last_mut().unwrap());

                value
            },
            GridTemplateComponent::Subgrid(list) => {
                let names_length = match list.fill_idx {
                    Some(_) => list.names.len() - 1,
                    None => list.names.len(),
                };
                let num_values = cmp::min(names_length, max_lines + 1);
                let value = unsafe {
                    bindings::Gecko_CreateStyleGridTemplate(0, num_values as u32).as_mut().unwrap()
                };
                value.set_mIsSubgrid(true);

                let mut names = list.names.into_vec();
                if let Some(idx) = list.fill_idx {
                    value.set_mIsAutoFill(true);
                    value.mRepeatAutoIndex = idx as i16;
                    set_line_names(&names.swap_remove(idx as usize),
                                   &mut value.mRepeatAutoLineNameListBefore);
                }

                for (servo_names, gecko_names) in names.iter().zip(value.mLineNameLists.iter_mut()) {
                    set_line_names(servo_names, gecko_names);
                }

                value
            },
        };

        unsafe { bindings::Gecko_SetStyleGridTemplate(&mut ${self_grid}, result); }
    }

    pub fn copy_grid_template_${kind}_from(&mut self, other: &Self) {
        unsafe {
            bindings::Gecko_CopyStyleGridTemplateValues(&mut ${self_grid},
                                                        other.gecko.mGridTemplate${kind.title()}.mPtr);
        }
    }

    pub fn reset_grid_template_${kind}(&mut self, other: &Self) {
        self.copy_grid_template_${kind}_from(other)
    }

    pub fn clone_grid_template_${kind}(&self) -> longhands::grid_template_${kind}::computed_value::T {
        <% self_grid = "self.gecko.mGridTemplate%s" % kind.title() %>
        use crate::gecko_bindings::structs::nsTArray;
        use nsstring::nsString;
        use crate::values::CustomIdent;
        use crate::values::generics::grid::{GridTemplateComponent, LineNameList, RepeatCount};
        use crate::values::generics::grid::{TrackList, TrackListType, TrackListValue, TrackRepeat, TrackSize};

        let value = match unsafe { ${self_grid}.mPtr.as_ref() } {
            None => return GridTemplateComponent::None,
            Some(value) => value,
        };

        #[inline]
        fn to_boxed_customident_slice(gecko_names: &nsTArray<nsString>) -> Box<[CustomIdent]> {
            let idents: Vec<CustomIdent> = gecko_names.iter().map(|gecko_name| {
                CustomIdent(Atom::from(gecko_name.to_string()))
            }).collect();
            idents.into_boxed_slice()
        }

        #[inline]
        fn to_line_names_vec(gecko_line_names: &nsTArray<nsTArray<nsString>>)
            -> Vec<Box<[CustomIdent]>> {
            gecko_line_names.iter().map(|gecko_names| {
                to_boxed_customident_slice(gecko_names)
            }).collect()
        }

        let repeat_auto_index = value.mRepeatAutoIndex as usize;
        if value.mIsSubgrid() {
            let mut names_vec = to_line_names_vec(&value.mLineNameLists);
            let fill_idx = if value.mIsAutoFill() {
                names_vec.insert(
                    repeat_auto_index,
                    to_boxed_customident_slice(&value.mRepeatAutoLineNameListBefore));
                Some(repeat_auto_index as u32)
            } else {
                None
            };
            let names = names_vec.into_boxed_slice();

            GridTemplateComponent::Subgrid(LineNameList{names, fill_idx})
        } else {
            let mut auto_repeat = None;
            let mut list_type = TrackListType::Normal;
            let line_names = to_line_names_vec(&value.mLineNameLists).into_boxed_slice();
            let mut values = Vec::with_capacity(value.mMinTrackSizingFunctions.len());

            let min_max_iter = value.mMinTrackSizingFunctions.iter()
                .zip(value.mMaxTrackSizingFunctions.iter());
            for (i, (gecko_min, gecko_max)) in min_max_iter.enumerate() {
                let track_size = TrackSize::from_gecko_style_coords(gecko_min, gecko_max);

                if i == repeat_auto_index {
                    list_type = TrackListType::Auto(repeat_auto_index as u16);

                    let count = if value.mIsAutoFill() {
                        RepeatCount::AutoFill
                    } else {
                        RepeatCount::AutoFit
                    };

                    let line_names = {
                        let mut vec: Vec<Box<[CustomIdent]>> = Vec::with_capacity(2);
                        vec.push(to_boxed_customident_slice(
                            &value.mRepeatAutoLineNameListBefore));
                        vec.push(to_boxed_customident_slice(
                            &value.mRepeatAutoLineNameListAfter));
                        vec.into_boxed_slice()
                    };

                    let track_sizes = vec!(track_size);

                    auto_repeat = Some(TrackRepeat{count, line_names, track_sizes});
                } else {
                    values.push(TrackListValue::TrackSize(track_size));
                }
            }

            GridTemplateComponent::TrackList(TrackList{list_type, values, line_names, auto_repeat})
        }
    }
    % endfor

    ${impl_simple_type_with_conversion("grid_auto_flow")}

    pub fn set_grid_template_areas(&mut self, v: values::computed::position::GridTemplateAreas) {
        use crate::gecko_bindings::bindings::Gecko_NewGridTemplateAreasValue;
        use crate::gecko_bindings::sugar::refptr::UniqueRefPtr;

        let v = match v {
            Either::First(areas) => areas,
            Either::Second(_) => {
                unsafe { self.gecko.mGridTemplateAreas.clear() }
                return;
            },
        };

        let mut refptr = unsafe {
            UniqueRefPtr::from_addrefed(
                Gecko_NewGridTemplateAreasValue(v.0.areas.len() as u32, v.0.strings.len() as u32, v.0.width))
        };

        for (servo, gecko) in v.0.areas.into_iter().zip(refptr.mNamedAreas.iter_mut()) {
            gecko.mName.assign_str(&*servo.name);
            gecko.mColumnStart = servo.columns.start;
            gecko.mColumnEnd = servo.columns.end;
            gecko.mRowStart = servo.rows.start;
            gecko.mRowEnd = servo.rows.end;
        }

        for (servo, gecko) in v.0.strings.into_iter().zip(refptr.mTemplates.iter_mut()) {
            gecko.assign_str(&*servo);
        }

        self.gecko.mGridTemplateAreas.set_move(refptr.get())
    }

    pub fn copy_grid_template_areas_from(&mut self, other: &Self) {
        unsafe { self.gecko.mGridTemplateAreas.set(&other.gecko.mGridTemplateAreas) }
    }

    pub fn reset_grid_template_areas(&mut self, other: &Self) {
        self.copy_grid_template_areas_from(other)
    }

    pub fn clone_grid_template_areas(&self) -> values::computed::position::GridTemplateAreas {
        use std::ops::Range;
        use crate::values::None_;
        use crate::values::specified::position::{NamedArea, TemplateAreas, TemplateAreasArc};

        if self.gecko.mGridTemplateAreas.mRawPtr.is_null() {
            return Either::Second(None_);
        }

        let gecko_grid_template_areas = self.gecko.mGridTemplateAreas.mRawPtr;
        let areas = unsafe {
            let vec: Vec<NamedArea> =
                (*gecko_grid_template_areas).mNamedAreas.iter().map(|gecko_name_area| {
                    let name = gecko_name_area.mName.to_string().into_boxed_str();
                    let rows = Range {
                        start: gecko_name_area.mRowStart,
                        end: gecko_name_area.mRowEnd
                    };
                    let columns = Range {
                        start: gecko_name_area.mColumnStart,
                        end: gecko_name_area.mColumnEnd
                    };
                    NamedArea{ name, rows, columns }
                }).collect();
            vec.into_boxed_slice()
        };

        let strings = unsafe {
            let vec: Vec<Box<str>> =
                (*gecko_grid_template_areas).mTemplates.iter().map(|gecko_template| {
                    gecko_template.to_string().into_boxed_str()
                }).collect();
            vec.into_boxed_slice()
        };

        let width = unsafe {
            (*gecko_grid_template_areas).mNColumns
        };

        Either::First(TemplateAreasArc(Arc::new(TemplateAreas{ areas, strings, width })))
    }

</%self:impl_trait>

<% skip_outline_longhands = " ".join("outline-style outline-width".split() +
                                     ["-moz-outline-radius-{0}".format(x.replace("_", ""))
                                      for x in CORNERS]) %>
<%self:impl_trait style_struct_name="Outline"
                  skip_longhands="${skip_outline_longhands}">

    pub fn set_outline_style(&mut self, v: longhands::outline_style::computed_value::T) {
        self.gecko.mOutlineStyle = v;
        // NB: This is needed to correctly handling the initial value of
        // outline-width when outline-style changes, see the
        // update_border_${side.ident} comment for more details.
        self.gecko.mActualOutlineWidth = self.gecko.mOutlineWidth;
    }

    pub fn copy_outline_style_from(&mut self, other: &Self) {
        // FIXME(emilio): Why doesn't this need to reset mActualOutlineWidth?
        // Looks fishy.
        self.gecko.mOutlineStyle = other.gecko.mOutlineStyle;
    }

    pub fn reset_outline_style(&mut self, other: &Self) {
        self.copy_outline_style_from(other)
    }

    pub fn clone_outline_style(&self) -> longhands::outline_style::computed_value::T {
        self.gecko.mOutlineStyle.clone()
    }

    <% impl_non_negative_length("outline_width", "mActualOutlineWidth",
                                inherit_from="mOutlineWidth",
                                round_to_pixels=True) %>

    % for corner in CORNERS:
    <% impl_corner_style_coord("_moz_outline_radius_%s" % corner.replace("_", ""),
                               "mOutlineRadius",
                               corner) %>
    % endfor

    pub fn outline_has_nonzero_width(&self) -> bool {
        self.gecko.mActualOutlineWidth != 0
    }
</%self:impl_trait>

<%
    skip_font_longhands = """font-family font-size font-size-adjust font-weight
                             font-style font-stretch -moz-script-level
                             font-synthesis -x-lang font-variant-alternates
                             font-variant-east-asian font-variant-ligatures
                             font-variant-numeric font-language-override
                             font-feature-settings font-variation-settings
                             -moz-min-font-size-ratio -x-text-zoom"""
%>
<%self:impl_trait style_struct_name="Font"
    skip_longhands="${skip_font_longhands}">

    // Negative numbers are invalid at parse time, but <integer> is still an
    // i32.
    <% impl_font_settings("font_feature_settings", "gfxFontFeature", "FeatureTagValue", "i32", "u32") %>
    <% impl_font_settings("font_variation_settings", "gfxFontVariation", "VariationValue", "f32", "f32") %>

    pub fn set_font_family(&mut self, v: longhands::font_family::computed_value::T) {
        use crate::values::computed::font::GenericFontFamily;

        let is_system_font = v.is_system_font;
        self.gecko.mFont.systemFont = is_system_font;
        self.gecko.mGenericID = if is_system_font {
            GenericFontFamily::None
        } else {
            v.families.single_generic().unwrap_or(GenericFontFamily::None)
        };
        self.gecko.mFont.fontlist.mFontlist.mBasePtr.set_move(
            v.families.shared_font_list().clone()
        );
        // Fixed-up if needed in Cascade::fixup_font_stuff.
        self.gecko.mFont.fontlist.mDefaultFontType = GenericFontFamily::None;
    }

    pub fn copy_font_family_from(&mut self, other: &Self) {
        unsafe { Gecko_CopyFontFamilyFrom(&mut self.gecko.mFont, &other.gecko.mFont); }
        self.gecko.mGenericID = other.gecko.mGenericID;
        self.gecko.mFont.systemFont = other.gecko.mFont.systemFont;
    }

    pub fn reset_font_family(&mut self, other: &Self) {
        self.copy_font_family_from(other)
    }

    pub fn clone_font_family(&self) -> longhands::font_family::computed_value::T {
        use crate::values::computed::font::{FontFamily, SingleFontFamily, FontFamilyList};

        let fontlist = &self.gecko.mFont.fontlist;
        let shared_fontlist = unsafe { fontlist.mFontlist.mBasePtr.to_safe() };

        let families = if shared_fontlist.mNames.is_empty() {
            let default = SingleFontFamily::Generic(fontlist.mDefaultFontType);
            FontFamilyList::new(Box::new([default]))
        } else {
            FontFamilyList::SharedFontList(shared_fontlist)
        };

        FontFamily {
            families,
            is_system_font: self.gecko.mFont.systemFont,
        }
    }

    pub fn unzoom_fonts(&mut self, device: &Device) {
        self.gecko.mSize = device.unzoom_text(Au(self.gecko.mSize)).0;
        self.gecko.mScriptUnconstrainedSize = device.unzoom_text(Au(self.gecko.mScriptUnconstrainedSize)).0;
        self.gecko.mFont.size = device.unzoom_text(Au(self.gecko.mFont.size)).0;
    }

    pub fn copy_font_size_from(&mut self, other: &Self) {
        self.gecko.mScriptUnconstrainedSize = other.gecko.mScriptUnconstrainedSize;

        self.gecko.mSize = other.gecko.mScriptUnconstrainedSize;
        self.gecko.mFont.size = other.gecko.mSize;
        self.gecko.mFontSizeKeyword = other.gecko.mFontSizeKeyword;

        // TODO(emilio): Should we really copy over these two?
        self.gecko.mFontSizeFactor = other.gecko.mFontSizeFactor;
        self.gecko.mFontSizeOffset = other.gecko.mFontSizeOffset;
    }

    pub fn reset_font_size(&mut self, other: &Self) {
        self.copy_font_size_from(other)
    }

    pub fn set_font_size(&mut self, v: FontSize) {
        use crate::values::generics::font::KeywordSize;

        let size = v.size();
        self.gecko.mScriptUnconstrainedSize = size.0;

        // These two may be changed from Cascade::fixup_font_stuff.
        self.gecko.mSize = size.0;
        self.gecko.mFont.size = size.0;

        if let Some(info) = v.keyword_info {
            self.gecko.mFontSizeKeyword = match info.kw {
                KeywordSize::XXSmall => structs::NS_STYLE_FONT_SIZE_XXSMALL,
                KeywordSize::XSmall => structs::NS_STYLE_FONT_SIZE_XSMALL,
                KeywordSize::Small => structs::NS_STYLE_FONT_SIZE_SMALL,
                KeywordSize::Medium => structs::NS_STYLE_FONT_SIZE_MEDIUM,
                KeywordSize::Large => structs::NS_STYLE_FONT_SIZE_LARGE,
                KeywordSize::XLarge => structs::NS_STYLE_FONT_SIZE_XLARGE,
                KeywordSize::XXLarge => structs::NS_STYLE_FONT_SIZE_XXLARGE,
                KeywordSize::XXXLarge => structs::NS_STYLE_FONT_SIZE_XXXLARGE,
            } as u8;
            self.gecko.mFontSizeFactor = info.factor;
            self.gecko.mFontSizeOffset = info.offset.0.to_i32_au();
        } else {
            self.gecko.mFontSizeKeyword = structs::NS_STYLE_FONT_SIZE_NO_KEYWORD as u8;
            self.gecko.mFontSizeFactor = 1.;
            self.gecko.mFontSizeOffset = 0;
        }
    }

    pub fn clone_font_size(&self) -> FontSize {
        use crate::values::generics::font::{KeywordInfo, KeywordSize};
        let size = Au(self.gecko.mSize).into();
        let kw = match self.gecko.mFontSizeKeyword as u32 {
            structs::NS_STYLE_FONT_SIZE_XXSMALL => KeywordSize::XXSmall,
            structs::NS_STYLE_FONT_SIZE_XSMALL => KeywordSize::XSmall,
            structs::NS_STYLE_FONT_SIZE_SMALL => KeywordSize::Small,
            structs::NS_STYLE_FONT_SIZE_MEDIUM => KeywordSize::Medium,
            structs::NS_STYLE_FONT_SIZE_LARGE => KeywordSize::Large,
            structs::NS_STYLE_FONT_SIZE_XLARGE => KeywordSize::XLarge,
            structs::NS_STYLE_FONT_SIZE_XXLARGE => KeywordSize::XXLarge,
            structs::NS_STYLE_FONT_SIZE_XXXLARGE => KeywordSize::XXXLarge,
            structs::NS_STYLE_FONT_SIZE_NO_KEYWORD => {
                return FontSize {
                    size,
                    keyword_info: None,
                }
            }
            _ => unreachable!("mFontSizeKeyword should be an absolute keyword or NO_KEYWORD")
        };
        FontSize {
            size,
            keyword_info: Some(KeywordInfo {
                kw,
                factor: self.gecko.mFontSizeFactor,
                offset: Au(self.gecko.mFontSizeOffset).into()
            })
        }
    }

    pub fn set_font_weight(&mut self, v: longhands::font_weight::computed_value::T) {
        unsafe { bindings::Gecko_FontWeight_SetFloat(&mut self.gecko.mFont.weight, v.0) };
    }
    ${impl_simple_copy('font_weight', 'mFont.weight')}

    pub fn clone_font_weight(&self) -> longhands::font_weight::computed_value::T {
        let weight: f32 = unsafe {
            bindings::Gecko_FontWeight_ToFloat(self.gecko.mFont.weight)
        };
        longhands::font_weight::computed_value::T(weight)
    }

    pub fn set_font_stretch(&mut self, v: longhands::font_stretch::computed_value::T) {
        unsafe {
            bindings::Gecko_FontStretch_SetFloat(
                &mut self.gecko.mFont.stretch,
                v.value(),
            )
        };
    }
    ${impl_simple_copy('font_stretch', 'mFont.stretch')}
    pub fn clone_font_stretch(&self) -> longhands::font_stretch::computed_value::T {
        use crate::values::computed::font::FontStretch;
        use crate::values::computed::Percentage;
        use crate::values::generics::NonNegative;

        let stretch =
            unsafe { bindings::Gecko_FontStretch_ToFloat(self.gecko.mFont.stretch) };
        debug_assert!(stretch >= 0.);

        FontStretch(NonNegative(Percentage(stretch)))
    }

    pub fn set_font_style(&mut self, v: longhands::font_style::computed_value::T) {
        use crate::values::generics::font::FontStyle;
        let s = &mut self.gecko.mFont.style;
        unsafe {
            match v {
                FontStyle::Normal => bindings::Gecko_FontSlantStyle_SetNormal(s),
                FontStyle::Italic => bindings::Gecko_FontSlantStyle_SetItalic(s),
                FontStyle::Oblique(ref angle) => {
                    bindings::Gecko_FontSlantStyle_SetOblique(s, angle.0.degrees())
                }
            }
        }
    }
    ${impl_simple_copy('font_style', 'mFont.style')}
    pub fn clone_font_style(&self) -> longhands::font_style::computed_value::T {
        use crate::values::computed::font::FontStyle;
        FontStyle::from_gecko(self.gecko.mFont.style)
    }

    ${impl_simple_type_with_conversion("font_synthesis", "mFont.synthesis")}

    pub fn set_font_size_adjust(&mut self, v: longhands::font_size_adjust::computed_value::T) {
        use crate::properties::longhands::font_size_adjust::computed_value::T;
        match v {
            T::None => self.gecko.mFont.sizeAdjust = -1.0 as f32,
            T::Number(n) => self.gecko.mFont.sizeAdjust = n,
        }
    }

    pub fn copy_font_size_adjust_from(&mut self, other: &Self) {
        self.gecko.mFont.sizeAdjust = other.gecko.mFont.sizeAdjust;
    }

    pub fn reset_font_size_adjust(&mut self, other: &Self) {
        self.copy_font_size_adjust_from(other)
    }

    pub fn clone_font_size_adjust(&self) -> longhands::font_size_adjust::computed_value::T {
        use crate::properties::longhands::font_size_adjust::computed_value::T;
        T::from_gecko_adjust(self.gecko.mFont.sizeAdjust)
    }

    #[allow(non_snake_case)]
    pub fn set__x_lang(&mut self, v: longhands::_x_lang::computed_value::T) {
        let ptr = v.0.as_ptr();
        forget(v);
        unsafe {
            Gecko_nsStyleFont_SetLang(&mut *self.gecko, ptr);
        }
    }

    #[allow(non_snake_case)]
    pub fn copy__x_lang_from(&mut self, other: &Self) {
        unsafe {
            Gecko_nsStyleFont_CopyLangFrom(&mut *self.gecko, &*other.gecko);
        }
    }

    #[allow(non_snake_case)]
    pub fn reset__x_lang(&mut self, other: &Self) {
        self.copy__x_lang_from(other)
    }

    #[allow(non_snake_case)]
    pub fn clone__x_lang(&self) -> longhands::_x_lang::computed_value::T {
        longhands::_x_lang::computed_value::T(unsafe {
            Atom::from_raw(self.gecko.mLanguage.mRawPtr)
        })
    }

    #[allow(non_snake_case)]
    pub fn set__x_text_zoom(&mut self, v: longhands::_x_text_zoom::computed_value::T) {
        self.gecko.mAllowZoom = v.0;
    }

    #[allow(non_snake_case)]
    pub fn copy__x_text_zoom_from(&mut self, other: &Self) {
        self.gecko.mAllowZoom = other.gecko.mAllowZoom;
    }

    #[allow(non_snake_case)]
    pub fn reset__x_text_zoom(&mut self, other: &Self) {
        self.copy__x_text_zoom_from(other)
    }

    #[allow(non_snake_case)]
    pub fn clone__x_text_zoom(&self) -> longhands::_x_text_zoom::computed_value::T {
        longhands::_x_text_zoom::computed_value::T(self.gecko.mAllowZoom)
    }

    ${impl_simple("_moz_script_level", "mScriptLevel")}
    <% impl_simple_type_with_conversion("font_language_override", "mFont.languageOverride") %>

    pub fn set_font_variant_alternates(
        &mut self,
        v: values::computed::font::FontVariantAlternates,
    ) {
        use crate::gecko_bindings::bindings::{Gecko_ClearAlternateValues, Gecko_AppendAlternateValues};
        % for value in "normal swash stylistic ornaments annotation styleset character_variant historical".split():
            use crate::gecko_bindings::structs::NS_FONT_VARIANT_ALTERNATES_${value.upper()};
        % endfor
        use crate::values::specified::font::VariantAlternates;

        unsafe {
            Gecko_ClearAlternateValues(&mut self.gecko.mFont, v.len());
        }

        if v.0.is_empty() {
            self.gecko.mFont.variantAlternates = NS_FONT_VARIANT_ALTERNATES_NORMAL as u16;
            return;
        }

        for val in v.0.iter() {
            match *val {
                % for value in "Swash Stylistic Ornaments Annotation".split():
                    VariantAlternates::${value}(ref ident) => {
                        self.gecko.mFont.variantAlternates |= NS_FONT_VARIANT_ALTERNATES_${value.upper()} as u16;
                        unsafe {
                            Gecko_AppendAlternateValues(&mut self.gecko.mFont,
                                                        NS_FONT_VARIANT_ALTERNATES_${value.upper()},
                                                        ident.0.as_ptr());
                        }
                    },
                % endfor
                % for value in "styleset character_variant".split():
                    VariantAlternates::${to_camel_case(value)}(ref slice) => {
                        self.gecko.mFont.variantAlternates |= NS_FONT_VARIANT_ALTERNATES_${value.upper()} as u16;
                        for ident in slice.iter() {
                            unsafe {
                                Gecko_AppendAlternateValues(&mut self.gecko.mFont,
                                                            NS_FONT_VARIANT_ALTERNATES_${value.upper()},
                                                            ident.0.as_ptr());
                            }
                        }
                    },
                % endfor
                VariantAlternates::HistoricalForms => {
                    self.gecko.mFont.variantAlternates |= NS_FONT_VARIANT_ALTERNATES_HISTORICAL as u16;
                }
            }
        }
    }

    #[allow(non_snake_case)]
    pub fn copy_font_variant_alternates_from(&mut self, other: &Self) {
        use crate::gecko_bindings::bindings::Gecko_CopyAlternateValuesFrom;

        self.gecko.mFont.variantAlternates = other.gecko.mFont.variantAlternates;
        unsafe {
            Gecko_CopyAlternateValuesFrom(&mut self.gecko.mFont, &other.gecko.mFont);
        }
    }

    pub fn reset_font_variant_alternates(&mut self, other: &Self) {
        self.copy_font_variant_alternates_from(other)
    }

    pub fn clone_font_variant_alternates(&self) -> values::computed::font::FontVariantAlternates {
        % for value in "normal swash stylistic ornaments annotation styleset character_variant historical".split():
            use crate::gecko_bindings::structs::NS_FONT_VARIANT_ALTERNATES_${value.upper()};
        % endfor
        use crate::values::specified::font::VariantAlternates;
        use crate::values::specified::font::VariantAlternatesList;
        use crate::values::CustomIdent;

        if self.gecko.mFont.variantAlternates == NS_FONT_VARIANT_ALTERNATES_NORMAL as u16 {
            return VariantAlternatesList(vec![].into_boxed_slice());
        }

        let mut alternates = Vec::with_capacity(self.gecko.mFont.alternateValues.len());
        if self.gecko.mFont.variantAlternates & (NS_FONT_VARIANT_ALTERNATES_HISTORICAL as u16) != 0 {
            alternates.push(VariantAlternates::HistoricalForms);
        }

        <%
            property_need_ident_list = "styleset character_variant".split()
        %>
        % for value in property_need_ident_list:
            let mut ${value}_list = Vec::new();
        % endfor

        for gecko_alternate_value in self.gecko.mFont.alternateValues.iter() {
            let ident = Atom::from(gecko_alternate_value.value.to_string());
            match gecko_alternate_value.alternate {
                % for value in "Swash Stylistic Ornaments Annotation".split():
                    NS_FONT_VARIANT_ALTERNATES_${value.upper()} => {
                        alternates.push(VariantAlternates::${value}(CustomIdent(ident)));
                    },
                % endfor
                % for value in property_need_ident_list:
                    NS_FONT_VARIANT_ALTERNATES_${value.upper()} => {
                        ${value}_list.push(CustomIdent(ident));
                    },
                % endfor
                _ => {
                    panic!("Found unexpected value for font-variant-alternates");
                }
            }
        }

        % for value in property_need_ident_list:
            if !${value}_list.is_empty() {
                alternates.push(VariantAlternates::${to_camel_case(value)}(${value}_list.into_boxed_slice()));
            }
        % endfor

        VariantAlternatesList(alternates.into_boxed_slice())
    }

    ${impl_simple_type_with_conversion("font_variant_ligatures", "mFont.variantLigatures")}
    ${impl_simple_type_with_conversion("font_variant_east_asian", "mFont.variantEastAsian")}
    ${impl_simple_type_with_conversion("font_variant_numeric", "mFont.variantNumeric")}

    #[allow(non_snake_case)]
    pub fn clone__moz_min_font_size_ratio(
        &self,
    ) -> longhands::_moz_min_font_size_ratio::computed_value::T {
        Percentage(self.gecko.mMinFontSizeRatio as f32 / 100.)
    }

    #[allow(non_snake_case)]
    pub fn set__moz_min_font_size_ratio(&mut self, v: longhands::_moz_min_font_size_ratio::computed_value::T) {
        let scaled = v.0 * 100.;
        let percentage = if scaled > 255. {
            255.
        } else if scaled < 0. {
            0.
        } else {
            scaled
        };

        self.gecko.mMinFontSizeRatio = percentage as u8;
    }

    ${impl_simple_copy('_moz_min_font_size_ratio', 'mMinFontSizeRatio')}
</%self:impl_trait>

<%def name="impl_copy_animation_or_transition_value(type, ident, gecko_ffi_name, member=None)">
    #[allow(non_snake_case)]
    pub fn copy_${type}_${ident}_from(&mut self, other: &Self) {
        self.gecko.m${type.capitalize()}s.ensure_len(other.gecko.m${type.capitalize()}s.len());

        let count = other.gecko.m${type.capitalize()}${gecko_ffi_name}Count;
        self.gecko.m${type.capitalize()}${gecko_ffi_name}Count = count;

        let iter = self.gecko.m${type.capitalize()}s.iter_mut().take(count as usize).zip(
            other.gecko.m${type.capitalize()}s.iter()
        );

        for (ours, others) in iter {
            % if member:
            ours.m${gecko_ffi_name}.${member} = others.m${gecko_ffi_name}.${member};
            % else:
            ours.m${gecko_ffi_name} = others.m${gecko_ffi_name};
            % endif
        }
    }

    #[allow(non_snake_case)]
    pub fn reset_${type}_${ident}(&mut self, other: &Self) {
        self.copy_${type}_${ident}_from(other)
    }
</%def>

<%def name="impl_animation_or_transition_count(type, ident, gecko_ffi_name)">
    #[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}<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_ne!(v.len(), 0);
        let input_len = v.len();
        self.gecko.m${type.capitalize()}s.ensure_len(input_len);

        self.gecko.m${type.capitalize()}${gecko_ffi_name}Count = input_len as u32;
        for (gecko, servo) in self.gecko.m${type.capitalize()}s.iter_mut().take(input_len as usize).zip(v) {
            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 crate::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<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_ne!(v.len(), 0);
        let input_len = v.len();
        self.gecko.m${type.capitalize()}s.ensure_len(input_len);

        self.gecko.m${type.capitalize()}TimingFunctionCount = input_len as u32;
        for (gecko, servo) in self.gecko.m${type.capitalize()}s.iter_mut().take(input_len as usize).zip(v) {
            gecko.mTimingFunction.mTiming = servo;
        }
    }
    ${impl_animation_or_transition_count(type, 'timing_function', 'TimingFunction')}
    ${impl_copy_animation_or_transition_value(type, 'timing_function', "TimingFunction", "mTiming")}
    pub fn ${type}_timing_function_at(&self, index: usize)
        -> longhands::${type}_timing_function::computed_value::SingleComputedValue {
        self.gecko.m${type.capitalize()}s[index].mTimingFunction.mTiming
    }
</%def>

<%def name="impl_transition_time_value(ident, gecko_ffi_name)">
    ${impl_animation_or_transition_time_value('transition', ident, gecko_ffi_name)}
</%def>

<%def name="impl_transition_count(ident, gecko_ffi_name)">
    ${impl_animation_or_transition_count('transition', ident, gecko_ffi_name)}
</%def>

<%def name="impl_copy_animation_value(ident, gecko_ffi_name)">
    ${impl_copy_animation_or_transition_value('animation', ident, gecko_ffi_name)}
</%def>

<%def name="impl_transition_timing_function()">
    ${impl_animation_or_transition_timing_function('transition')}
</%def>

<%def name="impl_animation_count(ident, gecko_ffi_name)">
    ${impl_animation_or_transition_count('animation', ident, gecko_ffi_name)}
</%def>

<%def name="impl_animation_time_value(ident, gecko_ffi_name)">
    ${impl_animation_or_transition_time_value('animation', ident, gecko_ffi_name)}
</%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}<I>(&mut self, v: I)
        where I: IntoIterator<Item = longhands::animation_${ident}::computed_value::single_value::T>,
              I::IntoIter: ExactSizeIterator + Clone
    {
        use crate::properties::longhands::animation_${ident}::single_value::computed_value::T as Keyword;

        let v = v.into_iter();

        debug_assert_ne!(v.len(), 0);
        let input_len = v.len();
        self.gecko.mAnimations.ensure_len(input_len);

        self.gecko.mAnimation${gecko_ffi_name}Count = input_len as u32;

        for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) {
            let result = match servo {
                % for value in keyword.gecko_values():
                    Keyword::${to_camel_case(value)} =>
                        structs::${keyword.gecko_constant(value)} ${keyword.maybe_cast(cast_type)},
                % endfor
            };
            gecko.m${gecko_ffi_name} = result;
        }
    }
    #[allow(non_snake_case)]
    pub fn animation_${ident}_at(&self, index: usize)
        -> longhands::animation_${ident}::computed_value::SingleComputedValue {
        use crate::properties::longhands::animation_${ident}::single_value::computed_value::T as Keyword;
        match self.gecko.mAnimations[index].m${gecko_ffi_name} ${keyword.maybe_cast("u32")} {
            % for value in keyword.gecko_values():
                structs::${keyword.gecko_constant(value)} => Keyword::${to_camel_case(value)},
            % endfor
            % if keyword.gecko_inexhaustive:
            _ => panic!("Found unexpected value for animation-${ident}"),
            % endif
        }
    }
    ${impl_animation_count(ident, gecko_ffi_name)}
    ${impl_copy_animation_value(ident, gecko_ffi_name)}
</%def>

<%def name="impl_individual_transform(ident, type, gecko_ffi_name)">
    pub fn set_${ident}(&mut self, other: values::computed::${type}) {
        unsafe { self.gecko.${gecko_ffi_name}.clear() };

        if let Some(operation) = other.to_transform_operation() {
            convert_transform(&[operation], &mut self.gecko.${gecko_ffi_name})
        }
    }

    pub fn copy_${ident}_from(&mut self, other: &Self) {
        unsafe { self.gecko.${gecko_ffi_name}.set(&other.gecko.${gecko_ffi_name}); }
    }

    pub fn reset_${ident}(&mut self, other: &Self) {
        self.copy_${ident}_from(other)
    }

    pub fn clone_${ident}(&self) -> values::computed::${type} {
        use crate::values::generics::transform::${type};

        if self.gecko.${gecko_ffi_name}.mRawPtr.is_null() {
            return ${type}::None;
        }

        let list = unsafe { (*self.gecko.${gecko_ffi_name}.to_safe().get()).mHead.as_ref() };

        let mut transform = clone_transform_from_list(list);
        debug_assert_eq!(transform.0.len(), 1);
        ${type}::from_transform_operation(&transform.0.pop().unwrap())
    }
</%def>

<% skip_box_longhands= """display
                          animation-name animation-delay animation-duration
                          animation-direction animation-fill-mode animation-play-state
                          animation-iteration-count animation-timing-function
                          clear transition-duration transition-delay
                          transition-timing-function transition-property
                          transform-style
                          rotate scroll-snap-points-x scroll-snap-points-y
                          scroll-snap-coordinate -moz-binding will-change
                          offset-path shape-outside
                          translate scale -webkit-line-clamp""" %>
<%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}">
    #[inline]
    pub fn generate_combined_transform(&mut self) {
        unsafe { bindings::Gecko_StyleDisplay_GenerateCombinedTransform(&mut *self.gecko) };
    }

    #[inline]
    pub fn set_display(&mut self, v: longhands::display::computed_value::T) {
        self.gecko.mDisplay = v;
        self.gecko.mOriginalDisplay = v;
    }

    #[inline]
    pub fn copy_display_from(&mut self, other: &Self) {
        self.gecko.mDisplay = other.gecko.mDisplay;
        self.gecko.mOriginalDisplay = other.gecko.mDisplay;
    }

    #[inline]
    pub fn reset_display(&mut self, other: &Self) {
        self.copy_display_from(other)
    }

    #[inline]
    pub fn set_adjusted_display(
        &mut self,
        v: longhands::display::computed_value::T,
        _is_item_or_root: bool
    ) {
        self.gecko.mDisplay = v;
    }

    #[inline]
    pub fn clone_display(&self) -> longhands::display::computed_value::T {
        self.gecko.mDisplay
    }

    <% clear_keyword = Keyword(
        "clear",
        "Left Right None Both",
        gecko_enum_prefix="StyleClear",
        gecko_inexhaustive=True,
    ) %>
    ${impl_keyword('clear', 'mBreakType', clear_keyword)}

    ${impl_style_coord("scroll_snap_points_x", "mScrollSnapPointsX")}
    ${impl_style_coord("scroll_snap_points_y", "mScrollSnapPointsY")}

    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
    {
        self.gecko.mScrollSnapCoordinate.assign_from_iter_pod(v.into_iter());
    }

    pub fn copy_scroll_snap_coordinate_from(&mut self, other: &Self) {
        let iter = other.gecko.mScrollSnapCoordinate.iter().map(|c| *c);
        self.gecko.mScrollSnapCoordinate.assign_from_iter_pod(iter);
    }

    pub fn reset_scroll_snap_coordinate(&mut self, other: &Self) {
        self.copy_scroll_snap_coordinate_from(other)
    }

    pub fn clone_scroll_snap_coordinate(&self) -> longhands::scroll_snap_coordinate::computed_value::T {
        let vec = self.gecko.mScrollSnapCoordinate.iter().cloned().collect();
        longhands::scroll_snap_coordinate::computed_value::List(vec)
    }

    ${impl_css_url('_moz_binding', 'mBinding')}

    ${impl_transition_time_value('delay', 'Delay')}
    ${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 % self.gecko.mTransitionDurationCount as usize].mDuration.max(0.0)
            + self.gecko.mTransitions[index % self.gecko.mTransitionDelayCount as usize].mDelay
    }

    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 crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties;
        use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
        use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;

        let v = v.into_iter();

        if v.len() != 0 {
            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()) {
                unsafe { gecko.mUnknownProperty.clear() };

                match servo {
                    TransitionProperty::Unsupported(ident) => {
                        gecko.mProperty = eCSSProperty_UNKNOWN;
                        gecko.mUnknownProperty.mRawPtr = ident.0.into_addrefed();
                    },
                    TransitionProperty::Custom(name) => {
                        gecko.mProperty = eCSSPropertyExtra_variable;
                        gecko.mUnknownProperty.mRawPtr = name.into_addrefed();
                    }
                    _ => gecko.mProperty = servo.to_nscsspropertyid().unwrap(),
                }
            }
        } else {
            // In gecko |none| is represented by eCSSPropertyExtra_no_properties.
            self.gecko.mTransitionPropertyCount = 1;
            self.gecko.mTransitions[0].mProperty = eCSSPropertyExtra_no_properties;
        }
    }

    /// Returns whether there are any transitions specified.
    pub fn specifies_transitions(&self) -> bool {
        use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_all_properties;
        if self.gecko.mTransitionPropertyCount == 1 &&
            self.gecko.mTransitions[0].mProperty == eCSSPropertyExtra_all_properties &&
            self.transition_combined_duration_at(0) <= 0.0f32 {
            return false;
        }

        self.gecko.mTransitionPropertyCount > 0
    }

    pub fn transition_property_at(&self, index: usize)
        -> longhands::transition_property::computed_value::SingleComputedValue {
        use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties;
        use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
        use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;

        let property = self.gecko.mTransitions[index].mProperty;
        if property == eCSSProperty_UNKNOWN {
            let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
            debug_assert!(!atom.is_null());
            TransitionProperty::Unsupported(CustomIdent(unsafe{
                Atom::from_raw(atom)
            }))
        } else if property == eCSSPropertyExtra_variable {
            let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
            debug_assert!(!atom.is_null());
            TransitionProperty::Custom(unsafe{
                Atom::from_raw(atom)
            })
        } else if property == eCSSPropertyExtra_no_properties {
            // Actually, we don't expect TransitionProperty::Unsupported also
            // represents "none", but if the caller wants to convert it, it is
            // fine. Please use it carefully.
            //
            // FIXME(emilio): This is a hack, is this reachable?
            TransitionProperty::Unsupported(CustomIdent(atom!("none")))
        } else {
            property.into()
        }
    }

    pub fn transition_nscsspropertyid_at(&self, index: usize) -> nsCSSPropertyID {
        self.gecko.mTransitions[index].mProperty
    }

    pub fn copy_transition_property_from(&mut self, other: &Self) {
        use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
        use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
        self.gecko.mTransitions.ensure_len(other.gecko.mTransitions.len());

        let count = other.gecko.mTransitionPropertyCount;
        self.gecko.mTransitionPropertyCount = count;

        for (index, transition) in self.gecko.mTransitions.iter_mut().enumerate().take(count as usize) {
            transition.mProperty = other.gecko.mTransitions[index].mProperty;
            unsafe { transition.mUnknownProperty.clear() };
            if transition.mProperty == eCSSProperty_UNKNOWN ||
               transition.mProperty == eCSSPropertyExtra_variable {
                let atom = other.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
                debug_assert!(!atom.is_null());
                transition.mUnknownProperty.mRawPtr = unsafe { Atom::from_raw(atom) }.into_addrefed();
            }
        }
    }

    pub fn reset_transition_property(&mut self, other: &Self) {
        self.copy_transition_property_from(other)
    }

    // Hand-written because the Mako helpers transform `Preserve3d` into `PRESERVE3D`.
    pub fn set_transform_style(&mut self, v: TransformStyle) {
        self.gecko.mTransformStyle = match v {
            TransformStyle::Flat => structs::NS_STYLE_TRANSFORM_STYLE_FLAT as u8,
            TransformStyle::Preserve3d => structs::NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D as u8,
        };
    }

    // Hand-written because the Mako helpers transform `Preserve3d` into `PRESERVE3D`.
    pub fn clone_transform_style(&self) -> TransformStyle {
        match self.gecko.mTransformStyle as u32 {
            structs::NS_STYLE_TRANSFORM_STYLE_FLAT => TransformStyle::Flat,
            structs::NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D => TransformStyle::Preserve3d,
            _ => panic!("illegal transform style"),
        }
    }

    ${impl_simple_copy('transform_style', 'mTransformStyle')}

    ${impl_transition_count('property', 'Property')}

    pub fn animations_equals(&self, other: &Self) -> bool {
        return self.gecko.mAnimationNameCount == other.gecko.mAnimationNameCount
            && self.gecko.mAnimationDelayCount == other.gecko.mAnimationDelayCount
            && self.gecko.mAnimationDirectionCount == other.gecko.mAnimationDirectionCount
            && self.gecko.mAnimationDurationCount == other.gecko.mAnimationDurationCount
            && self.gecko.mAnimationFillModeCount == other.gecko.mAnimationFillModeCount
            && self.gecko.mAnimationIterationCountCount == other.gecko.mAnimationIterationCountCount
            && self.gecko.mAnimationPlayStateCount == other.gecko.mAnimationPlayStateCount
            && self.gecko.mAnimationTimingFunctionCount == other.gecko.mAnimationTimingFunctionCount
            && unsafe { bindings::Gecko_StyleAnimationsEquals(&self.gecko.mAnimations, &other.gecko.mAnimations) }
    }

    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_ne!(v.len(), 0);
        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()) {
            let atom = match servo.0 {
                None => atom!(""),
                Some(ref name) => name.as_atom().clone(),
            };
            unsafe { bindings::Gecko_SetAnimationName(gecko, atom.into_addrefed()); }
        }
    }
    pub fn animation_name_at(&self, index: usize)
        -> longhands::animation_name::computed_value::SingleComputedValue {
        use crate::properties::longhands::animation_name::single_value::SpecifiedValue as AnimationName;

        let atom = self.gecko.mAnimations[index].mName.mRawPtr;
        if atom == atom!("").as_ptr() {
            return AnimationName(None)
        }
        AnimationName(Some(KeyframesName::from_atom(unsafe { Atom::from_raw(atom) })))
    }
    pub fn copy_animation_name_from(&mut self, other: &Self) {
        self.gecko.mAnimationNameCount = other.gecko.mAnimationNameCount;
        unsafe { bindings::Gecko_CopyAnimationNames(&mut self.gecko.mAnimations, &other.gecko.mAnimations); }
    }

    pub fn reset_animation_name(&mut self, other: &Self) {
        self.copy_animation_name_from(other)
    }

    ${impl_animation_count('name', 'Name')}

    ${impl_animation_time_value('delay', 'Delay')}
    ${impl_animation_time_value('duration', 'Duration')}

    ${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<I>(&mut self, v: I)
    where
        I: IntoIterator<Item = values::computed::AnimationIterationCount>,
        I::IntoIter: ExactSizeIterator + Clone
    {
        use std::f32;
        use crate::values::generics::box_::AnimationIterationCount;

        let v = v.into_iter();

        debug_assert_ne!(v.len(), 0);
        let input_len = v.len();
        self.gecko.mAnimations.ensure_len(input_len);

        self.gecko.mAnimationIterationCountCount = input_len as u32;
        for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) {
            match servo {
                AnimationIterationCount::Number(n) => gecko.mIterationCount = n,
                AnimationIterationCount::Infinite => gecko.mIterationCount = f32::INFINITY,
            }
        }
    }

    pub fn animation_iteration_count_at(
        &self,
        index: usize,
    ) -> values::computed::AnimationIterationCount {
        use crate::values::generics::box_::AnimationIterationCount;

        if self.gecko.mAnimations[index].mIterationCount.is_infinite() {
            AnimationIterationCount::Infinite
        } else {
            AnimationIterationCount::Number(self.gecko.mAnimations[index].mIterationCount)
        }
    }

    ${impl_animation_count('iteration_count', 'IterationCount')}
    ${impl_copy_animation_value('iteration_count', 'IterationCount')}

    ${impl_animation_timing_function()}

    ${impl_individual_transform('rotate', 'Rotate', 'mSpecifiedRotate')}
    ${impl_individual_transform('translate', 'Translate', 'mSpecifiedTranslate')}
    ${impl_individual_transform('scale', 'Scale', 'mSpecifiedScale')}

    pub fn set_will_change(&mut self, v: longhands::will_change::computed_value::T) {
        use crate::gecko_bindings::bindings::{Gecko_AppendWillChange, Gecko_ClearWillChange};
        use crate::values::specified::box_::{WillChangeBits, WillChange};

        match v {
            WillChange::AnimateableFeatures { features, bits } => {
                unsafe {
                    Gecko_ClearWillChange(&mut *self.gecko, features.len());
                }

                for feature in features.iter() {
                    unsafe {
                        Gecko_AppendWillChange(&mut *self.gecko, feature.0.as_ptr())
                    }
                }

                self.gecko.mWillChangeBitField = bits;
            },
            WillChange::Auto => {
                unsafe {
                    Gecko_ClearWillChange(&mut *self.gecko, 0);
                }
                self.gecko.mWillChangeBitField = WillChangeBits::empty();
            },
        };
    }

    pub fn copy_will_change_from(&mut self, other: &Self) {
        use crate::gecko_bindings::bindings::Gecko_CopyWillChangeFrom;

        self.gecko.mWillChangeBitField = other.gecko.mWillChangeBitField;
        unsafe {
            Gecko_CopyWillChangeFrom(&mut *self.gecko, &*other.gecko);
        }
    }

    pub fn reset_will_change(&mut self, other: &Self) {
        self.copy_will_change_from(other)
    }

    pub fn clone_will_change(&self) -> longhands::will_change::computed_value::T {
        use crate::values::CustomIdent;
        use crate::values::specified::box_::WillChange;

        if self.gecko.mWillChange.len() == 0 {
            return WillChange::Auto
        }

        let custom_idents: Vec<CustomIdent> = self.gecko.mWillChange.iter().map(|gecko_atom| {
            unsafe {
                CustomIdent(Atom::from_raw(gecko_atom.mRawPtr))
            }
        }).collect();

        WillChange::AnimateableFeatures {
            features: custom_idents.into_boxed_slice(),
            bits: self.gecko.mWillChangeBitField,
        }
    }

    <% impl_shape_source("shape_outside", "mShapeOutside") %>

    pub fn set_offset_path(&mut self, v: longhands::offset_path::computed_value::T) {
        use crate::gecko_bindings::bindings::{Gecko_NewStyleMotion, Gecko_SetStyleMotion};
        use crate::gecko_bindings::structs::StyleShapeSourceType;
        use crate::values::generics::basic_shape::FillRule;
        use crate::values::specified::OffsetPath;

        let motion = unsafe { Gecko_NewStyleMotion().as_mut().unwrap() };
        match v {
            OffsetPath::None => motion.mOffsetPath.mType = StyleShapeSourceType::None,
            OffsetPath::Path(p) => {
                set_style_svg_path(&mut motion.mOffsetPath, p, FillRule::Nonzero)
            },
        }
        unsafe { Gecko_SetStyleMotion(&mut self.gecko.mMotion, motion) };
    }

    pub fn clone_offset_path(&self) -> longhands::offset_path::computed_value::T {
        use crate::values::specified::OffsetPath;
        match unsafe { self.gecko.mMotion.mPtr.as_ref() } {
            None => OffsetPath::none(),
            Some(v) => (&v.mOffsetPath).into()
        }
    }

    pub fn copy_offset_path_from(&mut self, other: &Self) {
        use crate::gecko_bindings::bindings::Gecko_CopyStyleMotions;
        unsafe { Gecko_CopyStyleMotions(&mut self.gecko.mMotion, other.gecko.mMotion.mPtr) };
    }

    pub fn reset_offset_path(&mut self, other: &Self) {
        self.copy_offset_path_from(other);
    }

    #[allow(non_snake_case)]
    pub fn set__webkit_line_clamp(&mut self, v: longhands::_webkit_line_clamp::computed_value::T) {
        self.gecko.mLineClamp = match v {
            Either::First(n) => n.0 as u32,
            Either::Second(None_) => 0,
        };
    }

    ${impl_simple_copy('_webkit_line_clamp', 'mLineClamp')}

    #[allow(non_snake_case)]
    pub fn clone__webkit_line_clamp(&self) -> longhands::_webkit_line_clamp::computed_value::T {
        match self.gecko.mLineClamp {
            0 => Either::Second(None_),
            n => {
                debug_assert!(n <= std::i32::MAX as u32);
                Either::First((n as i32).into())
            }
        }
    }

</%self:impl_trait>

<%def name="simple_image_array_property(name, shorthand, field_name)">
    <%
        image_layers_field = "mImage" if shorthand == "background" else "mMask"
        copy_simple_image_array_property(name, shorthand, image_layers_field, field_name)
    %>

    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 crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType;
        let v = v.into_iter();

        unsafe {
          Gecko_EnsureImageLayersLength(&mut self.gecko.${image_layers_field}, v.len(),
                                        LayerType::${shorthand.title()});
        }

        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="copy_simple_image_array_property(name, shorthand, layers_field_name, field_name)">
    pub fn copy_${shorthand}_${name}_from(&mut self, other: &Self) {
        use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType;

        let count = other.gecko.${layers_field_name}.${field_name}Count;
        unsafe {
            Gecko_EnsureImageLayersLength(&mut self.gecko.${layers_field_name},
                                          count as usize,
                                          LayerType::${shorthand.title()});
        }
        // FIXME(emilio): This may be bogus in the same way as bug 1426246.
        for (layer, other) in self.gecko.${layers_field_name}.mLayers.iter_mut()
                                  .zip(other.gecko.${layers_field_name}.mLayers.iter())
                                  .take(count as usize) {
            layer.${field_name} = other.${field_name};
        }
        self.gecko.${layers_field_name}.${field_name}Count = count;
    }

    pub fn reset_${shorthand}_${name}(&mut self, other: &Self) {
        self.copy_${shorthand}_${name}_from(other)
    }
</%def>

<%def name="impl_simple_image_array_property(name, shorthand, layer_field_name, field_name, struct_name)">
    <%
        ident = "%s_%s" % (shorthand, name)
        style_struct = next(x for x in data.style_structs if x.name == struct_name)
        longhand = next(x for x in style_struct.longhands if x.ident == ident)
        keyword = longhand.keyword
    %>

    <% copy_simple_image_array_property(name, shorthand, layer_field_name, field_name) %>

    pub fn set_${ident}<I>(&mut self, v: I)
    where
        I: IntoIterator<Item=longhands::${ident}::computed_value::single_value::T>,
        I::IntoIter: ExactSizeIterator,
    {
        use crate::properties::longhands::${ident}::single_value::computed_value::T as Keyword;
        use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType;

        let v = v.into_iter();

        unsafe {
          Gecko_EnsureImageLayersLength(&mut self.gecko.${layer_field_name}, v.len(),
                                        LayerType::${shorthand.title()});
        }

        self.gecko.${layer_field_name}.${field_name}Count = v.len() as u32;
        for (servo, geckolayer) in v.zip(self.gecko.${layer_field_name}.mLayers.iter_mut()) {
            geckolayer.${field_name} = {
                match servo {
                    % for value in keyword.values_for("gecko"):
                    Keyword::${to_camel_case(value)} =>
                        structs::${keyword.gecko_constant(value)} ${keyword.maybe_cast('u8')},
                    % endfor
                }
            };
        }
    }

    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
        use crate::properties::longhands::${ident}::single_value::computed_value::T as Keyword;

        % if keyword.needs_cast():
        % for value in keyword.values_for('gecko'):
        const ${keyword.casted_constant_name(value, "u8")} : u8 =
            structs::${keyword.gecko_constant(value)} as u8;
        % endfor
        % endif

        longhands::${ident}::computed_value::List(
            self.gecko.${layer_field_name}.mLayers.iter()
                .take(self.gecko.${layer_field_name}.${field_name}Count as usize)
                .map(|ref layer| {
                    match layer.${field_name} {
                        % for value in longhand.keyword.values_for("gecko"):
                        % if keyword.needs_cast():
                        ${keyword.casted_constant_name(value, "u8")}
                        % else:
                        structs::${keyword.gecko_constant(value)}
                        % endif
                            => Keyword::${to_camel_case(value)},
                        % endfor
                        % if keyword.gecko_inexhaustive:
                        _ => panic!("Found unexpected value in style struct for ${ident} property"),
                        % endif
                    }
                }).collect()
        )
    }
</%def>

<%def name="impl_common_image_layer_properties(shorthand)">
    <%
        if shorthand == "background":
            image_layers_field = "mImage"
            struct_name = "Background"
        else:
            image_layers_field = "mMask"
            struct_name = "SVG"
    %>

    <%self:simple_image_array_property name="repeat" shorthand="${shorthand}" field_name="mRepeat">
        use crate::values::specified::background::BackgroundRepeatKeyword;
        use crate::gecko_bindings::structs::nsStyleImageLayers_Repeat;
        use crate::gecko_bindings::structs::StyleImageLayerRepeat;

        fn to_ns(repeat: BackgroundRepeatKeyword) -> StyleImageLayerRepeat {
            match repeat {
                BackgroundRepeatKeyword::Repeat => StyleImageLayerRepeat::Repeat,
                BackgroundRepeatKeyword::Space => StyleImageLayerRepeat::Space,
                BackgroundRepeatKeyword::Round => StyleImageLayerRepeat::Round,
                BackgroundRepeatKeyword::NoRepeat => StyleImageLayerRepeat::NoRepeat,
            }
        }

        let repeat_x = to_ns(servo.0);
        let repeat_y = to_ns(servo.1);
        nsStyleImageLayers_Repeat {
              mXRepeat: repeat_x,
              mYRepeat: repeat_y,
        }
    </%self:simple_image_array_property>

    pub fn clone_${shorthand}_repeat(&self) -> longhands::${shorthand}_repeat::computed_value::T {
        use crate::properties::longhands::${shorthand}_repeat::single_value::computed_value::T;
        use crate::values::specified::background::BackgroundRepeatKeyword;
        use crate::gecko_bindings::structs::StyleImageLayerRepeat;

        fn to_servo(repeat: StyleImageLayerRepeat) -> BackgroundRepeatKeyword {
            match repeat {
                StyleImageLayerRepeat::Repeat => BackgroundRepeatKeyword::Repeat,
                StyleImageLayerRepeat::Space => BackgroundRepeatKeyword::Space,
                StyleImageLayerRepeat::Round => BackgroundRepeatKeyword::Round,
                StyleImageLayerRepeat::NoRepeat => BackgroundRepeatKeyword::NoRepeat,
                _ => panic!("Found unexpected value in style struct for ${shorthand}_repeat property"),
            }
        }

        longhands::${shorthand}_repeat::computed_value::List(
            self.gecko.${image_layers_field}.mLayers.iter()
                .take(self.gecko.${image_layers_field}.mRepeatCount as usize)
                .map(|ref layer| {
                    T(to_servo(layer.mRepeat.mXRepeat), to_servo(layer.mRepeat.mYRepeat))
                }).collect()
        )
    }

    <% impl_simple_image_array_property("clip", shorthand, image_layers_field, "mClip", struct_name) %>
    <% impl_simple_image_array_property("origin", shorthand, image_layers_field, "mOrigin", struct_name) %>

    % for (orientation, keyword) in [("x", "horizontal"), ("y", "vertical")]:
    pub fn copy_${shorthand}_position_${orientation}_from(&mut self, other: &Self) {
        use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType;

        let count = other.gecko.${image_layers_field}.mPosition${orientation.upper()}Count;

        unsafe {
            Gecko_EnsureImageLayersLength(&mut self.gecko.${image_layers_field},
                                          count as usize,
                                          LayerType::${shorthand.capitalize()});
        }

        for (layer, other) in self.gecko.${image_layers_field}.mLayers.iter_mut()
                                  .zip(other.gecko.${image_layers_field}.mLayers.iter())
                                  .take(count as usize) {
            layer.mPosition.${keyword} = other.mPosition.${keyword};
        }
        self.gecko.${image_layers_field}.mPosition${orientation.upper()}Count = count;
    }

    pub fn reset_${shorthand}_position_${orientation}(&mut self, other: &Self) {
        self.copy_${shorthand}_position_${orientation}_from(other)
    }

    pub fn clone_${shorthand}_position_${orientation}(&self)
        -> longhands::${shorthand}_position_${orientation}::computed_value::T {
        longhands::${shorthand}_position_${orientation}::computed_value::List(
            self.gecko.${image_layers_field}.mLayers.iter()
                .take(self.gecko.${image_layers_field}.mPosition${orientation.upper()}Count as usize)
                .map(|position| position.mPosition.${keyword})
                .collect()
        )
    }

    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 crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType;

        let v = v.into_iter();

        unsafe {
            Gecko_EnsureImageLayersLength(&mut self.gecko.${image_layers_field}, v.len(),
                                        LayerType::${shorthand.capitalize()});
        }

        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.${keyword} = servo;
        }
    }
    % endfor

    <%self:simple_image_array_property name="size" shorthand="${shorthand}" field_name="mSize">
        servo
    </%self:simple_image_array_property>

    pub fn clone_${shorthand}_size(&self) -> longhands::${shorthand}_size::computed_value::T {
        longhands::${shorthand}_size::computed_value::List(
            self.gecko.${image_layers_field}.mLayers.iter().map(|layer| layer.mSize).collect()
        )
    }

    pub fn copy_${shorthand}_image_from(&mut self, other: &Self) {
        use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType;
        unsafe {
            let count = other.gecko.${image_layers_field}.mImageCount;
            Gecko_EnsureImageLayersLength(&mut self.gecko.${image_layers_field},
                                          count as usize,
                                          LayerType::${shorthand.capitalize()});

            for (layer, other) in self.gecko.${image_layers_field}.mLayers.iter_mut()
                                      .zip(other.gecko.${image_layers_field}.mLayers.iter())
                                      .take(count as usize) {
                Gecko_CopyImageValueFrom(&mut layer.mImage, &other.mImage);
            }
            self.gecko.${image_layers_field}.mImageCount = count;
        }
    }

    pub fn reset_${shorthand}_image(&mut self, other: &Self) {
        self.copy_${shorthand}_image_from(other)
    }

    #[allow(unused_variables)]
    pub fn set_${shorthand}_image<I>(&mut self, images: I)
        where I: IntoIterator<Item = longhands::${shorthand}_image::computed_value::single_value::T>,
              I::IntoIter: ExactSizeIterator
    {
        use crate::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.len(),
                                          LayerType::${shorthand.title()});
        }

        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 Either::Second(image) = image {
                geckoimage.mImage.set(image)
            }
        }
    }

    pub fn clone_${shorthand}_image(&self) -> longhands::${shorthand}_image::computed_value::T {
        use crate::values::None_;

        longhands::${shorthand}_image::computed_value::List(
            self.gecko.${image_layers_field}.mLayers.iter()
                .take(self.gecko.${image_layers_field}.mImageCount as usize)
                .map(|ref layer| {
                    match unsafe { layer.mImage.into_image() } {
                        Some(image) => Either::Second(image),
                        None => Either::First(None_),
                    }
            }).collect()
        )
    }

    <%
        fill_fields = "mRepeat mClip mOrigin mPositionX mPositionY mImage mSize"
        if shorthand == "background":
            fill_fields += " mAttachment mBlendMode"
        else:
            # mSourceURI uses mImageCount
            fill_fields += " mMaskMode mComposite"
    %>
    pub fn fill_arrays(&mut self) {
        use crate::gecko_bindings::bindings::Gecko_FillAllImageLayers;
        use std::cmp;
        let mut max_len = 1;
        % for member in fill_fields.split():
            max_len = cmp::max(max_len, self.gecko.${image_layers_field}.${member}Count);
        % endfor
        unsafe {
            // While we could do this manually, we'd need to also manually
            // run all the copy constructors, so we just delegate to gecko
            Gecko_FillAllImageLayers(&mut self.gecko.${image_layers_field}, max_len);
        }
    }
</%def>

// TODO: Gecko accepts lists in most background-related properties. We just use
// the first element (which is the common case), but at some point we want to
// add support for parsing these lists in servo and pushing to nsTArray's.
<% skip_background_longhands = """background-repeat
                                  background-image background-clip
                                  background-origin background-attachment
                                  background-size background-position
                                  background-blend-mode
                                  background-position-x
                                  background-position-y""" %>
<%self:impl_trait style_struct_name="Background"
                  skip_longhands="${skip_background_longhands}">

    <% impl_common_image_layer_properties("background") %>
    <% impl_simple_image_array_property("attachment", "background", "mImage", "mAttachment", "Background") %>
    <% impl_simple_image_array_property("blend_mode", "background", "mImage", "mBlendMode", "Background") %>
</%self:impl_trait>

<%self:impl_trait style_struct_name="List"
                  skip_longhands="list-style-image list-style-type quotes -moz-image-region">

    pub fn set_list_style_image(&mut self, image: longhands::list_style_image::computed_value::T) {
        match image {
            UrlOrNone::None => {
                unsafe {
                    Gecko_SetListStyleImageNone(&mut *self.gecko);
                }
            }
            UrlOrNone::Url(ref url) => {
                unsafe {
                    Gecko_SetListStyleImageImageValue(
                        &mut *self.gecko,
                        url.url_value_ptr(),
                    );
                }
            }
        }
    }

    pub fn copy_list_style_image_from(&mut self, other: &Self) {
        unsafe { Gecko_CopyListStyleImageFrom(&mut *self.gecko, &*other.gecko); }
    }

    pub fn reset_list_style_image(&mut self, other: &Self) {
        self.copy_list_style_image_from(other)
    }

    pub fn clone_list_style_image(&self) -> longhands::list_style_image::computed_value::T {
        use crate::values::computed::url::ComputedImageUrl;

        if self.gecko.mListStyleImage.mRawPtr.is_null() {
            return UrlOrNone::None;
        }

        unsafe {
            let ref gecko_image_request = *self.gecko.mListStyleImage.mRawPtr;
            UrlOrNone::Url(ComputedImageUrl::from_image_request(gecko_image_request))
        }
    }

    pub fn set_list_style_type(&mut self, v: longhands::list_style_type::computed_value::T) {
        use crate::gecko_bindings::bindings::Gecko_SetCounterStyleToString;
        use nsstring::{nsACString, nsCStr};
        use self::longhands::list_style_type::computed_value::T;
        match v {
            T::CounterStyle(s) => s.to_gecko_value(&mut self.gecko.mCounterStyle),
            T::String(s) => unsafe {
                Gecko_SetCounterStyleToString(&mut self.gecko.mCounterStyle,
                                              &nsCStr::from(&s) as &nsACString)
            }
        }
    }

    pub fn copy_list_style_type_from(&mut self, other: &Self) {
        unsafe {
            Gecko_CopyCounterStyle(&mut self.gecko.mCounterStyle, &other.gecko.mCounterStyle);
        }
    }

    pub fn reset_list_style_type(&mut self, other: &Self) {
        self.copy_list_style_type_from(other)
    }

    pub fn clone_list_style_type(&self) -> longhands::list_style_type::computed_value::T {
        use self::longhands::list_style_type::computed_value::T;
        use crate::values::Either;
        use crate::values::generics::CounterStyleOrNone;

        let result = CounterStyleOrNone::from_gecko_value(&self.gecko.mCounterStyle);
        match result {
            Either::First(counter_style) => T::CounterStyle(counter_style),
            Either::Second(string) => T::String(string),
        }
    }

    pub fn set_quotes(&mut <