servo: Merge #18072 - Move some code out of animated_properties (from servo:we-are-leaving-babylon); r=emilio
authorAnthony Ramine <n.oxyde@gmail.com>
Mon, 14 Aug 2017 07:51:34 -0500
changeset 374624 8de0e5e7d0fa1f4f6ebb031af0e9ce619651ae2b
parent 374623 c218d170bd314525d9e0e83090a8668753a23ed2
child 374625 181e326996e9d40e8481a016060cd569e0c81bd2
push id93728
push userkwierso@gmail.com
push dateTue, 15 Aug 2017 00:58:32 +0000
treeherdermozilla-inbound@ec83c820877e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
bugs18072
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #18072 - Move some code out of animated_properties (from servo:we-are-leaving-babylon); r=emilio Source-Repo: https://github.com/servo/servo Source-Revision: 4d22af613649f25859e24cea2b616d1ff1844fb6
servo/components/style/properties/helpers/animated_properties.mako.rs
servo/components/style/properties/longhand/background.mako.rs
servo/components/style/properties/longhand/border.mako.rs
servo/components/style/properties/longhand/color.mako.rs
servo/components/style/properties/longhand/column.mako.rs
servo/components/style/properties/longhand/inherited_text.mako.rs
servo/components/style/properties/longhand/outline.mako.rs
servo/components/style/properties/longhand/pointing.mako.rs
servo/components/style/properties/longhand/svg.mako.rs
servo/components/style/properties/longhand/text.mako.rs
servo/components/style/values/animated/color.rs
servo/components/style/values/animated/effects.rs
servo/components/style/values/animated/mod.rs
servo/components/style/values/computed/border.rs
servo/components/style/values/computed/color.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/computed/percentage.rs
servo/components/style/values/specified/length.rs
servo/components/style/values/specified/mod.rs
servo/components/style/values/specified/percentage.rs
servo/components/style/values/specified/rect.rs
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% from data import to_idl_name, SYSTEM_FONT_LONGHANDS %>
 
 use app_units::Au;
-use cssparser::{Parser, RGBA};
+use cssparser::Parser;
 use euclid::{Point2D, Size2D};
 #[cfg(feature = "gecko")] use gecko_bindings::bindings::RawServoAnimationValueMap;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::RawGeckoGfxMatrix4x4;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSPropertyID;
 #[cfg(feature = "gecko")] use gecko_bindings::sugar::ownership::{HasFFI, HasSimpleFFI};
 #[cfg(feature = "gecko")] use gecko_string_cache::Atom;
 use properties::{CSSWideKeyword, PropertyDeclaration};
 use properties::longhands;
@@ -33,23 +33,24 @@ use smallvec::SmallVec;
 use std::cmp;
 #[cfg(feature = "gecko")] use fnv::FnvHashMap;
 use style_traits::ParseError;
 use super::ComputedValues;
 #[cfg(feature = "gecko")]
 use values::Auto;
 use values::{CSSFloat, CustomIdent, Either};
 use values::animated::{ToAnimatedValue, ToAnimatedZero};
+use values::animated::color::{Color as AnimatedColor, RGBA as AnimatedRGBA};
 use values::animated::effects::BoxShadowList as AnimatedBoxShadowList;
 use values::animated::effects::Filter as AnimatedFilter;
 use values::animated::effects::FilterList as AnimatedFilterList;
 use values::animated::effects::TextShadowList as AnimatedTextShadowList;
 use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone};
 use values::computed::{BorderCornerRadius, ClipRect};
-use values::computed::{CalcLengthOrPercentage, Color, Context, ComputedValueAsSpecified, ComputedUrl};
+use values::computed::{CalcLengthOrPercentage, Context, ComputedValueAsSpecified, ComputedUrl};
 use values::computed::{LengthOrPercentage, MaxLength, MozLength, Percentage, ToComputedValue};
 use values::computed::{NonNegativeAu, NonNegativeNumber, PositiveIntegerOrAuto};
 use values::computed::length::{NonNegativeLengthOrAuto, NonNegativeLengthOrNormal};
 use values::computed::length::NonNegativeLengthOrPercentage;
 use values::distance::{ComputeSquaredDistance, SquaredDistance};
 use values::generics::{GreaterThanOrEqualToOne, NonNegative};
 use values::generics::effects::Filter;
 use values::generics::position as generic_position;
@@ -882,31 +883,16 @@ impl Animatable for Angle {
                 self.radians()
                     .add_weighted(&other.radians(), self_portion, other_portion)
                     .map(Angle::from_radians)
             }
         }
     }
 }
 
-/// https://drafts.csswg.org/css-transitions/#animtype-percentage
-impl Animatable for Percentage {
-    #[inline]
-    fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
-        Ok(Percentage((self.0 as f64 * self_portion + other.0 as f64 * other_portion) as f32))
-    }
-}
-
-impl ToAnimatedZero for Percentage {
-    #[inline]
-    fn to_animated_zero(&self) -> Result<Self, ()> {
-        Ok(Percentage(0.))
-    }
-}
-
 /// https://drafts.csswg.org/css-transitions/#animtype-visibility
 impl Animatable for Visibility {
     #[inline]
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         match (*self, *other) {
             (Visibility::visible, _) => {
                 Ok(if self_portion > 0.0 { *self } else { *other })
             },
@@ -947,21 +933,16 @@ impl<T: Animatable + Copy> Animatable fo
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         let x = self.x.add_weighted(&other.x, self_portion, other_portion)?;
         let y = self.y.add_weighted(&other.y, self_portion, other_portion)?;
 
         Ok(Point2D::new(x, y))
     }
 }
 
-impl ToAnimatedZero for BorderCornerRadius {
-    #[inline]
-    fn to_animated_zero(&self) -> Result<Self, ()> { Err(()) }
-}
-
 /// https://drafts.csswg.org/css-transitions/#animtype-length
 impl Animatable for VerticalAlign {
     #[inline]
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         match (*self, *other) {
             (VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref this)),
              VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref other))) => {
                 this.add_weighted(other, self_portion, other_portion).map(|value| {
@@ -2450,263 +2431,21 @@ where
             },
             Either::Second(ref second) => {
                 Ok(Either::Second(second.to_animated_zero()?))
             },
         }
     }
 }
 
-#[derive(Copy, Clone, Debug, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-/// Unlike RGBA, each component value may exceed the range [0.0, 1.0].
-pub struct IntermediateRGBA {
-    /// The red component.
-    pub red: f32,
-    /// The green component.
-    pub green: f32,
-    /// The blue component.
-    pub blue: f32,
-    /// The alpha component.
-    pub alpha: f32,
-}
-
-impl IntermediateRGBA {
-    /// Returns a transparent color.
-    #[inline]
-    pub fn transparent() -> Self {
-        Self::new(0., 0., 0., 0.)
-    }
-
-    /// Returns a new color.
-    #[inline]
-    pub fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
-        IntermediateRGBA { red: red, green: green, blue: blue, alpha: alpha }
-    }
-}
-
-impl ToAnimatedValue for RGBA {
-    type AnimatedValue = IntermediateRGBA;
-
-    #[inline]
-    fn to_animated_value(self) -> Self::AnimatedValue {
-        IntermediateRGBA::new(
-            self.red_f32(),
-            self.green_f32(),
-            self.blue_f32(),
-            self.alpha_f32(),
-        )
-    }
-
-    #[inline]
-    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
-        // RGBA::from_floats clamps each component values.
-        RGBA::from_floats(
-            animated.red,
-            animated.green,
-            animated.blue,
-            animated.alpha,
-        )
-    }
-}
-
-/// Unlike Animatable for RGBA we don't clamp any component values.
-impl Animatable for IntermediateRGBA {
-    #[inline]
-    fn add_weighted(&self, other: &IntermediateRGBA, self_portion: f64, other_portion: f64)
-        -> Result<Self, ()> {
-        let mut alpha = self.alpha.add_weighted(&other.alpha, self_portion, other_portion)?;
-        if alpha <= 0. {
-            // Ideally we should return color value that only alpha component is
-            // 0, but this is what current gecko does.
-            Ok(IntermediateRGBA::transparent())
-        } else {
-            alpha = alpha.min(1.);
-            let red = (self.red * self.alpha).add_weighted(
-                &(other.red * other.alpha), self_portion, other_portion
-            )? * 1. / alpha;
-            let green = (self.green * self.alpha).add_weighted(
-                &(other.green * other.alpha), self_portion, other_portion
-            )? * 1. / alpha;
-            let blue = (self.blue * self.alpha).add_weighted(
-                &(other.blue * other.alpha), self_portion, other_portion
-            )? * 1. / alpha;
-            Ok(IntermediateRGBA::new(red, green, blue, alpha))
-        }
-    }
-}
-
-impl ComputeSquaredDistance for IntermediateRGBA {
-    #[inline]
-    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
-        let start = [ self.alpha, self.red * self.alpha, self.green * self.alpha, self.blue * self.alpha ];
-        let end = [ other.alpha, other.red * other.alpha, other.green * other.alpha, other.blue * other.alpha ];
-        start.iter().zip(&end).map(|(this, other)| this.compute_squared_distance(other)).sum()
-    }
-}
-
-impl ToAnimatedZero for IntermediateRGBA {
-    #[inline]
-    fn to_animated_zero(&self) -> Result<Self, ()> {
-        Ok(IntermediateRGBA::transparent())
-    }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub struct IntermediateColor {
-    color: IntermediateRGBA,
-    foreground_ratio: f32,
-}
-
-impl IntermediateColor {
-    fn currentcolor() -> Self {
-        IntermediateColor {
-            color: IntermediateRGBA::transparent(),
-            foreground_ratio: 1.,
-        }
-    }
-
-    /// Returns a transparent intermediate color.
-    pub fn transparent() -> Self {
-        IntermediateColor {
-            color: IntermediateRGBA::transparent(),
-            foreground_ratio: 0.,
-        }
-    }
-
-    fn is_currentcolor(&self) -> bool {
-        self.foreground_ratio >= 1.
-    }
-
-    fn is_numeric(&self) -> bool {
-        self.foreground_ratio <= 0.
-    }
-
-    fn effective_intermediate_rgba(&self) -> IntermediateRGBA {
-        IntermediateRGBA {
-            alpha: self.color.alpha * (1. - self.foreground_ratio),
-            .. self.color
-        }
-    }
-}
-
-impl ToAnimatedValue for Color {
-    type AnimatedValue = IntermediateColor;
-
-    #[inline]
-    fn to_animated_value(self) -> Self::AnimatedValue {
-        IntermediateColor {
-            color: self.color.to_animated_value(),
-            foreground_ratio: self.foreground_ratio as f32 * (1. / 255.),
-        }
-    }
-
-    #[inline]
-    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
-        Color {
-            color: RGBA::from_animated_value(animated.color),
-            foreground_ratio: (animated.foreground_ratio * 255.).round() as u8,
-        }
-    }
-}
-
-impl Animatable for IntermediateColor {
-    #[inline]
-    fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
-        // Common cases are interpolating between two numeric colors,
-        // two currentcolors, and a numeric color and a currentcolor.
-        //
-        // Note: this algorithm assumes self_portion + other_portion
-        // equals to one, so it may be broken for additive operation.
-        // To properly support additive color interpolation, we would
-        // need two ratio fields in computed color types.
-        if self.foreground_ratio == other.foreground_ratio {
-            if self.is_currentcolor() {
-                Ok(IntermediateColor::currentcolor())
-            } else {
-                Ok(IntermediateColor {
-                    color: self.color.add_weighted(&other.color, self_portion, other_portion)?,
-                    foreground_ratio: self.foreground_ratio,
-                })
-            }
-        } else if self.is_currentcolor() && other.is_numeric() {
-            Ok(IntermediateColor {
-                color: other.color,
-                foreground_ratio: self_portion as f32,
-            })
-        } else if self.is_numeric() && other.is_currentcolor() {
-            Ok(IntermediateColor {
-                color: self.color,
-                foreground_ratio: other_portion as f32,
-            })
-        } else {
-            // For interpolating between two complex colors, we need to
-            // generate colors with effective alpha value.
-            let self_color = self.effective_intermediate_rgba();
-            let other_color = other.effective_intermediate_rgba();
-            let color = self_color.add_weighted(&other_color, self_portion, other_portion)?;
-            // Then we compute the final foreground ratio, and derive
-            // the final alpha value from the effective alpha value.
-            let foreground_ratio = self.foreground_ratio
-                .add_weighted(&other.foreground_ratio, self_portion, other_portion)?;
-            let alpha = color.alpha / (1. - foreground_ratio);
-            Ok(IntermediateColor {
-                color: IntermediateRGBA {
-                    alpha: alpha,
-                    .. color
-                },
-                foreground_ratio: foreground_ratio,
-            })
-        }
-    }
-}
-
-impl ComputeSquaredDistance for IntermediateColor {
-    #[inline]
-    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
-        // All comments in add_weighted also applies here.
-        if self.foreground_ratio == other.foreground_ratio {
-            if self.is_currentcolor() {
-                Ok(SquaredDistance::Value(0.))
-            } else {
-                self.color.compute_squared_distance(&other.color)
-            }
-        } else if self.is_currentcolor() && other.is_numeric() {
-            Ok(
-                IntermediateRGBA::transparent().compute_squared_distance(&other.color)? +
-                SquaredDistance::Value(1.),
-            )
-        } else if self.is_numeric() && other.is_currentcolor() {
-            Ok(
-                self.color.compute_squared_distance(&IntermediateRGBA::transparent())? +
-                SquaredDistance::Value(1.),
-            )
-        } else {
-            let self_color = self.effective_intermediate_rgba();
-            let other_color = other.effective_intermediate_rgba();
-            Ok(
-                self_color.compute_squared_distance(&other_color)? +
-                self.foreground_ratio.compute_squared_distance(&other.foreground_ratio)?,
-            )
-        }
-    }
-}
-
-impl ToAnimatedZero for IntermediateColor {
-    #[inline]
-    fn to_animated_zero(&self) -> Result<Self, ()> { Err(()) }
-}
-
 /// Animatable SVGPaint
-pub type IntermediateSVGPaint = SVGPaint<IntermediateRGBA, ComputedUrl>;
+pub type IntermediateSVGPaint = SVGPaint<AnimatedRGBA, ComputedUrl>;
 
 /// Animatable SVGPaintKind
-pub type IntermediateSVGPaintKind = SVGPaintKind<IntermediateRGBA, ComputedUrl>;
+pub type IntermediateSVGPaintKind = SVGPaintKind<AnimatedRGBA, ComputedUrl>;
 
 impl Animatable for IntermediateSVGPaint {
     #[inline]
     fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
         Ok(IntermediateSVGPaint {
             kind: self.kind.add_weighted(&other.kind, self_portion, other_portion)?,
             fallback: self.fallback.add_weighted(&other.fallback, self_portion, other_portion)?,
         })
--- a/servo/components/style/properties/longhand/background.mako.rs
+++ b/servo/components/style/properties/longhand/background.mako.rs
@@ -1,24 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("Background", inherited=False) %>
 
-${helpers.predefined_type("background-color", "Color",
+${helpers.predefined_type(
+    "background-color",
+    "Color",
     "computed_value::T::transparent()",
     initial_specified_value="SpecifiedValue::transparent()",
     spec="https://drafts.csswg.org/css-backgrounds/#background-color",
-    animation_value_type="IntermediateColor",
+    animation_value_type="AnimatedColor",
     ignored_when_colors_disabled=True,
     allow_quirks=True,
-    flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER")}
+    flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
+)}
 
 ${helpers.predefined_type("background-image", "ImageLayer",
     initial_value="Either::First(None_)",
     initial_specified_value="Either::First(None_)",
     spec="https://drafts.csswg.org/css-backgrounds/#the-background-image",
     vector="True",
     animation_value_type="discrete",
     has_uncacheable_values="True" if product == "gecko" else "False",
--- a/servo/components/style/properties/longhand/border.mako.rs
+++ b/servo/components/style/properties/longhand/border.mako.rs
@@ -15,25 +15,27 @@
         else:
             return "https://drafts.csswg.org/css-backgrounds/#border-%s-%s" % (side[0], kind)
 %>
 % for side in ALL_SIDES:
     <%
         side_name = side[0]
         is_logical = side[1]
     %>
-    ${helpers.predefined_type("border-%s-color" % side_name, "Color",
-                              "computed_value::T::currentcolor()",
-                              alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-color"),
-                              spec=maybe_logical_spec(side, "color"),
-                              animation_value_type="IntermediateColor",
-                              logical=is_logical,
-                              allow_quirks=not is_logical,
-                              flags="APPLIES_TO_FIRST_LETTER",
-                              ignored_when_colors_disabled=True)}
+    ${helpers.predefined_type(
+        "border-%s-color" % side_name, "Color",
+        "computed_value::T::currentcolor()",
+        alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-color"),
+        spec=maybe_logical_spec(side, "color"),
+        animation_value_type="AnimatedColor",
+        logical=is_logical,
+        allow_quirks=not is_logical,
+        flags="APPLIES_TO_FIRST_LETTER",
+        ignored_when_colors_disabled=True,
+    )}
 
     ${helpers.predefined_type("border-%s-style" % side_name, "BorderStyle",
                               "specified::BorderStyle::none",
                               alias=maybe_moz_logical_alias(product, side, "-moz-border-%s-style"),
                               spec=maybe_logical_spec(side, "style"),
                               flags="APPLIES_TO_FIRST_LETTER",
                               animation_value_type="discrete" if not is_logical else "none",
                               logical=is_logical)}
--- a/servo/components/style/properties/longhand/color.mako.rs
+++ b/servo/components/style/properties/longhand/color.mako.rs
@@ -4,17 +4,17 @@
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
 <% data.new_style_struct("Color", inherited=True) %>
 
 <% from data import to_rust_ident %>
 
 <%helpers:longhand name="color" need_clone="True"
-                   animation_value_type="IntermediateRGBA"
+                   animation_value_type="AnimatedRGBA"
                    ignored_when_colors_disabled="True"
                    flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER"
                    spec="https://drafts.csswg.org/css-color/#color">
     use cssparser::RGBA;
     use values::specified::{AllowQuirks, Color};
 
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
--- a/servo/components/style/properties/longhand/column.mako.rs
+++ b/servo/components/style/properties/longhand/column.mako.rs
@@ -43,22 +43,28 @@
                           initial_specified_value="specified::BorderSideWidth::Medium",
                           computed_type="::values::computed::NonNegativeAu",
                           products="gecko",
                           spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-width",
                           animation_value_type="NonNegativeAu",
                           extra_prefixes="moz")}
 
 // https://drafts.csswg.org/css-multicol-1/#crc
-${helpers.predefined_type("column-rule-color", "Color",
-                          "computed_value::T::currentcolor()",
-                          initial_specified_value="specified::Color::currentcolor()",
-                          products="gecko", animation_value_type="IntermediateColor", extra_prefixes="moz",
-                          need_clone=True, ignored_when_colors_disabled=True,
-                          spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-color")}
+${helpers.predefined_type(
+    "column-rule-color",
+    "Color",
+    "computed_value::T::currentcolor()",
+    initial_specified_value="specified::Color::currentcolor()",
+    products="gecko",
+    animation_value_type="AnimatedColor",
+    extra_prefixes="moz",
+    need_clone=True,
+    ignored_when_colors_disabled=True,
+    spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-color",
+)}
 
 ${helpers.single_keyword("column-span", "none all",
                          products="gecko", animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-multicol/#propdef-column-span")}
 
 ${helpers.single_keyword("column-rule-style",
                          "none hidden dotted dashed solid double groove ridge inset outset",
                          products="gecko", extra_prefixes="moz",
--- a/servo/components/style/properties/longhand/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_text.mako.rs
@@ -700,49 +700,60 @@
                     }
                 };
                 result as u8
             }
         }
     % endif
 </%helpers:longhand>
 
-${helpers.predefined_type("text-emphasis-color", "Color",
-                          "computed_value::T::currentcolor()",
-                          initial_specified_value="specified::Color::currentcolor()",
-                          products="gecko", animation_value_type="IntermediateColor",
-                          need_clone=True, ignored_when_colors_disabled=True,
-                          spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-color")}
-
+${helpers.predefined_type(
+    "text-emphasis-color",
+    "Color",
+    "computed_value::T::currentcolor()",
+    initial_specified_value="specified::Color::currentcolor()",
+    products="gecko",
+    animation_value_type="AnimatedColor",
+    need_clone=True,
+    ignored_when_colors_disabled=True,
+    spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-color",
+)}
 
 ${helpers.predefined_type(
     "-moz-tab-size", "length::NonNegativeLengthOrNumber",
     "::values::Either::Second(From::from(8.0))",
     products="gecko", animation_value_type="::values::computed::length::NonNegativeLengthOrNumber",
     spec="https://drafts.csswg.org/css-text-3/#tab-size-property")}
 
 
 // CSS Compatibility
 // https://compat.spec.whatwg.org
 ${helpers.predefined_type(
-    "-webkit-text-fill-color", "Color",
+    "-webkit-text-fill-color",
+    "Color",
     "computed_value::T::currentcolor()",
-    products="gecko", animation_value_type="IntermediateColor",
-    need_clone=True, ignored_when_colors_disabled=True,
+    products="gecko",
+    animation_value_type="AnimatedColor",
+    need_clone=True,
+    ignored_when_colors_disabled=True,
     flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
-    spec="https://compat.spec.whatwg.org/#the-webkit-text-fill-color")}
+    spec="https://compat.spec.whatwg.org/#the-webkit-text-fill-color",
+)}
 
 ${helpers.predefined_type(
-    "-webkit-text-stroke-color", "Color",
+    "-webkit-text-stroke-color",
+    "Color",
     "computed_value::T::currentcolor()",
     initial_specified_value="specified::Color::currentcolor()",
-    products="gecko", animation_value_type="IntermediateColor",
+    products="gecko",
+    animation_value_type="AnimatedColor",
     need_clone=True, ignored_when_colors_disabled=True,
     flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
-    spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-color")}
+    spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-color",
+)}
 
 ${helpers.predefined_type("-webkit-text-stroke-width",
                           "BorderSideWidth",
                           "::values::computed::NonNegativeAu::from_px(0)",
                           initial_specified_value="specified::BorderSideWidth::Length(specified::Length::zero())",
                           computed_type="::values::computed::NonNegativeAu",
                           products="gecko",
                           flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
--- a/servo/components/style/properties/longhand/outline.mako.rs
+++ b/servo/components/style/properties/longhand/outline.mako.rs
@@ -5,21 +5,26 @@
 <%namespace name="helpers" file="/helpers.mako.rs" />
 <% from data import Method %>
 
 <% data.new_style_struct("Outline",
                          inherited=False,
                          additional_methods=[Method("outline_has_nonzero_width", "bool")]) %>
 
 // TODO(pcwalton): `invert`
-${helpers.predefined_type("outline-color", "Color", "computed_value::T::currentcolor()",
-                          initial_specified_value="specified::Color::currentcolor()",
-                          animation_value_type="IntermediateColor", need_clone=True,
-                          ignored_when_colors_disabled=True,
-                          spec="https://drafts.csswg.org/css-ui/#propdef-outline-color")}
+${helpers.predefined_type(
+    "outline-color",
+    "Color",
+    "computed_value::T::currentcolor()",
+    initial_specified_value="specified::Color::currentcolor()",
+    animation_value_type="AnimatedColor",
+    need_clone=True,
+    ignored_when_colors_disabled=True,
+    spec="https://drafts.csswg.org/css-ui/#propdef-outline-color",
+)}
 
 <%helpers:longhand name="outline-style" animation_value_type="discrete"
                    spec="https://drafts.csswg.org/css-ui/#propdef-outline-style">
     use values::specified::BorderStyle;
 
     pub type SpecifiedValue = Either<Auto, BorderStyle>;
 
     impl SpecifiedValue {
--- a/servo/components/style/properties/longhand/pointing.mako.rs
+++ b/servo/components/style/properties/longhand/pointing.mako.rs
@@ -173,16 +173,18 @@
 ${helpers.single_keyword("-moz-user-focus",
                          "none ignore normal select-after select-before select-menu select-same select-all",
                          products="gecko", gecko_ffi_name="mUserFocus",
                          gecko_enum_prefix="StyleUserFocus",
                          gecko_inexhaustive=True,
                          animation_value_type="discrete",
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-user-focus)")}
 
-${helpers.predefined_type("caret-color",
-                          "ColorOrAuto",
-                          "Either::Second(Auto)",
-                          spec="https://drafts.csswg.org/css-ui/#caret-color",
-                          animation_value_type="Either<IntermediateColor, Auto>",
-                          boxed=True,
-                          ignored_when_colors_disabled=True,
-                          products="gecko")}
+${helpers.predefined_type(
+    "caret-color",
+    "ColorOrAuto",
+    "Either::Second(Auto)",
+    spec="https://drafts.csswg.org/css-ui/#caret-color",
+    animation_value_type="Either<AnimatedColor, Auto>",
+    boxed=True,
+    ignored_when_colors_disabled=True,
+    products="gecko",
+)}
--- a/servo/components/style/properties/longhand/svg.mako.rs
+++ b/servo/components/style/properties/longhand/svg.mako.rs
@@ -15,46 +15,52 @@
 
 ${helpers.single_keyword("vector-effect", "none non-scaling-stroke",
                          products="gecko", animation_value_type="discrete",
                          spec="https://www.w3.org/TR/SVGTiny12/painting.html#VectorEffectProperty")}
 
 // Section 13 - Gradients and Patterns
 
 ${helpers.predefined_type(
-    "stop-color", "RGBAColor",
+    "stop-color",
+    "RGBAColor",
     "RGBA::new(0, 0, 0, 255)",
     products="gecko",
-    animation_value_type="IntermediateRGBA",
-    spec="https://www.w3.org/TR/SVGTiny12/painting.html#StopColorProperty")}
+    animation_value_type="AnimatedRGBA",
+    spec="https://www.w3.org/TR/SVGTiny12/painting.html#StopColorProperty",
+)}
 
 ${helpers.predefined_type("stop-opacity", "Opacity", "1.0",
                           products="gecko",
                           animation_value_type="ComputedValue",
                           spec="https://www.w3.org/TR/SVGTiny12/painting.html#propdef-stop-opacity")}
 
 // Section 15 - Filter Effects
 
 ${helpers.predefined_type(
-    "flood-color", "RGBAColor",
+    "flood-color",
+    "RGBAColor",
     "RGBA::new(0, 0, 0, 255)",
     products="gecko",
-    animation_value_type="IntermediateRGBA",
-    spec="https://www.w3.org/TR/SVG/filters.html#FloodColorProperty")}
+    animation_value_type="AnimatedRGBA",
+    spec="https://www.w3.org/TR/SVG/filters.html#FloodColorProperty",
+)}
 
 ${helpers.predefined_type("flood-opacity", "Opacity",
                           "1.0", products="gecko", animation_value_type="ComputedValue",
                           spec="https://www.w3.org/TR/SVG/filters.html#FloodOpacityProperty")}
 
 ${helpers.predefined_type(
-    "lighting-color", "RGBAColor",
+    "lighting-color",
+    "RGBAColor",
     "RGBA::new(255, 255, 255, 255)",
     products="gecko",
-    animation_value_type="IntermediateRGBA",
-    spec="https://www.w3.org/TR/SVG/filters.html#LightingColorProperty")}
+    animation_value_type="AnimatedRGBA",
+    spec="https://www.w3.org/TR/SVG/filters.html#LightingColorProperty",
+)}
 
 // CSS Masking Module Level 1
 // https://drafts.fxtf.org/css-masking
 ${helpers.single_keyword("mask-type", "luminance alpha",
                          products="gecko", animation_value_type="discrete",
                          spec="https://drafts.fxtf.org/css-masking/#propdef-mask-type")}
 
 ${helpers.predefined_type(
--- a/servo/components/style/properties/longhand/text.mako.rs
+++ b/servo/components/style/properties/longhand/text.mako.rs
@@ -263,24 +263,26 @@
 ${helpers.single_keyword("text-decoration-style",
                          "solid double dotted dashed wavy -moz-none",
                          products="gecko",
                          animation_value_type="discrete",
                          flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
                          spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-style")}
 
 ${helpers.predefined_type(
-    "text-decoration-color", "Color",
+    "text-decoration-color",
+    "Color",
     "computed_value::T::currentcolor()",
     initial_specified_value="specified::Color::currentcolor()",
     products="gecko",
-    animation_value_type="IntermediateColor",
+    animation_value_type="AnimatedColor",
     ignored_when_colors_disabled=True,
     flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER",
-    spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-color")}
+    spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-color",
+)}
 
 ${helpers.predefined_type(
     "initial-letter",
     "InitialLetter",
     "computed::InitialLetter::normal()",
     initial_specified_value="specified::InitialLetter::normal()",
     animation_value_type="discrete",
     products="gecko",
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/animated/color.rs
@@ -0,0 +1,214 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Animated types for CSS colors.
+
+use properties::animated_properties::Animatable;
+use values::animated::ToAnimatedZero;
+use values::distance::{ComputeSquaredDistance, SquaredDistance};
+
+/// An animated RGBA color.
+///
+/// Unlike in computed values, each component value may exceed the
+/// range `[0.0, 1.0]`.
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct RGBA {
+    /// The red component.
+    pub red: f32,
+    /// The green component.
+    pub green: f32,
+    /// The blue component.
+    pub blue: f32,
+    /// The alpha component.
+    pub alpha: f32,
+}
+
+impl RGBA {
+    /// Returns a transparent color.
+    #[inline]
+    pub fn transparent() -> Self {
+        Self::new(0., 0., 0., 0.)
+    }
+
+    /// Returns a new color.
+    #[inline]
+    pub fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
+        RGBA { red: red, green: green, blue: blue, alpha: alpha }
+    }
+}
+
+/// Unlike Animatable for computed colors, we don't clamp any component values.
+///
+/// FIXME(nox): Why do computed colors even implement Animatable?
+impl Animatable for RGBA {
+    #[inline]
+    fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
+        let mut alpha = self.alpha.add_weighted(&other.alpha, self_portion, other_portion)?;
+        if alpha <= 0. {
+            // Ideally we should return color value that only alpha component is
+            // 0, but this is what current gecko does.
+            return Ok(RGBA::transparent());
+        }
+
+        alpha = alpha.min(1.);
+        let red = (self.red * self.alpha).add_weighted(
+            &(other.red * other.alpha), self_portion, other_portion
+        )? * 1. / alpha;
+        let green = (self.green * self.alpha).add_weighted(
+            &(other.green * other.alpha), self_portion, other_portion
+        )? * 1. / alpha;
+        let blue = (self.blue * self.alpha).add_weighted(
+            &(other.blue * other.alpha), self_portion, other_portion
+        )? * 1. / alpha;
+
+        Ok(RGBA::new(red, green, blue, alpha))
+    }
+}
+
+impl ComputeSquaredDistance for RGBA {
+    #[inline]
+    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
+        let start = [ self.alpha, self.red * self.alpha, self.green * self.alpha, self.blue * self.alpha ];
+        let end = [ other.alpha, other.red * other.alpha, other.green * other.alpha, other.blue * other.alpha ];
+        start.iter().zip(&end).map(|(this, other)| this.compute_squared_distance(other)).sum()
+    }
+}
+
+impl ToAnimatedZero for RGBA {
+    #[inline]
+    fn to_animated_zero(&self) -> Result<Self, ()> {
+        Ok(RGBA::transparent())
+    }
+}
+
+#[allow(missing_docs)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct Color {
+    pub color: RGBA,
+    pub foreground_ratio: f32,
+}
+
+impl Color {
+    fn currentcolor() -> Self {
+        Color {
+            color: RGBA::transparent(),
+            foreground_ratio: 1.,
+        }
+    }
+
+    /// Returns a transparent intermediate color.
+    pub fn transparent() -> Self {
+        Color {
+            color: RGBA::transparent(),
+            foreground_ratio: 0.,
+        }
+    }
+
+    fn is_currentcolor(&self) -> bool {
+        self.foreground_ratio >= 1.
+    }
+
+    fn is_numeric(&self) -> bool {
+        self.foreground_ratio <= 0.
+    }
+
+    fn effective_intermediate_rgba(&self) -> RGBA {
+        RGBA {
+            alpha: self.color.alpha * (1. - self.foreground_ratio),
+            .. self.color
+        }
+    }
+}
+
+impl Animatable for Color {
+    #[inline]
+    fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
+        // Common cases are interpolating between two numeric colors,
+        // two currentcolors, and a numeric color and a currentcolor.
+        //
+        // Note: this algorithm assumes self_portion + other_portion
+        // equals to one, so it may be broken for additive operation.
+        // To properly support additive color interpolation, we would
+        // need two ratio fields in computed color types.
+        if self.foreground_ratio == other.foreground_ratio {
+            if self.is_currentcolor() {
+                Ok(Color::currentcolor())
+            } else {
+                Ok(Color {
+                    color: self.color.add_weighted(&other.color, self_portion, other_portion)?,
+                    foreground_ratio: self.foreground_ratio,
+                })
+            }
+        } else if self.is_currentcolor() && other.is_numeric() {
+            Ok(Color {
+                color: other.color,
+                foreground_ratio: self_portion as f32,
+            })
+        } else if self.is_numeric() && other.is_currentcolor() {
+            Ok(Color {
+                color: self.color,
+                foreground_ratio: other_portion as f32,
+            })
+        } else {
+            // For interpolating between two complex colors, we need to
+            // generate colors with effective alpha value.
+            let self_color = self.effective_intermediate_rgba();
+            let other_color = other.effective_intermediate_rgba();
+            let color = self_color.add_weighted(&other_color, self_portion, other_portion)?;
+            // Then we compute the final foreground ratio, and derive
+            // the final alpha value from the effective alpha value.
+            let foreground_ratio = self.foreground_ratio
+                .add_weighted(&other.foreground_ratio, self_portion, other_portion)?;
+            let alpha = color.alpha / (1. - foreground_ratio);
+            Ok(Color {
+                color: RGBA {
+                    alpha: alpha,
+                    .. color
+                },
+                foreground_ratio: foreground_ratio,
+            })
+        }
+    }
+}
+
+impl ComputeSquaredDistance for Color {
+    #[inline]
+    fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
+        // All comments in add_weighted also applies here.
+        if self.foreground_ratio == other.foreground_ratio {
+            if self.is_currentcolor() {
+                Ok(SquaredDistance::Value(0.))
+            } else {
+                self.color.compute_squared_distance(&other.color)
+            }
+        } else if self.is_currentcolor() && other.is_numeric() {
+            Ok(
+                RGBA::transparent().compute_squared_distance(&other.color)? +
+                SquaredDistance::Value(1.),
+            )
+        } else if self.is_numeric() && other.is_currentcolor() {
+            Ok(
+                self.color.compute_squared_distance(&RGBA::transparent())? +
+                SquaredDistance::Value(1.),
+            )
+        } else {
+            let self_color = self.effective_intermediate_rgba();
+            let other_color = other.effective_intermediate_rgba();
+            Ok(
+                self_color.compute_squared_distance(&other_color)? +
+                self.foreground_ratio.compute_squared_distance(&other.foreground_ratio)?,
+            )
+        }
+    }
+}
+
+impl ToAnimatedZero for Color {
+    #[inline]
+    fn to_animated_zero(&self) -> Result<Self, ()> {
+        /// FIXME(nox): This does not look correct to me.
+        Err(())
+    }
+}
--- a/servo/components/style/values/animated/effects.rs
+++ b/servo/components/style/values/animated/effects.rs
@@ -1,22 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Animated types for CSS values related to effects.
 
-use properties::animated_properties::{Animatable, IntermediateColor};
+use properties::animated_properties::Animatable;
 use properties::longhands::box_shadow::computed_value::T as ComputedBoxShadowList;
 use properties::longhands::filter::computed_value::T as ComputedFilterList;
 use properties::longhands::text_shadow::computed_value::T as ComputedTextShadowList;
 use std::cmp;
 #[cfg(not(feature = "gecko"))]
 use values::Impossible;
 use values::animated::{ToAnimatedValue, ToAnimatedZero};
+use values::animated::color::Color;
 use values::computed::{Angle, NonNegativeNumber};
 use values::computed::length::{Length, NonNegativeLength};
 use values::distance::{ComputeSquaredDistance, SquaredDistance};
 use values::generics::effects::BoxShadow as GenericBoxShadow;
 use values::generics::effects::Filter as GenericFilter;
 use values::generics::effects::SimpleShadow as GenericSimpleShadow;
 
 /// An animated value for the `box-shadow` property.
@@ -28,33 +29,33 @@ pub type TextShadowList = ShadowList<Sim
 /// An animated value for shadow lists.
 ///
 /// https://drafts.csswg.org/css-transitions/#animtype-shadow-list
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Debug, PartialEq)]
 pub struct ShadowList<Shadow>(Vec<Shadow>);
 
 /// An animated value for a single `box-shadow`.
-pub type BoxShadow = GenericBoxShadow<IntermediateColor, Length, NonNegativeLength, Length>;
+pub type BoxShadow = GenericBoxShadow<Color, Length, NonNegativeLength, Length>;
 
 /// An animated value for the `filter` property.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Debug, PartialEq)]
 pub struct FilterList(pub Vec<Filter>);
 
 /// An animated value for a single `filter`.
 #[cfg(feature = "gecko")]
 pub type Filter = GenericFilter<Angle, NonNegativeNumber, NonNegativeLength, SimpleShadow>;
 
 /// An animated value for a single `filter`.
 #[cfg(not(feature = "gecko"))]
 pub type Filter = GenericFilter<Angle, NonNegativeNumber, NonNegativeLength, Impossible>;
 
 /// An animated value for the `drop-shadow()` filter.
-pub type SimpleShadow = GenericSimpleShadow<IntermediateColor, Length, NonNegativeLength>;
+pub type SimpleShadow = GenericSimpleShadow<Color, Length, NonNegativeLength>;
 
 impl ToAnimatedValue for ComputedBoxShadowList {
     type AnimatedValue = BoxShadowList;
 
     #[inline]
     fn to_animated_value(self) -> Self::AnimatedValue {
         ShadowList(self.0.to_animated_value())
     }
@@ -226,15 +227,15 @@ impl Animatable for SimpleShadow {
         })
     }
 }
 
 impl ToAnimatedZero for SimpleShadow {
     #[inline]
     fn to_animated_zero(&self) -> Result<Self, ()> {
         Ok(SimpleShadow {
-            color: IntermediateColor::transparent(),
+            color: Color::transparent(),
             horizontal: self.horizontal.to_animated_zero()?,
             vertical: self.vertical.to_animated_zero()?,
             blur: self.blur.to_animated_zero()?,
         })
     }
 }
--- a/servo/components/style/values/animated/mod.rs
+++ b/servo/components/style/values/animated/mod.rs
@@ -19,16 +19,17 @@ use values::computed::GreaterThanOrEqual
 use values::computed::MaxLength as ComputedMaxLength;
 use values::computed::MozLength as ComputedMozLength;
 use values::computed::NonNegativeAu;
 use values::computed::NonNegativeLengthOrPercentage as ComputedNonNegativeLengthOrPercentage;
 use values::computed::NonNegativeNumber as ComputedNonNegativeNumber;
 use values::computed::PositiveInteger as ComputedPositiveInteger;
 use values::specified::url::SpecifiedUrl;
 
+pub mod color;
 pub mod effects;
 
 /// Conversion between computed values and intermediate values for animations.
 ///
 /// Notably, colors are represented as four floats during animations.
 pub trait ToAnimatedValue {
     /// The type of the animated value.
     type AnimatedValue;
--- a/servo/components/style/values/computed/border.rs
+++ b/servo/components/style/values/computed/border.rs
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Computed types for CSS values related to borders.
 
+use values::animated::ToAnimatedZero;
 use values::computed::{Number, NumberOrPercentage};
 use values::computed::length::LengthOrPercentage;
 use values::generics::border::BorderCornerRadius as GenericBorderCornerRadius;
 use values::generics::border::BorderImageSideWidth as GenericBorderImageSideWidth;
 use values::generics::border::BorderImageSlice as GenericBorderImageSlice;
 use values::generics::border::BorderRadius as GenericBorderRadius;
 use values::generics::rect::Rect;
 
@@ -29,8 +30,16 @@ pub type BorderCornerRadius = GenericBor
 
 impl BorderImageSideWidth {
     /// Returns `1`.
     #[inline]
     pub fn one() -> Self {
         GenericBorderImageSideWidth::Number(1.)
     }
 }
+
+impl ToAnimatedZero for BorderCornerRadius {
+    #[inline]
+    fn to_animated_zero(&self) -> Result<Self, ()> {
+        /// FIXME(nox): Why?
+        Err(())
+    }
+}
--- a/servo/components/style/values/computed/color.rs
+++ b/servo/components/style/values/computed/color.rs
@@ -2,37 +2,34 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Computed color values.
 
 use cssparser::{Color as CSSParserColor, RGBA};
 use std::fmt;
 use style_traits::ToCss;
+use values::animated::ToAnimatedValue;
+use values::animated::color::{Color as AnimatedColor, RGBA as AnimatedRGBA};
 
 /// This struct represents a combined color from a numeric color and
 /// the current foreground color (currentcolor keyword).
 /// Conceptually, the formula is "color * (1 - p) + currentcolor * p"
 /// where p is foreground_ratio.
 #[derive(Clone, Copy, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub struct Color {
     /// RGBA color.
     pub color: RGBA,
     /// The ratio of currentcolor in complex color.
     pub foreground_ratio: u8,
 }
 
-fn blend_color_component(bg: u8, fg: u8, fg_alpha: u8) -> u8 {
-    let bg_ratio = (u8::max_value() - fg_alpha) as u32;
-    let fg_ratio = fg_alpha as u32;
-    let color = bg as u32 * bg_ratio + fg as u32 * fg_ratio;
-    // Rounding divide the number by 255
-    ((color + 127) / 255) as u8
-}
+/// Computed value type for the specified RGBAColor.
+pub type RGBAColor = RGBA;
 
 impl Color {
     /// Returns a numeric color representing the given RGBA value.
     pub fn rgba(rgba: RGBA) -> Color {
         Color {
             color: rgba,
             foreground_ratio: 0,
         }
@@ -67,16 +64,25 @@ impl Color {
         // Common cases that the complex color is either pure numeric
         // color or pure currentcolor.
         if self.is_numeric() {
             return self.color;
         }
         if self.is_currentcolor() {
             return fg_color.clone();
         }
+
+        fn blend_color_component(bg: u8, fg: u8, fg_alpha: u8) -> u8 {
+            let bg_ratio = (u8::max_value() - fg_alpha) as u32;
+            let fg_ratio = fg_alpha as u32;
+            let color = bg as u32 * bg_ratio + fg as u32 * fg_ratio;
+            // Rounding divide the number by 255
+            ((color + 127) / 255) as u8
+        }
+
         // Common case that alpha channel is equal (usually both are opaque).
         let fg_ratio = self.foreground_ratio;
         if self.color.alpha == fg_color.alpha {
             let r = blend_color_component(self.color.red, fg_color.red, fg_ratio);
             let g = blend_color_component(self.color.green, fg_color.green, fg_ratio);
             let b = blend_color_component(self.color.blue, fg_color.blue, fg_ratio);
             return RGBA::new(r, g, b, fg_color.alpha);
         }
@@ -135,10 +141,52 @@ impl ToCss for Color {
         } else if self.is_currentcolor() {
             CSSParserColor::CurrentColor.to_css(dest)
         } else {
             Ok(())
         }
     }
 }
 
-/// Computed value type for the specified RGBAColor.
-pub type RGBAColor = RGBA;
+impl ToAnimatedValue for Color {
+    type AnimatedValue = AnimatedColor;
+
+    #[inline]
+    fn to_animated_value(self) -> Self::AnimatedValue {
+        AnimatedColor {
+            color: self.color.to_animated_value(),
+            foreground_ratio: self.foreground_ratio as f32 * (1. / 255.),
+        }
+    }
+
+    #[inline]
+    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
+        Color {
+            color: RGBA::from_animated_value(animated.color),
+            foreground_ratio: (animated.foreground_ratio * 255.).round() as u8,
+        }
+    }
+}
+
+impl ToAnimatedValue for RGBA {
+    type AnimatedValue = AnimatedRGBA;
+
+    #[inline]
+    fn to_animated_value(self) -> Self::AnimatedValue {
+        AnimatedRGBA::new(
+            self.red_f32(),
+            self.green_f32(),
+            self.blue_f32(),
+            self.alpha_f32(),
+        )
+    }
+
+    #[inline]
+    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
+        // RGBA::from_floats clamps each component values.
+        RGBA::from_floats(
+            animated.red,
+            animated.green,
+            animated.blue,
+            animated.alpha,
+        )
+    }
+}
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -44,16 +44,17 @@ pub use self::image::{Gradient, Gradient
 pub use self::gecko::ScrollSnapPoint;
 pub use self::rect::LengthOrNumberRect;
 pub use super::{Auto, Either, None_};
 pub use super::specified::BorderStyle;
 pub use super::generics::grid::GridLine;
 pub use self::length::{CalcLengthOrPercentage, Length, LengthOrNone, LengthOrNumber, LengthOrPercentage};
 pub use self::length::{LengthOrPercentageOrAuto, LengthOrPercentageOrNone, MaxLength, MozLength};
 pub use self::length::NonNegativeLengthOrPercentage;
+pub use self::percentage::Percentage;
 pub use self::position::Position;
 pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind, SVGStrokeDashArray, SVGWidth};
 pub use self::text::{InitialLetter, LetterSpacing, LineHeight, WordSpacing};
 pub use self::transform::{TimingFunction, TransformOrigin};
 
 #[cfg(feature = "gecko")]
 pub mod align;
 pub mod background;
@@ -61,16 +62,17 @@ pub mod basic_shape;
 pub mod border;
 pub mod color;
 pub mod effects;
 pub mod flex;
 pub mod image;
 #[cfg(feature = "gecko")]
 pub mod gecko;
 pub mod length;
+pub mod percentage;
 pub mod position;
 pub mod rect;
 pub mod svg;
 pub mod text;
 pub mod transform;
 
 /// A `Context` is all the data a specified value could ever need to compute
 /// itself and be transformed to a computed value.
@@ -653,55 +655,16 @@ impl NonNegativeAu {
 
 impl From<Au> for NonNegativeAu {
     #[inline]
     fn from(au: Au) -> NonNegativeAu {
         NonNegative::<Au>(au)
     }
 }
 
-/// A computed `<percentage>` value.
-#[derive(Clone, ComputeSquaredDistance, Copy, Debug, Default, HasViewportPercentage, PartialEq)]
-#[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))]
-pub struct Percentage(pub CSSFloat);
-
-impl Percentage {
-    /// 0%
-    #[inline]
-    pub fn zero() -> Self {
-        Percentage(0.)
-    }
-
-    /// 100%
-    #[inline]
-    pub fn hundred() -> Self {
-        Percentage(1.)
-    }
-}
-
-impl ToCss for Percentage {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        write!(dest, "{}%", self.0 * 100.)
-    }
-}
-
-impl ToComputedValue for specified::Percentage {
-    type ComputedValue = Percentage;
-
-    #[inline]
-    fn to_computed_value(&self, _: &Context) -> Percentage {
-        Percentage(self.get())
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &Percentage) -> Self {
-        specified::Percentage::new(computed.0)
-    }
-}
-
 /// The computed value of a CSS `url()`, resolved relative to the stylesheet URL.
 #[cfg(feature = "servo")]
 #[derive(Clone, Debug, HeapSizeOf, Serialize, Deserialize, PartialEq)]
 pub enum ComputedUrl {
     /// The `url()` was invalid or it wasn't specified by the user.
     Invalid(Arc<String>),
     /// The resolved `url()` relative to the stylesheet URL.
     Valid(ServoUrl),
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/computed/percentage.rs
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Computed percentages.
+
+use properties::animated_properties::Animatable;
+use std::fmt;
+use style_traits::ToCss;
+use values::CSSFloat;
+use values::animated::ToAnimatedZero;
+
+/// A computed percentage.
+#[derive(Clone, ComputeSquaredDistance, Copy, Debug, Default, HasViewportPercentage, PartialEq)]
+#[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))]
+pub struct Percentage(pub CSSFloat);
+
+impl Percentage {
+    /// 0%
+    #[inline]
+    pub fn zero() -> Self {
+        Percentage(0.)
+    }
+
+    /// 100%
+    #[inline]
+    pub fn hundred() -> Self {
+        Percentage(1.)
+    }
+}
+
+/// https://drafts.csswg.org/css-transitions/#animtype-percentage
+impl Animatable for Percentage {
+    #[inline]
+    fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
+        Ok(Percentage((self.0 as f64 * self_portion + other.0 as f64 * other_portion) as f32))
+    }
+}
+
+impl ToAnimatedZero for Percentage {
+    #[inline]
+    fn to_animated_zero(&self) -> Result<Self, ()> {
+        Ok(Percentage(0.))
+    }
+}
+
+impl ToCss for Percentage {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+    where
+        W: fmt::Write,
+    {
+        write!(dest, "{}%", self.0 * 100.)
+    }
+}
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -780,17 +780,17 @@ impl From<NoCalcLength> for LengthOrPerc
     fn from(len: NoCalcLength) -> Self {
         LengthOrPercentage::Length(len)
     }
 }
 
 impl From<Percentage> for LengthOrPercentage {
     #[inline]
     fn from(pc: Percentage) -> Self {
-        if pc.calc_clamping_mode.is_some() {
+        if pc.is_calc() {
             LengthOrPercentage::Calc(Box::new(CalcLengthOrPercentage {
                 percentage: Some(computed::Percentage(pc.get())),
                 .. Default::default()
             }))
         } else {
             LengthOrPercentage::Percentage(computed::Percentage(pc.get()))
         }
     }
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -39,16 +39,17 @@ pub use self::image::{ColorStop, EndingS
 pub use self::image::{GradientItem, GradientKind, Image, ImageLayer, MozImageRect};
 pub use self::length::{AbsoluteLength, CalcLengthOrPercentage, CharacterWidth};
 pub use self::length::{FontRelativeLength, Length, LengthOrNone, LengthOrNumber};
 pub use self::length::{LengthOrPercentage, LengthOrPercentageOrAuto};
 pub use self::length::{LengthOrPercentageOrNone, MaxLength, MozLength};
 pub use self::length::{NoCalcLength, ViewportPercentageLength};
 pub use self::length::NonNegativeLengthOrPercentage;
 pub use self::rect::LengthOrNumberRect;
+pub use self::percentage::Percentage;
 pub use self::position::{Position, PositionComponent};
 pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind, SVGStrokeDashArray, SVGWidth};
 pub use self::text::{InitialLetter, LetterSpacing, LineHeight, WordSpacing};
 pub use self::transform::{TimingFunction, TransformOrigin};
 pub use super::generics::grid::GridLine;
 pub use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent;
 
 #[cfg(feature = "gecko")]
@@ -60,16 +61,17 @@ pub mod calc;
 pub mod color;
 pub mod effects;
 pub mod flex;
 #[cfg(feature = "gecko")]
 pub mod gecko;
 pub mod grid;
 pub mod image;
 pub mod length;
+pub mod percentage;
 pub mod position;
 pub mod rect;
 pub mod svg;
 pub mod text;
 pub mod transform;
 
 /// Common handling for the specified value CSS url() values.
 pub mod url {
@@ -1042,126 +1044,8 @@ impl ToCss for Attr {
             dest.write_str("|")?;
         }
         serialize_identifier(&self.attribute, dest)?;
         dest.write_str(")")
     }
 }
 
 impl ComputedValueAsSpecified for Attr {}
-
-/// A percentage value.
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub struct Percentage {
-    /// The percentage value as a float.
-    ///
-    /// [0 .. 100%] maps to [0.0 .. 1.0]
-    value: CSSFloat,
-    /// If this percentage came from a calc() expression, this tells how
-    /// clamping should be done on the value.
-    calc_clamping_mode: Option<AllowedNumericType>,
-}
-
-no_viewport_percentage!(Percentage);
-
-impl ToCss for Percentage {
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
-        where W: fmt::Write,
-    {
-        if self.calc_clamping_mode.is_some() {
-            dest.write_str("calc(")?;
-        }
-
-        write!(dest, "{}%", self.value * 100.)?;
-
-        if self.calc_clamping_mode.is_some() {
-            dest.write_str(")")?;
-        }
-        Ok(())
-    }
-}
-
-impl Percentage {
-    /// Create a percentage from a numeric value.
-    pub fn new(value: CSSFloat) -> Self {
-        Self {
-            value,
-            calc_clamping_mode: None,
-        }
-    }
-
-    /// Get the underlying value for this float.
-    pub fn get(&self) -> CSSFloat {
-        self.calc_clamping_mode.map_or(self.value, |mode| mode.clamp(self.value))
-    }
-
-    /// Reverse this percentage, preserving calc-ness.
-    ///
-    /// For example: If it was 20%, convert it into 80%.
-    pub fn reverse(&mut self) {
-        let new_value = 1. - self.value;
-        self.value = new_value;
-    }
-
-
-    /// Parse a specific kind of percentage.
-    pub fn parse_with_clamping_mode<'i, 't>(
-        context: &ParserContext,
-        input: &mut Parser<'i, 't>,
-        num_context: AllowedNumericType,
-    ) -> Result<Self, ParseError<'i>> {
-        // FIXME: remove early returns when lifetimes are non-lexical
-        match *input.next()? {
-            Token::Percentage { unit_value, .. } if num_context.is_ok(context.parsing_mode, unit_value) => {
-                return Ok(Percentage::new(unit_value))
-            }
-            Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {}
-            ref t => return Err(BasicParseError::UnexpectedToken(t.clone()).into())
-        }
-
-        let result = input.parse_nested_block(|i| {
-            CalcNode::parse_percentage(context, i)
-        })?;
-
-        // TODO(emilio): -moz-image-rect is the only thing that uses
-        // the clamping mode... I guess we could disallow it...
-        Ok(Percentage {
-            value: result,
-            calc_clamping_mode: Some(num_context),
-        })
-    }
-
-    /// Parses a percentage token, but rejects it if it's negative.
-    pub fn parse_non_negative<'i, 't>(context: &ParserContext,
-                                      input: &mut Parser<'i, 't>)
-                                      -> Result<Self, ParseError<'i>> {
-        Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
-    }
-
-    /// 0%
-    #[inline]
-    pub fn zero() -> Self {
-        Percentage {
-            value: 0.,
-            calc_clamping_mode: None,
-        }
-    }
-
-    /// 100%
-    #[inline]
-    pub fn hundred() -> Self {
-        Percentage {
-            value: 1.,
-            calc_clamping_mode: None,
-        }
-    }
-}
-
-impl Parse for Percentage {
-    #[inline]
-    fn parse<'i, 't>(
-        context: &ParserContext,
-        input: &mut Parser<'i, 't>
-    ) -> Result<Self, ParseError<'i>> {
-        Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
-    }
-}
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/specified/percentage.rs
@@ -0,0 +1,152 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Specified percentages.
+
+use cssparser::{BasicParseError, Parser, Token};
+use parser::{Parse, ParserContext};
+use std::ascii::AsciiExt;
+use std::fmt;
+use style_traits::{ParseError, ToCss};
+use style_traits::values::specified::AllowedNumericType;
+use values::CSSFloat;
+use values::computed::{Context, ToComputedValue};
+use values::computed::percentage::Percentage as ComputedPercentage;
+use values::specified::calc::CalcNode;
+
+/// A percentage value.
+#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct Percentage {
+    /// The percentage value as a float.
+    ///
+    /// [0 .. 100%] maps to [0.0 .. 1.0]
+    value: CSSFloat,
+    /// If this percentage came from a calc() expression, this tells how
+    /// clamping should be done on the value.
+    calc_clamping_mode: Option<AllowedNumericType>,
+}
+
+no_viewport_percentage!(Percentage);
+
+impl ToCss for Percentage {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+        where W: fmt::Write,
+    {
+        if self.calc_clamping_mode.is_some() {
+            dest.write_str("calc(")?;
+        }
+
+        write!(dest, "{}%", self.value * 100.)?;
+
+        if self.calc_clamping_mode.is_some() {
+            dest.write_str(")")?;
+        }
+        Ok(())
+    }
+}
+
+impl Percentage {
+    /// Creates a percentage from a numeric value.
+    pub fn new(value: CSSFloat) -> Self {
+        Self {
+            value,
+            calc_clamping_mode: None,
+        }
+    }
+
+    /// `0%`
+    #[inline]
+    pub fn zero() -> Self {
+        Percentage {
+            value: 0.,
+            calc_clamping_mode: None,
+        }
+    }
+
+    /// `100%`
+    #[inline]
+    pub fn hundred() -> Self {
+        Percentage {
+            value: 1.,
+            calc_clamping_mode: None,
+        }
+    }
+    /// Gets the underlying value for this float.
+    pub fn get(&self) -> CSSFloat {
+        self.calc_clamping_mode.map_or(self.value, |mode| mode.clamp(self.value))
+    }
+
+    /// Returns whether this percentage is a `calc()` value.
+    pub fn is_calc(&self) -> bool {
+        self.calc_clamping_mode.is_some()
+    }
+
+    /// Reverses this percentage, preserving calc-ness.
+    ///
+    /// For example: If it was 20%, convert it into 80%.
+    pub fn reverse(&mut self) {
+        let new_value = 1. - self.value;
+        self.value = new_value;
+    }
+
+    /// Parses a specific kind of percentage.
+    pub fn parse_with_clamping_mode<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+        num_context: AllowedNumericType,
+    ) -> Result<Self, ParseError<'i>> {
+        // FIXME: remove early returns when lifetimes are non-lexical
+        match *input.next()? {
+            Token::Percentage { unit_value, .. } if num_context.is_ok(context.parsing_mode, unit_value) => {
+                return Ok(Percentage::new(unit_value));
+            }
+            Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {},
+            ref t => return Err(BasicParseError::UnexpectedToken(t.clone()).into()),
+        }
+
+        let result = input.parse_nested_block(|i| {
+            CalcNode::parse_percentage(context, i)
+        })?;
+
+        // TODO(emilio): -moz-image-rect is the only thing that uses
+        // the clamping mode... I guess we could disallow it...
+        Ok(Percentage {
+            value: result,
+            calc_clamping_mode: Some(num_context),
+        })
+    }
+
+    /// Parses a percentage token, but rejects it if it's negative.
+    pub fn parse_non_negative<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+    ) -> Result<Self, ParseError<'i>> {
+        Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
+    }
+}
+
+impl Parse for Percentage {
+    #[inline]
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>
+    ) -> Result<Self, ParseError<'i>> {
+        Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
+    }
+}
+
+impl ToComputedValue for Percentage {
+    type ComputedValue = ComputedPercentage;
+
+    #[inline]
+    fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
+        ComputedPercentage(self.get())
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        Percentage::new(computed.0)
+    }
+}
--- a/servo/components/style/values/specified/rect.rs
+++ b/servo/components/style/values/specified/rect.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-//! Computed types for CSS borders.
+//! Specified types for CSS borders.
 
 use cssparser::Parser;
 use parser::ParserContext;
 use style_traits::ParseError;
 use values::generics::rect::Rect;
 use values::specified::length::LengthOrNumber;
 
 /// A specified rectangle made of four `<length-or-number>` values.