Bug 1517511 - Simplify computed::LengthOrPercentage and friends. r=heycam
authorEmilio Cobos Álvarez <emilio@crisal.io>
Mon, 07 Jan 2019 11:00:27 +0000
changeset 452707 624782f944fc58580c3f1beeb59f9ef0c6a6f127
parent 452706 76c4b7d2ae72a58f13fc2806826be655c27c5c93
child 452708 658f77e5e78bf4efa3dce011f4aae33139206f7e
push id75539
push userealvarez@mozilla.com
push dateMon, 07 Jan 2019 11:01:13 +0000
treeherderautoland@624782f944fc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs1517511
milestone66.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
Bug 1517511 - Simplify computed::LengthOrPercentage and friends. r=heycam This is a first step to share LengthOrPercentage representation between Rust and Gecko. We need to preserve whether the value came from a calc() expression, for now at least, since we do different things depending on whether we're calc or not right now. See https://github.com/w3c/csswg-drafts/issues/3482 and dependent bugs for example. That means that the gecko conversion code needs to handle calc() in a bit of an awkward way until I change it to not be needed (patches for that incoming in the next few weeks I hope). I need to add a hack to exclude other things from the PartialEq implementation because the new conversion code is less lossy than the old one, and we relied on the lousiness in AnimationValue comparison (in order to start transitions and such, in [1] for example). I expect to remove that manual PartialEq implementation as soon as I'm done with the conversion. The less lossy conversion does fix a few serialization bugs for animation values though, like not loosing 0% values in calc() when interpolating lengths and percentages, see the two modified tests: * property-types.js * test_animation_properties.html Differential Revision: https://phabricator.services.mozilla.com/D15793
dom/animation/test/chrome/test_animation_properties.html
servo/components/style/gecko/conversions.rs
servo/components/style/gecko/values.rs
servo/components/style/gecko_bindings/sugar/ns_css_value.rs
servo/components/style/properties/gecko.mako.rs
servo/components/style/properties/longhands/inherited_text.mako.rs
servo/components/style/properties/longhands/margin.mako.rs
servo/components/style/stylesheets/viewport_rule.rs
servo/components/style/values/animated/length.rs
servo/components/style/values/animated/mod.rs
servo/components/style/values/animated/svg.rs
servo/components/style/values/animated/transform.rs
servo/components/style/values/computed/image.rs
servo/components/style/values/computed/length.rs
servo/components/style/values/computed/mod.rs
servo/components/style/values/computed/position.rs
servo/components/style/values/computed/transform.rs
servo/components/style/values/generics/text.rs
servo/components/style/values/generics/transform.rs
servo/components/style/values/specified/font.rs
servo/components/style/values/specified/length.rs
servo/components/style/values/specified/position.rs
servo/components/style/values/specified/transform.rs
servo/ports/geckolib/glue.rs
testing/web-platform/tests/web-animations/animation-model/animation-types/property-types.js
--- a/dom/animation/test/chrome/test_animation_properties.html
+++ b/dom/animation/test/chrome/test_animation_properties.html
@@ -560,17 +560,17 @@ var gTests = [
     frames:   { left: ['10em', '20em'] },
     expected: [ { property: 'left',
                   values: [ valueFormat(0, '100px', 'replace', 'linear'),
                             valueFormat(1, '200px', 'replace') ] } ]
   },
   { desc:     'calc() expressions are resolved to the equivalent units',
     frames:   { left: ['calc(10em + 10px)', 'calc(10em + 10%)'] },
     expected: [ { property: 'left',
-                  values: [ valueFormat(0, 'calc(110px)', 'replace', 'linear'),
+                  values: [ valueFormat(0, '110px', 'replace', 'linear'),
                             valueFormat(1, 'calc(10% + 100px)', 'replace') ] } ]
   },
 
   // ---------------------------------------------------------------------
   //
   // Tests for CSS variable handling conversion
   //
   // ---------------------------------------------------------------------
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -15,181 +15,136 @@ use crate::gecko_bindings::bindings;
 use crate::gecko_bindings::structs::RawGeckoGfxMatrix4x4;
 use crate::gecko_bindings::structs::{self, nsStyleCoord_CalcValue};
 use crate::gecko_bindings::structs::{nsStyleImage, nsresult, SheetType};
 use crate::gecko_bindings::sugar::ns_style_coord::{CoordData, CoordDataMut, CoordDataValue};
 use crate::stylesheets::{Origin, RulesMutateError};
 use crate::values::computed::image::LineDirection;
 use crate::values::computed::transform::Matrix3D;
 use crate::values::computed::url::ComputedImageUrl;
-use crate::values::computed::{Angle, CalcLengthOrPercentage, Gradient, Image};
+use crate::values::computed::{Angle, Gradient, Image};
 use crate::values::computed::{Integer, LengthOrPercentage};
 use crate::values::computed::{LengthOrPercentageOrAuto, NonNegativeLengthOrPercentageOrAuto};
 use crate::values::computed::{Percentage, TextAlign};
 use crate::values::generics::box_::VerticalAlign;
 use crate::values::generics::grid::{TrackListValue, TrackSize};
 use crate::values::generics::image::{CompatMode, GradientItem, Image as GenericImage};
 use crate::values::generics::rect::Rect;
 use crate::values::generics::NonNegative;
 use app_units::Au;
 use std::f32::consts::PI;
+use style_traits::values::specified::AllowedNumericType;
 
-impl From<CalcLengthOrPercentage> for nsStyleCoord_CalcValue {
-    fn from(other: CalcLengthOrPercentage) -> nsStyleCoord_CalcValue {
+impl From<LengthOrPercentage> for nsStyleCoord_CalcValue {
+    fn from(other: LengthOrPercentage) -> nsStyleCoord_CalcValue {
         let has_percentage = other.percentage.is_some();
         nsStyleCoord_CalcValue {
             mLength: other.unclamped_length().to_i32_au(),
             mPercent: other.percentage.map_or(0., |p| p.0),
             mHasPercent: has_percentage,
         }
     }
 }
 
-impl From<nsStyleCoord_CalcValue> for CalcLengthOrPercentage {
-    fn from(other: nsStyleCoord_CalcValue) -> CalcLengthOrPercentage {
+impl From<nsStyleCoord_CalcValue> for LengthOrPercentage {
+    fn from(other: nsStyleCoord_CalcValue) -> LengthOrPercentage {
         let percentage = if other.mHasPercent {
             Some(Percentage(other.mPercent))
         } else {
             None
         };
-        Self::new(Au(other.mLength).into(), percentage)
-    }
-}
-
-impl From<LengthOrPercentage> for nsStyleCoord_CalcValue {
-    fn from(other: LengthOrPercentage) -> nsStyleCoord_CalcValue {
-        match other {
-            LengthOrPercentage::Length(px) => nsStyleCoord_CalcValue {
-                mLength: px.to_i32_au(),
-                mPercent: 0.0,
-                mHasPercent: false,
-            },
-            LengthOrPercentage::Percentage(pc) => nsStyleCoord_CalcValue {
-                mLength: 0,
-                mPercent: pc.0,
-                mHasPercent: true,
-            },
-            LengthOrPercentage::Calc(calc) => calc.into(),
-        }
+        Self::with_clamping_mode(
+            Au(other.mLength).into(),
+            percentage,
+            AllowedNumericType::All,
+            /* was_calc = */ true,
+        )
     }
 }
 
 impl LengthOrPercentageOrAuto {
     /// Convert this value in an appropriate `nsStyleCoord::CalcValue`.
     pub fn to_calc_value(&self) -> Option<nsStyleCoord_CalcValue> {
         match *self {
-            LengthOrPercentageOrAuto::Length(px) => Some(nsStyleCoord_CalcValue {
-                mLength: px.to_i32_au(),
-                mPercent: 0.0,
-                mHasPercent: false,
-            }),
-            LengthOrPercentageOrAuto::Percentage(pc) => Some(nsStyleCoord_CalcValue {
-                mLength: 0,
-                mPercent: pc.0,
-                mHasPercent: true,
-            }),
-            LengthOrPercentageOrAuto::Calc(calc) => Some(calc.into()),
+            LengthOrPercentageOrAuto::LengthOrPercentage(len) => Some(From::from(len)),
             LengthOrPercentageOrAuto::Auto => None,
         }
     }
 }
 
-impl From<nsStyleCoord_CalcValue> for LengthOrPercentage {
-    fn from(other: nsStyleCoord_CalcValue) -> LengthOrPercentage {
-        match (other.mHasPercent, other.mLength) {
-            (false, _) => LengthOrPercentage::Length(Au(other.mLength).into()),
-            (true, 0) => LengthOrPercentage::Percentage(Percentage(other.mPercent)),
-            _ => LengthOrPercentage::Calc(other.into()),
-        }
-    }
-}
-
 impl From<nsStyleCoord_CalcValue> for LengthOrPercentageOrAuto {
     fn from(other: nsStyleCoord_CalcValue) -> LengthOrPercentageOrAuto {
-        match (other.mHasPercent, other.mLength) {
-            (false, _) => LengthOrPercentageOrAuto::Length(Au(other.mLength).into()),
-            (true, 0) => LengthOrPercentageOrAuto::Percentage(Percentage(other.mPercent)),
-            _ => LengthOrPercentageOrAuto::Calc(other.into()),
-        }
+        LengthOrPercentageOrAuto::LengthOrPercentage(LengthOrPercentage::from(other))
     }
 }
 
 // FIXME(emilio): A lot of these impl From should probably become explicit or
 // disappear as we move more stuff to cbindgen.
 impl From<nsStyleCoord_CalcValue> for NonNegativeLengthOrPercentageOrAuto {
     fn from(other: nsStyleCoord_CalcValue) -> Self {
-        use style_traits::values::specified::AllowedNumericType;
-        NonNegative(if other.mLength < 0 || other.mPercent < 0. {
-            LengthOrPercentageOrAuto::Calc(CalcLengthOrPercentage::with_clamping_mode(
+        NonNegative(
+            LengthOrPercentageOrAuto::LengthOrPercentage(LengthOrPercentage::with_clamping_mode(
                 Au(other.mLength).into(),
                 if other.mHasPercent {
                     Some(Percentage(other.mPercent))
                 } else {
                     None
                 },
                 AllowedNumericType::NonNegative,
+                /* was_calc = */ true,
             ))
-        } else {
-            other.into()
-        })
+        )
     }
 }
 
 impl From<Angle> for CoordDataValue {
     fn from(reference: Angle) -> Self {
         CoordDataValue::Degree(reference.degrees())
     }
 }
 
 fn line_direction(horizontal: LengthOrPercentage, vertical: LengthOrPercentage) -> LineDirection {
     use crate::values::computed::position::Position;
     use crate::values::specified::position::{X, Y};
 
-    let horizontal_percentage = match horizontal {
-        LengthOrPercentage::Percentage(percentage) => Some(percentage.0),
-        _ => None,
-    };
-
-    let vertical_percentage = match vertical {
-        LengthOrPercentage::Percentage(percentage) => Some(percentage.0),
-        _ => None,
-    };
+    let horizontal_percentage = horizontal.as_percentage();
+    let vertical_percentage = vertical.as_percentage();
 
     let horizontal_as_corner = horizontal_percentage.and_then(|percentage| {
-        if percentage == 0.0 {
+        if percentage.0 == 0.0 {
             Some(X::Left)
-        } else if percentage == 1.0 {
+        } else if percentage.0 == 1.0 {
             Some(X::Right)
         } else {
             None
         }
     });
 
     let vertical_as_corner = vertical_percentage.and_then(|percentage| {
-        if percentage == 0.0 {
+        if percentage.0 == 0.0 {
             Some(Y::Top)
-        } else if percentage == 1.0 {
+        } else if percentage.0 == 1.0 {
             Some(Y::Bottom)
         } else {
             None
         }
     });
 
     if let (Some(hc), Some(vc)) = (horizontal_as_corner, vertical_as_corner) {
         return LineDirection::Corner(hc, vc);
     }
 
     if let Some(hc) = horizontal_as_corner {
-        if vertical_percentage == Some(0.5) {
+        if vertical_percentage == Some(Percentage(0.5)) {
             return LineDirection::Horizontal(hc);
         }
     }
 
     if let Some(vc) = vertical_as_corner {
-        if horizontal_percentage == Some(0.5) {
+        if horizontal_percentage == Some(Percentage(0.5)) {
             return LineDirection::Vertical(vc);
         }
     }
 
     LineDirection::MozPosition(
         Some(Position {
             horizontal,
             vertical,
--- a/servo/components/style/gecko/values.rs
+++ b/servo/components/style/gecko/values.rs
@@ -143,29 +143,31 @@ impl GeckoStyleCoordConvertible for Numb
             CoordDataValue::Percent(p) => Some(NumberOrPercentage::Percentage(Percentage(p))),
             _ => None,
         }
     }
 }
 
 impl GeckoStyleCoordConvertible for LengthOrPercentage {
     fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
-        let value = match *self {
-            LengthOrPercentage::Length(px) => CoordDataValue::Coord(px.to_i32_au()),
-            LengthOrPercentage::Percentage(p) => CoordDataValue::Percent(p.0),
-            LengthOrPercentage::Calc(calc) => CoordDataValue::Calc(calc.into()),
-        };
-        coord.set_value(value);
+        if self.was_calc {
+            return coord.set_value(CoordDataValue::Calc((*self).into()))
+        }
+        debug_assert!(self.percentage.is_none() || self.unclamped_length() == Length::zero());
+        if let Some(p) = self.percentage {
+            return coord.set_value(CoordDataValue::Percent(p.0));
+        }
+        coord.set_value(CoordDataValue::Coord(self.unclamped_length().to_i32_au()))
     }
 
     fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
         match coord.as_value() {
-            CoordDataValue::Coord(coord) => Some(LengthOrPercentage::Length(Au(coord).into())),
-            CoordDataValue::Percent(p) => Some(LengthOrPercentage::Percentage(Percentage(p))),
-            CoordDataValue::Calc(calc) => Some(LengthOrPercentage::Calc(calc.into())),
+            CoordDataValue::Coord(coord) => Some(LengthOrPercentage::new(Au(coord).into(), None)),
+            CoordDataValue::Percent(p) => Some(LengthOrPercentage::new(Au(0).into(), Some(Percentage(p)))),
+            CoordDataValue::Calc(calc) => Some(calc.into()),
             _ => None,
         }
     }
 }
 
 impl GeckoStyleCoordConvertible for Length {
     fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
         coord.set_value(CoordDataValue::Coord(self.to_i32_au()));
@@ -176,58 +178,42 @@ impl GeckoStyleCoordConvertible for Leng
             CoordDataValue::Coord(coord) => Some(Au(coord).into()),
             _ => None,
         }
     }
 }
 
 impl GeckoStyleCoordConvertible for LengthOrPercentageOrAuto {
     fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
-        let value = match *self {
-            LengthOrPercentageOrAuto::Length(px) => CoordDataValue::Coord(px.to_i32_au()),
-            LengthOrPercentageOrAuto::Percentage(p) => CoordDataValue::Percent(p.0),
-            LengthOrPercentageOrAuto::Auto => CoordDataValue::Auto,
-            LengthOrPercentageOrAuto::Calc(calc) => CoordDataValue::Calc(calc.into()),
-        };
-        coord.set_value(value);
+        match *self {
+            LengthOrPercentageOrAuto::Auto => coord.set_value(CoordDataValue::Auto),
+            LengthOrPercentageOrAuto::LengthOrPercentage(ref lop) => lop.to_gecko_style_coord(coord),
+        }
     }
 
     fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
         match coord.as_value() {
-            CoordDataValue::Coord(coord) => {
-                Some(LengthOrPercentageOrAuto::Length(Au(coord).into()))
-            },
-            CoordDataValue::Percent(p) => Some(LengthOrPercentageOrAuto::Percentage(Percentage(p))),
             CoordDataValue::Auto => Some(LengthOrPercentageOrAuto::Auto),
-            CoordDataValue::Calc(calc) => Some(LengthOrPercentageOrAuto::Calc(calc.into())),
-            _ => None,
+            _ => LengthOrPercentage::from_gecko_style_coord(coord).map(LengthOrPercentageOrAuto::LengthOrPercentage),
         }
     }
 }
 
 impl GeckoStyleCoordConvertible for LengthOrPercentageOrNone {
     fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
-        let value = match *self {
-            LengthOrPercentageOrNone::Length(px) => CoordDataValue::Coord(px.to_i32_au()),
-            LengthOrPercentageOrNone::Percentage(p) => CoordDataValue::Percent(p.0),
-            LengthOrPercentageOrNone::None => CoordDataValue::None,
-            LengthOrPercentageOrNone::Calc(calc) => CoordDataValue::Calc(calc.into()),
-        };
-        coord.set_value(value);
+        match *self {
+            LengthOrPercentageOrNone::None => coord.set_value(CoordDataValue::None),
+            LengthOrPercentageOrNone::LengthOrPercentage(ref lop) => lop.to_gecko_style_coord(coord),
+        }
     }
 
     fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self> {
         match coord.as_value() {
-            CoordDataValue::Coord(coord) => {
-                Some(LengthOrPercentageOrNone::Length(Au(coord).into()))
-            },
-            CoordDataValue::Percent(p) => Some(LengthOrPercentageOrNone::Percentage(Percentage(p))),
             CoordDataValue::None => Some(LengthOrPercentageOrNone::None),
-            CoordDataValue::Calc(calc) => Some(LengthOrPercentageOrNone::Calc(calc.into())),
-            _ => None,
+            _ => LengthOrPercentage::from_gecko_style_coord(coord).map(LengthOrPercentageOrNone::LengthOrPercentage),
         }
     }
 }
 
 impl<L: GeckoStyleCoordConvertible> GeckoStyleCoordConvertible for TrackBreadth<L> {
     fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T) {
         match *self {
             TrackBreadth::Breadth(ref lop) => lop.to_gecko_style_coord(coord),
--- a/servo/components/style/gecko_bindings/sugar/ns_css_value.rs
+++ b/servo/components/style/gecko_bindings/sugar/ns_css_value.rs
@@ -64,44 +64,50 @@ impl nsCSSValue {
         );
         let array = *self.mValue.mArray.as_ref();
         debug_assert!(!array.is_null());
         &*array
     }
 
     /// Sets LengthOrPercentage value to this nsCSSValue.
     pub unsafe fn set_lop(&mut self, lop: LengthOrPercentage) {
-        match lop {
-            LengthOrPercentage::Length(px) => self.set_px(px.px()),
-            LengthOrPercentage::Percentage(pc) => self.set_percentage(pc.0),
-            LengthOrPercentage::Calc(calc) => bindings::Gecko_CSSValue_SetCalc(self, calc.into()),
+        if lop.was_calc {
+            return bindings::Gecko_CSSValue_SetCalc(self, lop.into())
         }
+        debug_assert!(lop.percentage.is_none() || lop.unclamped_length() == Length::zero());
+        if let Some(p) = lop.percentage {
+            return self.set_percentage(p.0);
+        }
+        self.set_px(lop.unclamped_length().px());
     }
 
     /// Sets a px value to this nsCSSValue.
     pub unsafe fn set_px(&mut self, px: f32) {
         bindings::Gecko_CSSValue_SetPixelLength(self, px)
     }
 
     /// Sets a percentage value to this nsCSSValue.
     pub unsafe fn set_percentage(&mut self, unit_value: f32) {
         bindings::Gecko_CSSValue_SetPercentage(self, unit_value)
     }
 
     /// Returns LengthOrPercentage value.
     pub unsafe fn get_lop(&self) -> LengthOrPercentage {
         match self.mUnit {
             nsCSSUnit::eCSSUnit_Pixel => {
-                LengthOrPercentage::Length(Length::new(bindings::Gecko_CSSValue_GetNumber(self)))
+                LengthOrPercentage::new(
+                    Length::new(bindings::Gecko_CSSValue_GetNumber(self)),
+                    None,
+                )
             },
-            nsCSSUnit::eCSSUnit_Percent => LengthOrPercentage::Percentage(Percentage(
+            nsCSSUnit::eCSSUnit_Percent => LengthOrPercentage::new_percent(Percentage(
                 bindings::Gecko_CSSValue_GetPercentage(self),
             )),
             nsCSSUnit::eCSSUnit_Calc => {
-                LengthOrPercentage::Calc(bindings::Gecko_CSSValue_GetCalc(self).into())
+                bindings::Gecko_CSSValue_GetCalc(self).into()
             },
             _ => panic!("Unexpected unit"),
         }
     }
 
     /// Returns Length  value.
     pub unsafe fn get_length(&self) -> Length {
         match self.mUnit {
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -553,28 +553,26 @@ def set_gecko_property(ffi_name, expr):
             return SVGLength::ContextValue;
         }
         let length = match self.gecko.${gecko_ffi_name}.as_value() {
             CoordDataValue::Factor(number) => {
                 SvgLengthOrPercentageOrNumber::Number(number)
             },
             CoordDataValue::Coord(coord) => {
                 SvgLengthOrPercentageOrNumber::LengthOrPercentage(
-                    LengthOrPercentage::Length(Au(coord).into())
+                    LengthOrPercentage::new(Au(coord).into(), None)
                 )
             },
             CoordDataValue::Percent(p) => {
                 SvgLengthOrPercentageOrNumber::LengthOrPercentage(
-                    LengthOrPercentage::Percentage(Percentage(p))
+                    LengthOrPercentage::new(Au(0).into(), Some(Percentage(p)))
                 )
             },
             CoordDataValue::Calc(calc) => {
-                SvgLengthOrPercentageOrNumber::LengthOrPercentage(
-                    LengthOrPercentage::Calc(calc.into())
-                )
+                SvgLengthOrPercentageOrNumber::LengthOrPercentage(calc.into())
             },
             _ => unreachable!("Unexpected coordinate in ${ident}"),
         };
         SVGLength::Length(length.into())
     }
 </%def>
 
 <%def name="impl_svg_opacity(ident, gecko_ffi_name)">
@@ -5057,36 +5055,37 @@ clip-path
 
     pub fn reset_stroke_dasharray(&mut self, other: &Self) {
         self.copy_stroke_dasharray_from(other)
     }
 
     pub fn clone_stroke_dasharray(&self) -> longhands::stroke_dasharray::computed_value::T {
         use crate::gecko_bindings::structs::nsStyleSVG_STROKE_DASHARRAY_CONTEXT as CONTEXT_VALUE;
         use crate::values::computed::LengthOrPercentage;
+        use crate::values::generics::NonNegative;
         use crate::values::generics::svg::{SVGStrokeDashArray, SvgLengthOrPercentageOrNumber};
 
         if self.gecko.mContextFlags & CONTEXT_VALUE != 0 {
             debug_assert_eq!(self.gecko.mStrokeDasharray.len(), 0);
             return SVGStrokeDashArray::ContextValue;
         }
         let mut vec = vec![];
         for gecko in self.gecko.mStrokeDasharray.iter() {
             match gecko.as_value() {
                 CoordDataValue::Factor(number) =>
                     vec.push(SvgLengthOrPercentageOrNumber::Number(number.into())),
                 CoordDataValue::Coord(coord) =>
                     vec.push(SvgLengthOrPercentageOrNumber::LengthOrPercentage(
-                        LengthOrPercentage::Length(Au(coord).into()).into())),
+                        NonNegative(LengthOrPercentage::new(Au(coord).into(), None).into()))),
                 CoordDataValue::Percent(p) =>
                     vec.push(SvgLengthOrPercentageOrNumber::LengthOrPercentage(
-                        LengthOrPercentage::Percentage(Percentage(p)).into())),
+                        NonNegative(LengthOrPercentage::new_percent(Percentage(p)).into()))),
                 CoordDataValue::Calc(calc) =>
                     vec.push(SvgLengthOrPercentageOrNumber::LengthOrPercentage(
-                        LengthOrPercentage::Calc(calc.into()).into())),
+                        NonNegative(LengthOrPercentage::from(calc).clamp_to_non_negative()))),
                 _ => unreachable!(),
             }
         }
         SVGStrokeDashArray::Values(vec)
     }
 
     #[allow(non_snake_case)]
     pub fn _moz_context_properties_count(&self) -> usize {
--- a/servo/components/style/properties/longhands/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhands/inherited_text.mako.rs
@@ -49,17 +49,17 @@
     products="gecko", animation_value_type="discrete",
     spec="https://drafts.csswg.org/css-size-adjust/#adjustment-control",
     alias="-webkit-text-size-adjust",
 )}
 
 ${helpers.predefined_type(
     "text-indent",
     "LengthOrPercentage",
-    "computed::LengthOrPercentage::Length(computed::Length::new(0.))",
+    "computed::LengthOrPercentage::zero()",
     animation_value_type="ComputedValue",
     spec="https://drafts.csswg.org/css-text/#propdef-text-indent",
     allow_quirks=True,
     servo_restyle_damage = "reflow",
 )}
 
 // Also known as "word-wrap" (which is more popular because of IE), but this is
 // the preferred name per CSS-TEXT 6.2.
--- a/servo/components/style/properties/longhands/margin.mako.rs
+++ b/servo/components/style/properties/longhands/margin.mako.rs
@@ -10,17 +10,17 @@
     <%
         spec = "https://drafts.csswg.org/css-box/#propdef-margin-%s" % side[0]
         if side[1]:
             spec = "https://drafts.csswg.org/css-logical-props/#propdef-margin-%s" % side[1]
     %>
     ${helpers.predefined_type(
         "margin-%s" % side[0],
         "LengthOrPercentageOrAuto",
-        "computed::LengthOrPercentageOrAuto::Length(computed::Length::new(0.))",
+        "computed::LengthOrPercentageOrAuto::zero()",
         alias=maybe_moz_logical_alias(product, side, "-moz-margin-%s"),
         allow_quirks=not side[1],
         animation_value_type="ComputedValue",
         logical=side[1],
         logical_group="margin",
         spec=spec,
         flags="APPLIES_TO_FIRST_LETTER GETCS_NEEDS_LAYOUT_FLUSH",
         allowed_in_page_rule=True,
--- a/servo/components/style/stylesheets/viewport_rule.rs
+++ b/servo/components/style/stylesheets/viewport_rule.rs
@@ -13,17 +13,17 @@ use crate::font_metrics::get_metrics_pro
 use crate::media_queries::Device;
 use crate::parser::ParserContext;
 use crate::properties::StyleBuilder;
 use crate::rule_cache::RuleCacheConditions;
 use crate::shared_lock::{SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard};
 use crate::str::CssStringWriter;
 use crate::stylesheets::{Origin, StylesheetInDocument};
 use crate::values::computed::{Context, ToComputedValue};
-use crate::values::specified::{LengthOrPercentageOrAuto, NoCalcLength, ViewportPercentageLength};
+use crate::values::specified::{self, LengthOrPercentageOrAuto, NoCalcLength, ViewportPercentageLength};
 use app_units::Au;
 use cssparser::CowRcStr;
 use cssparser::{parse_important, AtRuleParser, DeclarationListParser, DeclarationParser, Parser};
 use euclid::TypedSize2D;
 use selectors::parser::SelectorParseErrorKind;
 use std::borrow::Cow;
 use std::cell::RefCell;
 use std::fmt::{self, Write};
@@ -152,17 +152,19 @@ pub enum ViewportLength {
     Specified(LengthOrPercentageOrAuto),
     ExtendToZoom,
 }
 
 impl FromMeta for ViewportLength {
     fn from_meta(value: &str) -> Option<ViewportLength> {
         macro_rules! specified {
             ($value:expr) => {
-                ViewportLength::Specified(LengthOrPercentageOrAuto::Length($value))
+                ViewportLength::Specified(LengthOrPercentageOrAuto::LengthOrPercentage(
+                    specified::LengthOrPercentage::Length($value)
+                ))
             };
         }
 
         Some(match value {
             v if v.eq_ignore_ascii_case("device-width") => specified!(
                 NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(100.))
             ),
             v if v.eq_ignore_ascii_case("device-height") => specified!(
@@ -747,26 +749,20 @@ impl MaybeNew for ViewportConstraints {
             extend_height = None;
         }
 
         macro_rules! to_pixel_length {
             ($value:ident, $dimension:ident, $extend_to:ident => $auto_extend_to:expr) => {
                 if let Some($value) = $value {
                     match *$value {
                         ViewportLength::Specified(ref length) => match *length {
-                            LengthOrPercentageOrAuto::Length(ref value) => {
-                                Some(Au::from(value.to_computed_value(&context)))
-                            },
-                            LengthOrPercentageOrAuto::Percentage(value) => {
-                                Some(initial_viewport.$dimension.scale_by(value.0))
-                            },
                             LengthOrPercentageOrAuto::Auto => None,
-                            LengthOrPercentageOrAuto::Calc(ref calc) => calc
+                            LengthOrPercentageOrAuto::LengthOrPercentage(ref lop) => Some(lop
                                 .to_computed_value(&context)
-                                .to_used_value(Some(initial_viewport.$dimension)),
+                                .to_used_value(initial_viewport.$dimension)),
                         },
                         ViewportLength::ExtendToZoom => {
                             // $extend_to will be 'None' if 'extend-to-zoom' is 'auto'
                             match ($extend_to, $auto_extend_to) {
                                 (None, None) => None,
                                 (a, None) => a,
                                 (None, b) => b,
                                 (a, b) => cmp::max(a, b),
--- a/servo/components/style/values/animated/length.rs
+++ b/servo/components/style/values/animated/length.rs
@@ -1,96 +1,67 @@
 /* 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/. */
 
 //! Animation implementation for various length-related types.
 
-use super::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero};
-use crate::values::computed::length::{CalcLengthOrPercentage, Length};
-use crate::values::computed::length::{LengthOrPercentageOrAuto, LengthOrPercentageOrNone};
+use super::{Animate, Procedure, ToAnimatedValue};
+use crate::values::computed::length::LengthOrPercentage;
 use crate::values::computed::MaxLength as ComputedMaxLength;
 use crate::values::computed::MozLength as ComputedMozLength;
 use crate::values::computed::Percentage;
 
 /// <https://drafts.csswg.org/css-transitions/#animtype-lpcalc>
-impl Animate for CalcLengthOrPercentage {
+impl Animate for LengthOrPercentage {
     #[inline]
     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
         let animate_percentage_half = |this: Option<Percentage>, other: Option<Percentage>| {
             if this.is_none() && other.is_none() {
                 return Ok(None);
             }
             let this = this.unwrap_or_default();
             let other = other.unwrap_or_default();
             Ok(Some(this.animate(&other, procedure)?))
         };
 
         let length = self
             .unclamped_length()
             .animate(&other.unclamped_length(), procedure)?;
         let percentage = animate_percentage_half(self.percentage, other.percentage)?;
-        Ok(CalcLengthOrPercentage::with_clamping_mode(
+        let is_calc = self.was_calc || other.was_calc || self.percentage.is_some() != other.percentage.is_some();
+        Ok(Self::with_clamping_mode(
             length,
             percentage,
             self.clamping_mode,
+            is_calc,
         ))
     }
 }
 
-impl ToAnimatedZero for LengthOrPercentageOrAuto {
-    #[inline]
-    fn to_animated_zero(&self) -> Result<Self, ()> {
-        match *self {
-            LengthOrPercentageOrAuto::Length(_) |
-            LengthOrPercentageOrAuto::Percentage(_) |
-            LengthOrPercentageOrAuto::Calc(_) => {
-                Ok(LengthOrPercentageOrAuto::Length(Length::new(0.)))
-            },
-            LengthOrPercentageOrAuto::Auto => Err(()),
-        }
-    }
-}
-
-impl ToAnimatedZero for LengthOrPercentageOrNone {
-    #[inline]
-    fn to_animated_zero(&self) -> Result<Self, ()> {
-        match *self {
-            LengthOrPercentageOrNone::Length(_) |
-            LengthOrPercentageOrNone::Percentage(_) |
-            LengthOrPercentageOrNone::Calc(_) => {
-                Ok(LengthOrPercentageOrNone::Length(Length::new(0.)))
-            },
-            LengthOrPercentageOrNone::None => Err(()),
-        }
-    }
-}
-
+// FIXME(emilio): These should use NonNegative<> instead.
 impl ToAnimatedValue for ComputedMaxLength {
     type AnimatedValue = Self;
 
     #[inline]
     fn to_animated_value(self) -> Self {
         self
     }
 
     #[inline]
     fn from_animated_value(animated: Self::AnimatedValue) -> Self {
-        use crate::values::computed::{Length, LengthOrPercentageOrNone, Percentage};
+        use crate::values::computed::LengthOrPercentageOrNone;
         use crate::values::generics::length::MaxLength as GenericMaxLength;
         match animated {
             GenericMaxLength::LengthOrPercentageOrNone(lopn) => {
                 let result = match lopn {
-                    LengthOrPercentageOrNone::Length(px) => {
-                        LengthOrPercentageOrNone::Length(Length::new(px.px().max(0.)))
+                    LengthOrPercentageOrNone::LengthOrPercentage(len) => {
+                        LengthOrPercentageOrNone::LengthOrPercentage(len.clamp_to_non_negative())
                     },
-                    LengthOrPercentageOrNone::Percentage(percentage) => {
-                        LengthOrPercentageOrNone::Percentage(Percentage(percentage.0.max(0.)))
-                    },
-                    _ => lopn,
+                    LengthOrPercentageOrNone::None => lopn,
                 };
                 GenericMaxLength::LengthOrPercentageOrNone(result)
             },
             _ => animated,
         }
     }
 }
 
@@ -99,27 +70,17 @@ impl ToAnimatedValue for ComputedMozLeng
 
     #[inline]
     fn to_animated_value(self) -> Self {
         self
     }
 
     #[inline]
     fn from_animated_value(animated: Self::AnimatedValue) -> Self {
-        use crate::values::computed::{Length, LengthOrPercentageOrAuto, Percentage};
         use crate::values::generics::length::MozLength as GenericMozLength;
         match animated {
             GenericMozLength::LengthOrPercentageOrAuto(lopa) => {
-                let result = match lopa {
-                    LengthOrPercentageOrAuto::Length(px) => {
-                        LengthOrPercentageOrAuto::Length(Length::new(px.px().max(0.)))
-                    },
-                    LengthOrPercentageOrAuto::Percentage(percentage) => {
-                        LengthOrPercentageOrAuto::Percentage(Percentage(percentage.0.max(0.)))
-                    },
-                    _ => lopa,
-                };
-                GenericMozLength::LengthOrPercentageOrAuto(result)
+                GenericMozLength::LengthOrPercentageOrAuto(lopa.clamp_to_non_negative())
             },
             _ => animated,
         }
     }
 }
--- a/servo/components/style/values/animated/mod.rs
+++ b/servo/components/style/values/animated/mod.rs
@@ -4,17 +4,17 @@
 
 //! Animated values.
 //!
 //! Some values, notably colors, cannot be interpolated directly with their
 //! computed values and need yet another intermediate representation. This
 //! module's raison d'ĂȘtre is to ultimately contain all these types.
 
 use crate::properties::PropertyId;
-use crate::values::computed::length::CalcLengthOrPercentage;
+use crate::values::computed::length::LengthOrPercentage;
 use crate::values::computed::url::ComputedUrl;
 use crate::values::computed::Angle as ComputedAngle;
 use crate::values::computed::Image;
 use crate::values::specified::SVGPathData;
 use crate::values::CSSFloat;
 use app_units::Au;
 use euclid::{Point2D, Size2D};
 use smallvec::SmallVec;
@@ -330,17 +330,17 @@ macro_rules! trivial_to_animated_value {
             fn from_animated_value(animated: Self::AnimatedValue) -> Self {
                 animated
             }
         }
     };
 }
 
 trivial_to_animated_value!(Au);
-trivial_to_animated_value!(CalcLengthOrPercentage);
+trivial_to_animated_value!(LengthOrPercentage);
 trivial_to_animated_value!(ComputedAngle);
 trivial_to_animated_value!(ComputedUrl);
 trivial_to_animated_value!(bool);
 trivial_to_animated_value!(f32);
 // Note: This implementation is for ToAnimatedValue of ShapeSource.
 //
 // SVGPathData uses Box<[T]>. If we want to derive ToAnimatedValue for all the
 // types, we have to do "impl ToAnimatedValue for Box<[T]>" first.
--- a/servo/components/style/values/animated/svg.rs
+++ b/servo/components/style/values/animated/svg.rs
@@ -27,20 +27,26 @@ impl ToAnimatedZero for IntermediateSVGP
 }
 
 // FIXME: We need to handle calc here properly, see
 // https://bugzilla.mozilla.org/show_bug.cgi?id=1386967
 fn to_number_or_percentage(
     value: &SvgLengthOrPercentageOrNumber<LengthOrPercentage, Number>,
 ) -> Result<NumberOrPercentage, ()> {
     Ok(match *value {
-        SvgLengthOrPercentageOrNumber::LengthOrPercentage(ref l) => match *l {
-            LengthOrPercentage::Length(ref l) => NumberOrPercentage::Number(l.px()),
-            LengthOrPercentage::Percentage(ref p) => NumberOrPercentage::Percentage(*p),
-            LengthOrPercentage::Calc(..) => return Err(()),
+        SvgLengthOrPercentageOrNumber::LengthOrPercentage(ref l) => {
+            match l.percentage {
+                Some(p) => {
+                    if l.unclamped_length().px() != 0. {
+                        return Err(());
+                    }
+                    NumberOrPercentage::Percentage(p)
+                }
+                None => NumberOrPercentage::Number(l.length().px())
+            }
         },
         SvgLengthOrPercentageOrNumber::Number(ref n) => NumberOrPercentage::Number(*n),
     })
 }
 
 impl Animate for SvgLengthOrPercentageOrNumber<LengthOrPercentage, Number> {
     #[inline]
     fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
@@ -50,17 +56,17 @@ impl Animate for SvgLengthOrPercentageOr
         match (this, other) {
             (NumberOrPercentage::Number(ref this), NumberOrPercentage::Number(ref other)) => Ok(
                 SvgLengthOrPercentageOrNumber::Number(this.animate(other, procedure)?),
             ),
             (
                 NumberOrPercentage::Percentage(ref this),
                 NumberOrPercentage::Percentage(ref other),
             ) => Ok(SvgLengthOrPercentageOrNumber::LengthOrPercentage(
-                LengthOrPercentage::Percentage(this.animate(other, procedure)?),
+                LengthOrPercentage::new_percent(this.animate(other, procedure)?),
             )),
             _ => Err(()),
         }
     }
 }
 
 impl ComputeSquaredDistance for SvgLengthOrPercentageOrNumber<LengthOrPercentage, Number> {
     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
--- a/servo/components/style/values/animated/transform.rs
+++ b/servo/components/style/values/animated/transform.rs
@@ -1162,27 +1162,16 @@ impl Animate for ComputedTransformOperat
 }
 
 // This might not be the most useful definition of distance. It might be better, for example,
 // to trace the distance travelled by a point as its transform is interpolated between the two
 // lists. That, however, proves to be quite complicated so we take a simple approach for now.
 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1318591#c0.
 impl ComputeSquaredDistance for ComputedTransformOperation {
     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
-        // For translate, We don't want to require doing layout in order to calculate the result, so
-        // drop the percentage part. However, dropping percentage makes us impossible to
-        // compute the distance for the percentage-percentage case, but Gecko uses the
-        // same formula, so it's fine for now.
-        // Note: We use pixel value to compute the distance for translate, so we have to
-        // convert Au into px.
-        let extract_pixel_length = |lop: &LengthOrPercentage| match *lop {
-            LengthOrPercentage::Length(px) => px.px(),
-            LengthOrPercentage::Percentage(_) => 0.,
-            LengthOrPercentage::Calc(calc) => calc.length().px(),
-        };
         match (self, other) {
             (&TransformOperation::Matrix3D(ref this), &TransformOperation::Matrix3D(ref other)) => {
                 this.compute_squared_distance(other)
             },
             (&TransformOperation::Matrix(ref this), &TransformOperation::Matrix(ref other)) => {
                 let this: Matrix3D = (*this).into();
                 let other: Matrix3D = (*other).into();
                 this.compute_squared_distance(&other)
@@ -1194,20 +1183,26 @@ impl ComputeSquaredDistance for Computed
             (&TransformOperation::SkewX(ref f), &TransformOperation::SkewX(ref t)) |
             (&TransformOperation::SkewY(ref f), &TransformOperation::SkewY(ref t)) => {
                 f.compute_squared_distance(&t)
             },
             (
                 &TransformOperation::Translate3D(ref fx, ref fy, ref fz),
                 &TransformOperation::Translate3D(ref tx, ref ty, ref tz),
             ) => {
-                let fx = extract_pixel_length(&fx);
-                let fy = extract_pixel_length(&fy);
-                let tx = extract_pixel_length(&tx);
-                let ty = extract_pixel_length(&ty);
+                // For translate, We don't want to require doing layout in order
+                // to calculate the result, so drop the percentage part.
+                //
+                // However, dropping percentage makes us impossible to compute
+                // the distance for the percentage-percentage case, but Gecko
+                // uses the same formula, so it's fine for now.
+                let fx = fx.length_component().px();
+                let fy = fy.length_component().px();
+                let tx = tx.length_component().px();
+                let ty = ty.length_component().px();
 
                 Ok(fx.compute_squared_distance(&tx)? +
                     fy.compute_squared_distance(&ty)? +
                     fz.compute_squared_distance(&tz)?)
             },
             (
                 &TransformOperation::Scale3D(ref fx, ref fy, ref fz),
                 &TransformOperation::Scale3D(ref tx, ref ty, ref tz),
--- a/servo/components/style/values/computed/image.rs
+++ b/servo/components/style/values/computed/image.rs
@@ -4,18 +4,16 @@
 
 //! CSS handling for the computed value of
 //! [`image`][image]s
 //!
 //! [image]: https://drafts.csswg.org/css-images/#image-values
 
 use crate::values::computed::position::Position;
 use crate::values::computed::url::ComputedImageUrl;
-#[cfg(feature = "gecko")]
-use crate::values::computed::Percentage;
 use crate::values::computed::{Angle, Color, Context};
 use crate::values::computed::{Length, LengthOrPercentage, NumberOrPercentage, ToComputedValue};
 use crate::values::generics::image::{self as generic, CompatMode};
 use crate::values::specified::image::LineDirection as SpecifiedLineDirection;
 use crate::values::specified::position::{X, Y};
 use crate::values::{Either, None_};
 use std::f32::consts::PI;
 use std::fmt::{self, Write};
@@ -68,25 +66,20 @@ pub type MozImageRect = generic::MozImag
 impl generic::LineDirection for LineDirection {
     fn points_downwards(&self, compat_mode: CompatMode) -> bool {
         match *self {
             LineDirection::Angle(angle) => angle.radians() == PI,
             LineDirection::Vertical(Y::Bottom) if compat_mode == CompatMode::Modern => true,
             LineDirection::Vertical(Y::Top) if compat_mode != CompatMode::Modern => true,
             LineDirection::Corner(..) => false,
             #[cfg(feature = "gecko")]
-            LineDirection::MozPosition(
-                Some(Position {
-                    horizontal: LengthOrPercentage::Percentage(Percentage(x)),
-                    vertical: LengthOrPercentage::Percentage(Percentage(y)),
-                }),
-                None,
-            ) => {
+            LineDirection::MozPosition(Some(Position { ref vertical, ref horizontal }), None) => {
                 // `50% 0%` is the default value for line direction.
-                x == 0.5 && y == 0.0
+                horizontal.as_percentage().map_or(false, |p| p.0 == 0.5) &&
+                vertical.as_percentage().map_or(false, |p| p.0 == 0.0)
             },
             _ => false,
         }
     }
 
     fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: CompatMode) -> fmt::Result
     where
         W: Write,
--- a/servo/components/style/values/computed/length.rs
+++ b/servo/components/style/values/computed/length.rs
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 //! `<length>` computed values, and related ones.
 
 use super::{Context, Number, Percentage, ToComputedValue};
-use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero};
+use crate::values::animated::{ToAnimatedValue};
 use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
 use crate::values::generics::length::MaxLength as GenericMaxLength;
 use crate::values::generics::length::MozLength as GenericMozLength;
 use crate::values::generics::transform::IsZeroLength;
 use crate::values::generics::NonNegative;
 use crate::values::specified::length::ViewportPercentageLength;
 use crate::values::specified::length::{AbsoluteLength, FontBaseSize, FontRelativeLength};
 use crate::values::{specified, Auto, CSSFloat, Either, IsAuto, Normal};
@@ -63,55 +63,90 @@ impl ToComputedValue for specified::Leng
 
     #[inline]
     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
         specified::Length::NoCalc(specified::NoCalcLength::from_computed_value(computed))
     }
 }
 
 #[allow(missing_docs)]
-#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedZero)]
-pub struct CalcLengthOrPercentage {
+#[derive(Clone, Copy, Debug, MallocSizeOf, ToAnimatedZero)]
+pub struct LengthOrPercentage {
     #[animation(constant)]
     pub clamping_mode: AllowedNumericType,
     length: Length,
     pub percentage: Option<Percentage>,
+    /// Whether this was from a calc() expression. This is needed because right
+    /// now we don't treat calc() the same way as non-calc everywhere, but
+    /// that's a bug in most cases.
+    ///
+    /// Please don't add new uses of this that aren't for converting to Gecko's
+    /// representation, or to interpolate values.
+    ///
+    /// See https://github.com/w3c/csswg-drafts/issues/3482.
+    #[animation(constant)]
+    pub was_calc: bool,
 }
 
-impl ComputeSquaredDistance for CalcLengthOrPercentage {
+// FIXME(emilio): This is a bit of a hack that can disappear as soon as we share
+// representation of LengthOrPercentage with Gecko. The issue here is that Gecko
+// uses CalcValue to represent position components, so they always come back as
+// was_calc == true, and we mess up in the transitions code.
+//
+// This was a pre-existing bug, though arguably so only in pretty obscure cases
+// like calc(0px + 5%) and such.
+impl PartialEq for LengthOrPercentage {
+    fn eq(&self, other: &Self) -> bool {
+        self.length == other.length && self.percentage == other.percentage
+    }
+}
+
+impl ComputeSquaredDistance for LengthOrPercentage {
     #[inline]
     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
         // FIXME(nox): This looks incorrect to me, to add a distance between lengths
         // with a distance between percentages.
         Ok(self
             .unclamped_length()
             .compute_squared_distance(&other.unclamped_length())? +
             self.percentage()
                 .compute_squared_distance(&other.percentage())?)
     }
 }
 
-impl CalcLengthOrPercentage {
-    /// Returns a new `CalcLengthOrPercentage`.
+impl LengthOrPercentage {
+    /// Returns a new `LengthOrPercentage`.
     #[inline]
     pub fn new(length: Length, percentage: Option<Percentage>) -> Self {
-        Self::with_clamping_mode(length, percentage, AllowedNumericType::All)
+        Self::with_clamping_mode(
+            length,
+            percentage,
+            AllowedNumericType::All,
+            /* was_calc = */ false,
+        )
     }
 
-    /// Returns a new `CalcLengthOrPercentage` with a specific clamping mode.
+    /// Returns a new `LengthOrPercentage` with zero length and some percentage.
+    pub fn new_percent(percentage: Percentage) -> Self {
+        Self::new(Length::zero(), Some(percentage))
+    }
+
+    /// Returns a new `LengthOrPercentage` with a specific clamping mode.
     #[inline]
     pub fn with_clamping_mode(
         length: Length,
         percentage: Option<Percentage>,
         clamping_mode: AllowedNumericType,
+        was_calc: bool,
     ) -> Self {
         Self {
             clamping_mode,
             length,
             percentage,
+            was_calc,
         }
     }
 
     /// Returns this `calc()` as a `<length>`.
     ///
     /// Panics in debug mode if a percentage is present in the expression.
     #[inline]
     pub fn length(&self) -> CSSPixelLength {
@@ -126,129 +161,76 @@ impl CalcLengthOrPercentage {
     }
 
     /// Returns the `<length>` component of this `calc()`, unclamped.
     #[inline]
     pub fn unclamped_length(&self) -> CSSPixelLength {
         self.length
     }
 
+
     /// Return the percentage value as CSSFloat.
     #[inline]
     pub fn percentage(&self) -> CSSFloat {
         self.percentage.map_or(0., |p| p.0)
     }
 
+    /// Returns the percentage component if this could be represented as a
+    /// non-calc percentage.
+    pub fn as_percentage(&self) -> Option<Percentage> {
+        if self.length.px() != 0. {
+            return None;
+        }
+
+        let p = self.percentage?;
+        if self.clamping_mode.clamp(p.0) != p.0 {
+            return None;
+        }
+
+        Some(p)
+    }
+
     /// Convert the computed value into used value.
     #[inline]
-    pub fn to_used_value(&self, container_len: Option<Au>) -> Option<Au> {
-        self.to_pixel_length(container_len).map(Au::from)
+    pub fn maybe_to_used_value(&self, container_len: Option<Au>) -> Option<Au> {
+        self.maybe_to_pixel_length(container_len).map(Au::from)
     }
 
     /// If there are special rules for computing percentages in a value (e.g.
     /// the height property), they apply whenever a calc() expression contains
     /// percentages.
-    pub fn to_pixel_length(&self, container_len: Option<Au>) -> Option<Length> {
+    pub fn maybe_to_pixel_length(&self, container_len: Option<Au>) -> Option<Length> {
         match (container_len, self.percentage) {
             (Some(len), Some(percent)) => {
                 let pixel = self.length.px() + len.scale_by(percent.0).to_f32_px();
                 Some(Length::new(self.clamping_mode.clamp(pixel)))
             },
             (_, None) => Some(self.length()),
             _ => None,
         }
     }
 }
 
-impl From<LengthOrPercentage> for CalcLengthOrPercentage {
-    fn from(len: LengthOrPercentage) -> CalcLengthOrPercentage {
-        match len {
-            LengthOrPercentage::Percentage(this) => {
-                CalcLengthOrPercentage::new(Length::new(0.), Some(this))
-            },
-            LengthOrPercentage::Length(this) => CalcLengthOrPercentage::new(this, None),
-            LengthOrPercentage::Calc(this) => this,
-        }
-    }
-}
-
-impl From<LengthOrPercentageOrAuto> for Option<CalcLengthOrPercentage> {
-    fn from(len: LengthOrPercentageOrAuto) -> Option<CalcLengthOrPercentage> {
-        match len {
-            LengthOrPercentageOrAuto::Percentage(this) => {
-                Some(CalcLengthOrPercentage::new(Length::new(0.), Some(this)))
-            },
-            LengthOrPercentageOrAuto::Length(this) => Some(CalcLengthOrPercentage::new(this, None)),
-            LengthOrPercentageOrAuto::Calc(this) => Some(this),
-            LengthOrPercentageOrAuto::Auto => None,
-        }
-    }
-}
-
-impl From<LengthOrPercentageOrNone> for Option<CalcLengthOrPercentage> {
-    fn from(len: LengthOrPercentageOrNone) -> Option<CalcLengthOrPercentage> {
-        match len {
-            LengthOrPercentageOrNone::Percentage(this) => {
-                Some(CalcLengthOrPercentage::new(Length::new(0.), Some(this)))
-            },
-            LengthOrPercentageOrNone::Length(this) => Some(CalcLengthOrPercentage::new(this, None)),
-            LengthOrPercentageOrNone::Calc(this) => Some(this),
-            LengthOrPercentageOrNone::None => None,
-        }
-    }
-}
-
-impl ToCss for CalcLengthOrPercentage {
+impl ToCss for LengthOrPercentage {
     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
     where
         W: Write,
     {
-        use num_traits::Zero;
-
-        let length = self.unclamped_length();
-        match self.percentage {
-            Some(p) => {
-                if length.px() == 0. && self.clamping_mode.clamp(p.0) == p.0 {
-                    return p.to_css(dest);
-                }
-            },
-            None => {
-                if self.clamping_mode.clamp(length.px()) == length.px() {
-                    return length.to_css(dest);
-                }
-            },
-        }
-
-        dest.write_str("calc(")?;
-        if let Some(percentage) = self.percentage {
-            percentage.to_css(dest)?;
-            if length.px() != 0. {
-                dest.write_str(if length.px() < Zero::zero() {
-                    " - "
-                } else {
-                    " + "
-                })?;
-                length.abs().to_css(dest)?;
-            }
-        } else {
-            length.to_css(dest)?;
-        }
-
-        dest.write_str(")")
+        specified::LengthOrPercentage::from_computed_value(self).to_css(dest)
     }
 }
 
 impl specified::CalcLengthOrPercentage {
     /// Compute the value, zooming any absolute units by the zoom function.
     fn to_computed_value_with_zoom<F>(
         &self,
         context: &Context,
         zoom_fn: F,
         base_size: FontBaseSize,
-    ) -> CalcLengthOrPercentage
+    ) -> LengthOrPercentage
     where
         F: Fn(Length) -> Length,
     {
         use std::f32;
         let mut length = 0.;
 
         if let Some(absolute) = self.absolute {
             length += zoom_fn(absolute.to_computed_value(context)).px();
@@ -272,29 +254,30 @@ impl specified::CalcLengthOrPercentage {
             self.ex.map(FontRelativeLength::Ex),
             self.rem.map(FontRelativeLength::Rem),
         ] {
             if let Some(val) = *val {
                 length += val.to_computed_value(context, base_size).px();
             }
         }
 
-        CalcLengthOrPercentage {
+        LengthOrPercentage {
             clamping_mode: self.clamping_mode,
             length: Length::new(length.min(f32::MAX).max(f32::MIN)),
             percentage: self.percentage,
+            was_calc: true,
         }
     }
 
     /// Compute font-size or line-height taking into account text-zoom if necessary.
     pub fn to_computed_value_zoomed(
         &self,
         context: &Context,
         base_size: FontBaseSize,
-    ) -> CalcLengthOrPercentage {
+    ) -> LengthOrPercentage {
         self.to_computed_value_with_zoom(
             context,
             |abs| context.maybe_zoom_text(abs.into()).0,
             base_size,
         )
     }
 
     /// Compute the value into pixel length as CSSFloat without context,
@@ -319,227 +302,164 @@ impl specified::CalcLengthOrPercentage {
                 debug_assert!(false, "Someone forgot to handle an unit here: {:?}", self);
                 Err(())
             },
         }
     }
 }
 
 impl ToComputedValue for specified::CalcLengthOrPercentage {
-    type ComputedValue = CalcLengthOrPercentage;
+    type ComputedValue = LengthOrPercentage;
 
-    fn to_computed_value(&self, context: &Context) -> CalcLengthOrPercentage {
+    fn to_computed_value(&self, context: &Context) -> LengthOrPercentage {
         // normal properties don't zoom, and compute em units against the current style's font-size
         self.to_computed_value_with_zoom(context, |abs| abs, FontBaseSize::CurrentStyle)
     }
 
     #[inline]
-    fn from_computed_value(computed: &CalcLengthOrPercentage) -> Self {
+    fn from_computed_value(computed: &LengthOrPercentage) -> Self {
         specified::CalcLengthOrPercentage {
             clamping_mode: computed.clamping_mode,
             absolute: Some(AbsoluteLength::from_computed_value(&computed.length)),
             percentage: computed.percentage,
             ..Default::default()
         }
     }
 }
 
-#[allow(missing_docs)]
-#[animate(fallback = "Self::animate_fallback")]
-#[css(derive_debug)]
-#[derive(
-    Animate,
-    Clone,
-    ComputeSquaredDistance,
-    Copy,
-    MallocSizeOf,
-    PartialEq,
-    ToAnimatedValue,
-    ToAnimatedZero,
-    ToCss,
-)]
-#[distance(fallback = "Self::compute_squared_distance_fallback")]
-pub enum LengthOrPercentage {
-    Length(Length),
-    Percentage(Percentage),
-    Calc(CalcLengthOrPercentage),
-}
-
-impl LengthOrPercentage {
-    /// <https://drafts.csswg.org/css-transitions/#animtype-lpcalc>
-    fn animate_fallback(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
-        // Special handling for zero values since these should not require calc().
-        if self.is_definitely_zero() {
-            return other.to_animated_zero()?.animate(other, procedure);
-        }
-        if other.is_definitely_zero() {
-            return self.animate(&self.to_animated_zero()?, procedure);
-        }
-
-        let this = CalcLengthOrPercentage::from(*self);
-        let other = CalcLengthOrPercentage::from(*other);
-        Ok(LengthOrPercentage::Calc(this.animate(&other, procedure)?))
-    }
-
-    #[inline]
-    fn compute_squared_distance_fallback(&self, other: &Self) -> Result<SquaredDistance, ()> {
-        CalcLengthOrPercentage::compute_squared_distance(&(*self).into(), &(*other).into())
-    }
-}
-
-impl From<Au> for LengthOrPercentage {
-    #[inline]
-    fn from(length: Au) -> Self {
-        LengthOrPercentage::Length(length.into())
-    }
-}
-
 impl LengthOrPercentage {
     #[inline]
     #[allow(missing_docs)]
     pub fn zero() -> LengthOrPercentage {
-        LengthOrPercentage::Length(Length::new(0.))
+        LengthOrPercentage::new(Length::new(0.), None)
     }
 
+    /// 1px length value for SVG defaults
     #[inline]
-    /// 1px length value for SVG defaults
     pub fn one() -> LengthOrPercentage {
-        LengthOrPercentage::Length(Length::new(1.))
+        LengthOrPercentage::new(Length::new(1.), None)
     }
 
     /// Returns true if the computed value is absolute 0 or 0%.
-    ///
-    /// (Returns false for calc() values, even if ones that may resolve to zero.)
     #[inline]
     pub fn is_definitely_zero(&self) -> bool {
-        use self::LengthOrPercentage::*;
-        match *self {
-            Length(l) => l.px() == 0.0,
-            Percentage(p) => p.0 == 0.0,
-            Calc(_) => false,
-        }
+        self.unclamped_length().px() == 0.0 && self.percentage.map_or(true, |p| p.0 == 0.0)
     }
 
     // CSSFloat doesn't implement Hash, so does CSSPixelLength. Therefore, we still use Au as the
     // hash key.
     #[allow(missing_docs)]
     pub fn to_hash_key(&self) -> (Au, NotNan<f32>) {
-        use self::LengthOrPercentage::*;
-        match *self {
-            Length(l) => (Au::from(l), NotNan::new(0.0).unwrap()),
-            Percentage(p) => (Au(0), NotNan::new(p.0).unwrap()),
-            Calc(c) => (
-                Au::from(c.unclamped_length()),
-                NotNan::new(c.percentage()).unwrap(),
-            ),
-        }
+        (
+            Au::from(self.unclamped_length()),
+            NotNan::new(self.percentage()).unwrap(),
+        )
     }
 
     /// Returns the used value.
     pub fn to_used_value(&self, containing_length: Au) -> Au {
         Au::from(self.to_pixel_length(containing_length))
     }
 
     /// Returns the used value as CSSPixelLength.
     pub fn to_pixel_length(&self, containing_length: Au) -> Length {
-        match *self {
-            LengthOrPercentage::Length(length) => length,
-            LengthOrPercentage::Percentage(p) => containing_length.scale_by(p.0).into(),
-            LengthOrPercentage::Calc(ref calc) => {
-                calc.to_pixel_length(Some(containing_length)).unwrap()
-            },
-        }
+        self.maybe_to_pixel_length(Some(containing_length)).unwrap()
     }
 
     /// Returns the clamped non-negative values.
+    ///
+    /// TODO(emilio): It's a bit unfortunate that this depends on whether the
+    /// value was a `calc()` value or not. Should it?
     #[inline]
     pub fn clamp_to_non_negative(self) -> Self {
-        match self {
-            LengthOrPercentage::Length(length) => {
-                LengthOrPercentage::Length(length.clamp_to_non_negative())
-            },
-            LengthOrPercentage::Percentage(percentage) => {
-                LengthOrPercentage::Percentage(percentage.clamp_to_non_negative())
-            },
-            _ => self,
+        if self.was_calc {
+            return Self::with_clamping_mode(
+                self.length,
+                self.percentage,
+                AllowedNumericType::NonNegative,
+                self.was_calc,
+            )
         }
+
+        debug_assert!(self.percentage.is_none() || self.unclamped_length() == Length::zero());
+        if let Some(p) = self.percentage {
+            return Self::with_clamping_mode(
+                Length::zero(),
+                Some(p.clamp_to_non_negative()),
+                AllowedNumericType::NonNegative,
+                self.was_calc,
+            );
+        }
+
+        Self::with_clamping_mode(
+            self.length.clamp_to_non_negative(),
+            None,
+            AllowedNumericType::NonNegative,
+            self.was_calc,
+        )
     }
 }
 
 impl ToComputedValue for specified::LengthOrPercentage {
     type ComputedValue = LengthOrPercentage;
 
     fn to_computed_value(&self, context: &Context) -> LengthOrPercentage {
         match *self {
             specified::LengthOrPercentage::Length(ref value) => {
-                LengthOrPercentage::Length(value.to_computed_value(context))
+                LengthOrPercentage::new(value.to_computed_value(context), None)
             },
             specified::LengthOrPercentage::Percentage(value) => {
-                LengthOrPercentage::Percentage(value)
+                LengthOrPercentage::new_percent(value)
             },
             specified::LengthOrPercentage::Calc(ref calc) => {
-                LengthOrPercentage::Calc((**calc).to_computed_value(context))
+                (**calc).to_computed_value(context)
             },
         }
     }
 
     fn from_computed_value(computed: &LengthOrPercentage) -> Self {
-        match *computed {
-            LengthOrPercentage::Length(value) => {
-                specified::LengthOrPercentage::Length(ToComputedValue::from_computed_value(&value))
-            },
-            LengthOrPercentage::Percentage(value) => {
-                specified::LengthOrPercentage::Percentage(value)
-            },
-            LengthOrPercentage::Calc(ref calc) => specified::LengthOrPercentage::Calc(Box::new(
-                ToComputedValue::from_computed_value(calc),
-            )),
+        let length = computed.unclamped_length();
+        if let Some(p) = computed.as_percentage() {
+            return specified::LengthOrPercentage::Percentage(p)
         }
+
+        let percentage = computed.percentage;
+        if percentage.is_none() &&
+            computed.clamping_mode.clamp(length.px()) == length.px() {
+            return specified::LengthOrPercentage::Length(
+                ToComputedValue::from_computed_value(&length)
+            )
+        }
+
+        specified::LengthOrPercentage::Calc(Box::new(
+            ToComputedValue::from_computed_value(computed),
+        ))
     }
 }
 
 impl IsZeroLength for LengthOrPercentage {
     #[inline]
     fn is_zero_length(&self) -> bool {
-        match *self {
-            LengthOrPercentage::Length(l) => l.0 == 0.0,
-            LengthOrPercentage::Percentage(p) => p.0 == 0.0,
-            LengthOrPercentage::Calc(c) => c.unclamped_length().0 == 0.0 && c.percentage() == 0.0,
-        }
+        self.is_definitely_zero()
     }
 }
 
 #[allow(missing_docs)]
-#[animate(fallback = "Self::animate_fallback")]
 #[css(derive_debug)]
-#[derive(Animate, Clone, ComputeSquaredDistance, Copy, MallocSizeOf, PartialEq, ToCss)]
-#[distance(fallback = "Self::compute_squared_distance_fallback")]
+#[derive(Animate, Clone, ComputeSquaredDistance, Copy, MallocSizeOf, PartialEq, ToAnimatedZero, ToCss)]
 pub enum LengthOrPercentageOrAuto {
-    Length(Length),
-    Percentage(Percentage),
+    LengthOrPercentage(LengthOrPercentage),
     Auto,
-    Calc(CalcLengthOrPercentage),
 }
 
 impl LengthOrPercentageOrAuto {
-    /// <https://drafts.csswg.org/css-transitions/#animtype-lpcalc>
-    fn animate_fallback(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
-        let this = <Option<CalcLengthOrPercentage>>::from(*self);
-        let other = <Option<CalcLengthOrPercentage>>::from(*other);
-        Ok(LengthOrPercentageOrAuto::Calc(
-            this.animate(&other, procedure)?.ok_or(())?,
-        ))
-    }
-
+    /// Returns the `0` value.
     #[inline]
-    fn compute_squared_distance_fallback(&self, other: &Self) -> Result<SquaredDistance, ()> {
-        <Option<CalcLengthOrPercentage>>::compute_squared_distance(
-            &(*self).into(),
-            &(*other).into(),
-        )
+    pub fn zero() -> Self {
+        LengthOrPercentageOrAuto::LengthOrPercentage(LengthOrPercentage::zero())
     }
 }
 
 /// A wrapper of LengthOrPercentageOrAuto, whose value must be >= 0.
 pub type NonNegativeLengthOrPercentageOrAuto = NonNegative<LengthOrPercentageOrAuto>;
 
 impl IsAuto for NonNegativeLengthOrPercentageOrAuto {
     #[inline]
@@ -567,176 +487,131 @@ impl ToAnimatedValue for NonNegativeLeng
     #[inline]
     fn from_animated_value(animated: Self::AnimatedValue) -> Self {
         NonNegative(animated.clamp_to_non_negative())
     }
 }
 
 impl LengthOrPercentageOrAuto {
     /// Returns true if the computed value is absolute 0 or 0%.
-    ///
-    /// (Returns false for calc() values, even if ones that may resolve to zero.)
     #[inline]
     pub fn is_definitely_zero(&self) -> bool {
         use self::LengthOrPercentageOrAuto::*;
         match *self {
-            Length(l) => l.px() == 0.0,
-            Percentage(p) => p.0 == 0.0,
-            Calc(_) | Auto => false,
+            LengthOrPercentage(ref l) => l.is_definitely_zero(),
+            Auto => false,
         }
     }
 
-    fn clamp_to_non_negative(self) -> Self {
+    /// Clamps the value to a non-negative value.
+    pub fn clamp_to_non_negative(self) -> Self {
         use self::LengthOrPercentageOrAuto::*;
         match self {
-            Length(l) => Length(l.clamp_to_non_negative()),
-            Percentage(p) => Percentage(p.clamp_to_non_negative()),
-            _ => self,
+            LengthOrPercentage(l) => LengthOrPercentage(l.clamp_to_non_negative()),
+            Auto => Auto,
         }
     }
 }
 
 impl ToComputedValue for specified::LengthOrPercentageOrAuto {
     type ComputedValue = LengthOrPercentageOrAuto;
 
     #[inline]
     fn to_computed_value(&self, context: &Context) -> LengthOrPercentageOrAuto {
         match *self {
-            specified::LengthOrPercentageOrAuto::Length(ref value) => {
-                LengthOrPercentageOrAuto::Length(value.to_computed_value(context))
-            },
-            specified::LengthOrPercentageOrAuto::Percentage(value) => {
-                LengthOrPercentageOrAuto::Percentage(value)
+            specified::LengthOrPercentageOrAuto::LengthOrPercentage(ref value) => {
+                LengthOrPercentageOrAuto::LengthOrPercentage(
+                    value.to_computed_value(context),
+                )
             },
             specified::LengthOrPercentageOrAuto::Auto => LengthOrPercentageOrAuto::Auto,
-            specified::LengthOrPercentageOrAuto::Calc(ref calc) => {
-                LengthOrPercentageOrAuto::Calc((**calc).to_computed_value(context))
-            },
         }
     }
 
     #[inline]
     fn from_computed_value(computed: &LengthOrPercentageOrAuto) -> Self {
         match *computed {
             LengthOrPercentageOrAuto::Auto => specified::LengthOrPercentageOrAuto::Auto,
-            LengthOrPercentageOrAuto::Length(value) => specified::LengthOrPercentageOrAuto::Length(
-                ToComputedValue::from_computed_value(&value),
-            ),
-            LengthOrPercentageOrAuto::Percentage(value) => {
-                specified::LengthOrPercentageOrAuto::Percentage(value)
+            LengthOrPercentageOrAuto::LengthOrPercentage(ref value) => {
+                specified::LengthOrPercentageOrAuto::LengthOrPercentage(
+                    ToComputedValue::from_computed_value(value),
+                )
             },
-            LengthOrPercentageOrAuto::Calc(calc) => specified::LengthOrPercentageOrAuto::Calc(
-                Box::new(ToComputedValue::from_computed_value(&calc)),
-            ),
         }
     }
 }
 
 #[allow(missing_docs)]
-#[animate(fallback = "Self::animate_fallback")]
-#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
 #[css(derive_debug)]
-#[derive(Animate, Clone, ComputeSquaredDistance, Copy, PartialEq, ToCss)]
-#[distance(fallback = "Self::compute_squared_distance_fallback")]
+#[derive(Animate, Clone, ComputeSquaredDistance, Copy, MallocSizeOf, PartialEq, ToAnimatedZero, ToCss)]
 pub enum LengthOrPercentageOrNone {
-    Length(Length),
-    Percentage(Percentage),
-    Calc(CalcLengthOrPercentage),
+    LengthOrPercentage(LengthOrPercentage),
     None,
 }
 
 impl LengthOrPercentageOrNone {
-    /// <https://drafts.csswg.org/css-transitions/#animtype-lpcalc>
-    fn animate_fallback(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
-        let this = <Option<CalcLengthOrPercentage>>::from(*self);
-        let other = <Option<CalcLengthOrPercentage>>::from(*other);
-        Ok(LengthOrPercentageOrNone::Calc(
-            this.animate(&other, procedure)?.ok_or(())?,
-        ))
-    }
-
-    fn compute_squared_distance_fallback(&self, other: &Self) -> Result<SquaredDistance, ()> {
-        <Option<CalcLengthOrPercentage>>::compute_squared_distance(
-            &(*self).into(),
-            &(*other).into(),
-        )
-    }
-}
-
-impl LengthOrPercentageOrNone {
     /// Returns the used value.
     pub fn to_used_value(&self, containing_length: Au) -> Option<Au> {
         match *self {
             LengthOrPercentageOrNone::None => None,
-            LengthOrPercentageOrNone::Length(length) => Some(Au::from(length)),
-            LengthOrPercentageOrNone::Percentage(percent) => {
-                Some(containing_length.scale_by(percent.0))
+            LengthOrPercentageOrNone::LengthOrPercentage(ref lop) => {
+                Some(lop.to_used_value(containing_length))
             },
-            LengthOrPercentageOrNone::Calc(ref calc) => calc.to_used_value(Some(containing_length)),
         }
     }
 }
 
+// FIXME(emilio): Derive this.
 impl ToComputedValue for specified::LengthOrPercentageOrNone {
     type ComputedValue = LengthOrPercentageOrNone;
 
     #[inline]
     fn to_computed_value(&self, context: &Context) -> LengthOrPercentageOrNone {
         match *self {
-            specified::LengthOrPercentageOrNone::Length(ref value) => {
-                LengthOrPercentageOrNone::Length(value.to_computed_value(context))
-            },
-            specified::LengthOrPercentageOrNone::Percentage(value) => {
-                LengthOrPercentageOrNone::Percentage(value)
-            },
-            specified::LengthOrPercentageOrNone::Calc(ref calc) => {
-                LengthOrPercentageOrNone::Calc((**calc).to_computed_value(context))
+            specified::LengthOrPercentageOrNone::LengthOrPercentage(ref value) => {
+                LengthOrPercentageOrNone::LengthOrPercentage(value.to_computed_value(context))
             },
             specified::LengthOrPercentageOrNone::None => LengthOrPercentageOrNone::None,
         }
     }
 
     #[inline]
     fn from_computed_value(computed: &LengthOrPercentageOrNone) -> Self {
         match *computed {
             LengthOrPercentageOrNone::None => specified::LengthOrPercentageOrNone::None,
-            LengthOrPercentageOrNone::Length(value) => specified::LengthOrPercentageOrNone::Length(
-                ToComputedValue::from_computed_value(&value),
-            ),
-            LengthOrPercentageOrNone::Percentage(value) => {
-                specified::LengthOrPercentageOrNone::Percentage(value)
+            LengthOrPercentageOrNone::LengthOrPercentage(value) => {
+                specified::LengthOrPercentageOrNone::LengthOrPercentage(
+                    ToComputedValue::from_computed_value(&value),
+                )
             },
-            LengthOrPercentageOrNone::Calc(calc) => specified::LengthOrPercentageOrNone::Calc(
-                Box::new(ToComputedValue::from_computed_value(&calc)),
-            ),
         }
     }
 }
 
 /// A wrapper of LengthOrPercentage, whose value must be >= 0.
 pub type NonNegativeLengthOrPercentage = NonNegative<LengthOrPercentage>;
 
 impl ToAnimatedValue for NonNegativeLengthOrPercentage {
     type AnimatedValue = LengthOrPercentage;
 
     #[inline]
     fn to_animated_value(self) -> Self::AnimatedValue {
-        self.into()
+        self.0
     }
 
     #[inline]
     fn from_animated_value(animated: Self::AnimatedValue) -> Self {
-        animated.clamp_to_non_negative().into()
+        NonNegative(animated.clamp_to_non_negative())
     }
 }
 
 impl From<NonNegativeLength> for NonNegativeLengthOrPercentage {
     #[inline]
     fn from(length: NonNegativeLength) -> Self {
-        LengthOrPercentage::Length(length.0).into()
+        LengthOrPercentage::new(length.0, None).into()
     }
 }
 
 impl From<LengthOrPercentage> for NonNegativeLengthOrPercentage {
     #[inline]
     fn from(lop: LengthOrPercentage) -> Self {
         NonNegative::<LengthOrPercentage>(lop)
     }
@@ -744,16 +619,25 @@ impl From<LengthOrPercentage> for NonNeg
 
 impl From<NonNegativeLengthOrPercentage> for LengthOrPercentage {
     #[inline]
     fn from(lop: NonNegativeLengthOrPercentage) -> LengthOrPercentage {
         lop.0
     }
 }
 
+// TODO(emilio): This is a really generic impl which is only needed to implement
+// Animated and co for Spacing<>. Get rid of this, probably?
+impl From<Au> for LengthOrPercentage {
+    #[inline]
+    fn from(length: Au) -> Self {
+        LengthOrPercentage::new(length.into(), None)
+    }
+}
+
 impl NonNegativeLengthOrPercentage {
     /// Get zero value.
     #[inline]
     pub fn zero() -> Self {
         NonNegative::<LengthOrPercentage>(LengthOrPercentage::zero())
     }
 
     /// Returns true if the computed value is absolute 0 or 0%.
@@ -793,33 +677,36 @@ impl CSSPixelLength {
     }
 
     /// Return the containing pixel value.
     #[inline]
     pub fn px(&self) -> CSSFloat {
         self.0
     }
 
-    #[inline]
-    fn clamp_to_non_negative(self) -> Self {
-        Self::new(self.px().max(0.))
-    }
-
     /// Return the length with app_unit i32 type.
     #[inline]
     pub fn to_i32_au(&self) -> i32 {
         Au::from(*self).0
     }
 
     /// Return the absolute value of this length.
+    #[inline]
     pub fn abs(self) -> Self {
         CSSPixelLength::new(self.0.abs())
     }
 
+    /// Return the clamped value of this length.
+    #[inline]
+    pub fn clamp_to_non_negative(self) -> Self {
+        CSSPixelLength::new(self.0.max(0.))
+    }
+
     /// Zero value
+    #[inline]
     pub fn zero() -> Self {
         CSSPixelLength::new(0.)
     }
 }
 
 impl ToCss for CSSPixelLength {
     #[inline]
     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -57,17 +57,17 @@ pub use self::font::{FontFeatureSettings
 pub use self::font::{FontSize, FontSizeAdjust, FontStretch, FontSynthesis};
 pub use self::font::{FontVariantAlternates, FontWeight};
 pub use self::font::{FontVariantEastAsian, FontVariationSettings};
 pub use self::font::{MozScriptLevel, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
 #[cfg(feature = "gecko")]
 pub use self::gecko::ScrollSnapPoint;
 pub use self::image::{Gradient, GradientItem, Image, ImageLayer, LineDirection, MozImageRect};
 pub use self::length::{CSSPixelLength, ExtremumLength, NonNegativeLength};
-pub use self::length::{CalcLengthOrPercentage, Length, LengthOrNumber, LengthOrPercentage};
+pub use self::length::{Length, LengthOrNumber, LengthOrPercentage};
 pub use self::length::{LengthOrPercentageOrAuto, LengthOrPercentageOrNone, MaxLength, MozLength};
 pub use self::length::{NonNegativeLengthOrPercentage, NonNegativeLengthOrPercentageOrAuto};
 #[cfg(feature = "gecko")]
 pub use self::list::ListStyleType;
 pub use self::list::{QuotePair, Quotes};
 pub use self::motion::OffsetPath;
 pub use self::outline::OutlineStyle;
 pub use self::percentage::{NonNegativePercentage, Percentage};
--- a/servo/components/style/values/computed/position.rs
+++ b/servo/components/style/values/computed/position.rs
@@ -23,18 +23,18 @@ pub type HorizontalPosition = LengthOrPe
 /// The computed value of a CSS vertical position.
 pub type VerticalPosition = LengthOrPercentage;
 
 impl Position {
     /// `50% 50%`
     #[inline]
     pub fn center() -> Self {
         Self::new(
-            LengthOrPercentage::Percentage(Percentage(0.5)),
-            LengthOrPercentage::Percentage(Percentage(0.5)),
+            LengthOrPercentage::new_percent(Percentage(0.5)),
+            LengthOrPercentage::new_percent(Percentage(0.5)),
         )
     }
 
     /// `0% 0%`
     #[inline]
     pub fn zero() -> Self {
         Self::new(LengthOrPercentage::zero(), LengthOrPercentage::zero())
     }
--- a/servo/components/style/values/computed/transform.rs
+++ b/servo/components/style/values/computed/transform.rs
@@ -26,18 +26,18 @@ pub type TransformOrigin = generic::Tran
 /// A vector to represent the direction vector (rotate axis) for Rotate3D.
 pub type DirectionVector = Vector3D<CSSFloat>;
 
 impl TransformOrigin {
     /// Returns the initial computed value for `transform-origin`.
     #[inline]
     pub fn initial_value() -> Self {
         Self::new(
-            LengthOrPercentage::Percentage(Percentage(0.5)),
-            LengthOrPercentage::Percentage(Percentage(0.5)),
+            LengthOrPercentage::new_percent(Percentage(0.5)),
+            LengthOrPercentage::new_percent(Percentage(0.5)),
             Length::new(0.),
         )
     }
 }
 
 /// computed value of matrix3d()
 pub type Matrix3D = generic::Matrix3D<Number>;
 
--- a/servo/components/style/values/generics/text.rs
+++ b/servo/components/style/values/generics/text.rs
@@ -98,20 +98,17 @@ where
     fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
         let zero = V::from(Au(0));
         let this = self.value().unwrap_or(&zero);
         let other = other.value().unwrap_or(&zero);
         this.compute_squared_distance(other)
     }
 }
 
-impl<V> ToAnimatedZero for Spacing<V>
-where
-    V: From<Au>,
-{
+impl<V> ToAnimatedZero for Spacing<V> {
     #[inline]
     fn to_animated_zero(&self) -> Result<Self, ()> {
         Err(())
     }
 }
 
 /// A generic value for the `line-height` property.
 #[derive(
--- a/servo/components/style/values/generics/transform.rs
+++ b/servo/components/style/values/generics/transform.rs
@@ -283,28 +283,24 @@ impl ToAbsoluteLength for ComputedLength
     fn to_pixel_length(&self, _containing_len: Option<Au>) -> Result<CSSFloat, ()> {
         Ok(self.px())
     }
 }
 
 impl ToAbsoluteLength for ComputedLengthOrPercentage {
     #[inline]
     fn to_pixel_length(&self, containing_len: Option<Au>) -> Result<CSSFloat, ()> {
-        let extract_pixel_length = |lop: &ComputedLengthOrPercentage| match *lop {
-            ComputedLengthOrPercentage::Length(px) => px.px(),
-            ComputedLengthOrPercentage::Percentage(_) => 0.,
-            ComputedLengthOrPercentage::Calc(calc) => calc.length().px(),
-        };
-
         match containing_len {
             Some(relative_len) => Ok(self.to_pixel_length(relative_len).px()),
             // If we don't have reference box, we cannot resolve the used value,
             // so only retrieve the length part. This will be used for computing
             // distance without any layout info.
-            None => Ok(extract_pixel_length(self)),
+            //
+            // FIXME(emilio): This looks wrong.
+            None => Ok(self.length_component().px()),
         }
     }
 }
 
 /// Support the conversion to a 3d matrix.
 pub trait ToMatrix {
     /// Check if it is a 3d transform function.
     fn is_3d(&self) -> bool;
--- a/servo/components/style/values/specified/font.rs
+++ b/servo/components/style/values/specified/font.rs
@@ -911,19 +911,17 @@ impl FontSize {
                             context,
                             FontBaseSize::InheritedStyleButStripEmUnits,
                         )
                         .length_component();
 
                     info = parent.keyword_info.map(|i| i.compose(ratio, abs.into()));
                 }
                 let calc = calc.to_computed_value_zoomed(context, base_size);
-                calc.to_used_value(Some(base_size.resolve(context)))
-                    .unwrap()
-                    .into()
+                calc.to_used_value(base_size.resolve(context)).into()
             },
             FontSize::Keyword(i) => {
                 // As a specified keyword, this is keyword derived
                 info = Some(i);
                 i.to_computed_value(context)
             },
             FontSize::Smaller => {
                 info = compose_keyword(1. / LARGER_FONT_SIZE_RATIO);
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -784,16 +784,22 @@ impl From<computed::Percentage> for Leng
 
 impl LengthOrPercentage {
     #[inline]
     /// Returns a `zero` length.
     pub fn zero() -> LengthOrPercentage {
         LengthOrPercentage::Length(NoCalcLength::zero())
     }
 
+    #[inline]
+    /// Returns a `0%` value.
+    pub fn zero_percent() -> LengthOrPercentage {
+        LengthOrPercentage::Percentage(computed::Percentage::zero())
+    }
+
     fn parse_internal<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
         num_context: AllowedNumericType,
         allow_quirks: AllowQuirks,
     ) -> Result<Self, ParseError<'i>> {
         // FIXME: remove early returns when lifetimes are non-lexical
         {
@@ -893,85 +899,37 @@ impl IsZeroLength for LengthOrPercentage
         }
     }
 }
 
 /// Either a `<length>`, a `<percentage>`, or the `auto` keyword.
 #[allow(missing_docs)]
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss)]
 pub enum LengthOrPercentageOrAuto {
-    Length(NoCalcLength),
-    Percentage(computed::Percentage),
+    LengthOrPercentage(LengthOrPercentage),
     Auto,
-    Calc(Box<CalcLengthOrPercentage>),
-}
-
-impl From<NoCalcLength> for LengthOrPercentageOrAuto {
-    #[inline]
-    fn from(len: NoCalcLength) -> Self {
-        LengthOrPercentageOrAuto::Length(len)
-    }
-}
-
-impl From<computed::Percentage> for LengthOrPercentageOrAuto {
-    #[inline]
-    fn from(pc: computed::Percentage) -> Self {
-        LengthOrPercentageOrAuto::Percentage(pc)
-    }
 }
 
 impl LengthOrPercentageOrAuto {
     fn parse_internal<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
         num_context: AllowedNumericType,
         allow_quirks: AllowQuirks,
     ) -> Result<Self, ParseError<'i>> {
-        // FIXME: remove early returns when lifetimes are non-lexical
-        {
-            let location = input.current_source_location();
-            let token = input.next()?;
-            match *token {
-                Token::Dimension {
-                    value, ref unit, ..
-                } if num_context.is_ok(context.parsing_mode, value) => {
-                    return NoCalcLength::parse_dimension(context, value, unit)
-                        .map(LengthOrPercentageOrAuto::Length)
-                        .map_err(|()| location.new_unexpected_token_error(token.clone()));
-                },
-                Token::Percentage { unit_value, .. }
-                    if num_context.is_ok(context.parsing_mode, unit_value) =>
-                {
-                    return Ok(LengthOrPercentageOrAuto::Percentage(computed::Percentage(
-                        unit_value,
-                    )));
-                }
-                Token::Number { value, .. } if num_context.is_ok(context.parsing_mode, value) => {
-                    if value != 0. &&
-                        !context.parsing_mode.allows_unitless_lengths() &&
-                        !allow_quirks.allowed(context.quirks_mode)
-                    {
-                        return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
-                    }
-                    return Ok(LengthOrPercentageOrAuto::Length(NoCalcLength::Absolute(
-                        AbsoluteLength::Px(value),
-                    )));
-                },
-                Token::Ident(ref value) if value.eq_ignore_ascii_case("auto") => {
-                    return Ok(LengthOrPercentageOrAuto::Auto);
-                },
-                Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {},
-                _ => return Err(location.new_unexpected_token_error(token.clone())),
-            }
+        if input.try(|i| i.expect_ident_matching("auto")).is_ok() {
+            return Ok(LengthOrPercentageOrAuto::Auto);
         }
 
-        let calc = input.parse_nested_block(|i| {
-            CalcNode::parse_length_or_percentage(context, i, num_context)
-        })?;
-        Ok(LengthOrPercentageOrAuto::Calc(Box::new(calc)))
+        Ok(LengthOrPercentageOrAuto::LengthOrPercentage(LengthOrPercentage::parse_internal(
+            context,
+            input,
+            num_context,
+            allow_quirks,
+        )?))
     }
 
     /// Parse a non-negative length, percentage, or auto.
     #[inline]
     pub fn parse_non_negative<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
     ) -> Result<LengthOrPercentageOrAuto, ParseError<'i>> {
@@ -995,23 +953,23 @@ impl LengthOrPercentageOrAuto {
 
     /// Returns the `auto` value.
     pub fn auto() -> Self {
         LengthOrPercentageOrAuto::Auto
     }
 
     /// Returns a value representing a `0` length.
     pub fn zero() -> Self {
-        LengthOrPercentageOrAuto::Length(NoCalcLength::zero())
+        LengthOrPercentageOrAuto::LengthOrPercentage(LengthOrPercentage::zero())
     }
 
     /// Returns a value representing `0%`.
     #[inline]
     pub fn zero_percent() -> Self {
-        LengthOrPercentageOrAuto::Percentage(computed::Percentage::zero())
+        LengthOrPercentageOrAuto::LengthOrPercentage(LengthOrPercentage::zero_percent())
     }
 
     /// Parses, with quirks.
     #[inline]
     pub fn parse_quirky<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
         allow_quirks: AllowQuirks,
@@ -1071,71 +1029,37 @@ impl Parse for NonNegativeLengthOrPercen
         )?))
     }
 }
 
 /// Either a `<length>`, a `<percentage>`, or the `none` keyword.
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss)]
 #[allow(missing_docs)]
 pub enum LengthOrPercentageOrNone {
-    Length(NoCalcLength),
-    Percentage(computed::Percentage),
-    Calc(Box<CalcLengthOrPercentage>),
+    LengthOrPercentage(LengthOrPercentage),
     None,
 }
 
 impl LengthOrPercentageOrNone {
     fn parse_internal<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
         num_context: AllowedNumericType,
         allow_quirks: AllowQuirks,
     ) -> Result<Self, ParseError<'i>> {
-        // FIXME: remove early returns when lifetimes are non-lexical
-        {
-            let location = input.current_source_location();
-            let token = input.next()?;
-            match *token {
-                Token::Dimension {
-                    value, ref unit, ..
-                } if num_context.is_ok(context.parsing_mode, value) => {
-                    return NoCalcLength::parse_dimension(context, value, unit)
-                        .map(LengthOrPercentageOrNone::Length)
-                        .map_err(|()| location.new_unexpected_token_error(token.clone()));
-                },
-                Token::Percentage { unit_value, .. }
-                    if num_context.is_ok(context.parsing_mode, unit_value) =>
-                {
-                    return Ok(LengthOrPercentageOrNone::Percentage(computed::Percentage(
-                        unit_value,
-                    )));
-                }
-                Token::Number { value, .. } if num_context.is_ok(context.parsing_mode, value) => {
-                    if value != 0. &&
-                        !context.parsing_mode.allows_unitless_lengths() &&
-                        !allow_quirks.allowed(context.quirks_mode)
-                    {
-                        return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
-                    }
-                    return Ok(LengthOrPercentageOrNone::Length(NoCalcLength::Absolute(
-                        AbsoluteLength::Px(value),
-                    )));
-                },
-                Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {},
-                Token::Ident(ref value) if value.eq_ignore_ascii_case("none") => {
-                    return Ok(LengthOrPercentageOrNone::None);
-                },
-                _ => return Err(location.new_unexpected_token_error(token.clone())),
-            }
+        if input.try(|i| i.expect_ident_matching("none")).is_ok() {
+            return Ok(LengthOrPercentageOrNone::None);
         }
 
-        let calc = input.parse_nested_block(|i| {
-            CalcNode::parse_length_or_percentage(context, i, num_context)
-        })?;
-        Ok(LengthOrPercentageOrNone::Calc(Box::new(calc)))
+        Ok(LengthOrPercentageOrNone::LengthOrPercentage(LengthOrPercentage::parse_internal(
+            context,
+            input,
+            num_context,
+            allow_quirks,
+        )?))
     }
 
     /// Parse a non-negative LengthOrPercentageOrNone.
     #[inline]
     pub fn parse_non_negative<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
     ) -> Result<Self, ParseError<'i>> {
--- a/servo/components/style/values/specified/position.rs
+++ b/servo/components/style/values/specified/position.rs
@@ -5,17 +5,16 @@
 //! CSS handling for the specified value of
 //! [`position`][position]s
 //!
 //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position
 
 use crate::hash::FxHashMap;
 use crate::parser::{Parse, ParserContext};
 use crate::str::HTML_SPACE_CHARACTERS;
-use crate::values::computed::CalcLengthOrPercentage;
 use crate::values::computed::LengthOrPercentage as ComputedLengthOrPercentage;
 use crate::values::computed::{Context, Percentage, ToComputedValue};
 use crate::values::generics::position::Position as GenericPosition;
 use crate::values::generics::position::ZIndex as GenericZIndex;
 use crate::values::specified::transform::OriginComponent;
 use crate::values::specified::{AllowQuirks, Integer, LengthOrPercentage};
 use crate::values::{Either, None_};
 use cssparser::Parser;
@@ -250,35 +249,31 @@ impl<S> PositionComponent<S> {
     }
 }
 
 impl<S: Side> ToComputedValue for PositionComponent<S> {
     type ComputedValue = ComputedLengthOrPercentage;
 
     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
         match *self {
-            PositionComponent::Center => ComputedLengthOrPercentage::Percentage(Percentage(0.5)),
+            PositionComponent::Center => ComputedLengthOrPercentage::new_percent(Percentage(0.5)),
             PositionComponent::Side(ref keyword, None) => {
                 let p = Percentage(if keyword.is_start() { 0. } else { 1. });
-                ComputedLengthOrPercentage::Percentage(p)
+                ComputedLengthOrPercentage::new_percent(p)
             },
             PositionComponent::Side(ref keyword, Some(ref length)) if !keyword.is_start() => {
-                match length.to_computed_value(context) {
-                    ComputedLengthOrPercentage::Length(length) => ComputedLengthOrPercentage::Calc(
-                        CalcLengthOrPercentage::new(-length, Some(Percentage::hundred())),
-                    ),
-                    ComputedLengthOrPercentage::Percentage(p) => {
-                        ComputedLengthOrPercentage::Percentage(Percentage(1.0 - p.0))
-                    },
-                    ComputedLengthOrPercentage::Calc(calc) => {
-                        let p = Percentage(1. - calc.percentage.map_or(0., |p| p.0));
-                        let l = -calc.unclamped_length();
-                        ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage::new(l, Some(p)))
-                    },
-                }
+                let length = length.to_computed_value(context);
+                let p = Percentage(1. - length.percentage());
+                let l = -length.unclamped_length();
+                ComputedLengthOrPercentage::with_clamping_mode(
+                    l,
+                    Some(p),
+                    length.clamping_mode,
+                    length.was_calc,
+                )
             },
             PositionComponent::Side(_, Some(ref length)) |
             PositionComponent::Length(ref length) => length.to_computed_value(context),
         }
     }
 
     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
         PositionComponent::Length(ToComputedValue::from_computed_value(computed))
--- a/servo/components/style/values/specified/transform.rs
+++ b/servo/components/style/values/specified/transform.rs
@@ -318,22 +318,22 @@ impl<S> ToComputedValue for OriginCompon
 where
     S: Side,
 {
     type ComputedValue = ComputedLengthOrPercentage;
 
     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
         match *self {
             OriginComponent::Center => {
-                ComputedLengthOrPercentage::Percentage(ComputedPercentage(0.5))
+                ComputedLengthOrPercentage::new_percent(ComputedPercentage(0.5))
             },
             OriginComponent::Length(ref length) => length.to_computed_value(context),
             OriginComponent::Side(ref keyword) => {
                 let p = ComputedPercentage(if keyword.is_start() { 0. } else { 1. });
-                ComputedLengthOrPercentage::Percentage(p)
+                ComputedLengthOrPercentage::new_percent(p)
             },
         }
     }
 
     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
         OriginComponent::Length(ToComputedValue::from_computed_value(computed))
     }
 }
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -4458,38 +4458,41 @@ pub extern "C" fn Servo_DeclarationBlock
 #[no_mangle]
 pub extern "C" fn Servo_DeclarationBlock_SetPixelValue(
     declarations: RawServoDeclarationBlockBorrowed,
     property: nsCSSPropertyID,
     value: f32,
 ) {
     use style::properties::longhands::border_spacing::SpecifiedValue as BorderSpacing;
     use style::properties::{LonghandId, PropertyDeclaration};
+    use style::values::generics::NonNegative;
     use style::values::generics::length::MozLength;
     use style::values::specified::length::{NoCalcLength, NonNegativeLength, NonNegativeLengthOrPercentage};
+    use style::values::specified::length::{LengthOrPercentageOrAuto, LengthOrPercentage};
     use style::values::specified::{BorderCornerRadius, BorderSideWidth};
 
     let long = get_longhand_from_id!(property);
     let nocalc = NoCalcLength::from_px(value);
-
+    let lop = LengthOrPercentage::Length(nocalc);
+    let lop_or_auto = LengthOrPercentageOrAuto::LengthOrPercentage(lop.clone());
     let prop = match_wrap_declared! { long,
-        Height => MozLength::LengthOrPercentageOrAuto(nocalc.into()),
-        Width => MozLength::LengthOrPercentageOrAuto(nocalc.into()),
+        Height => MozLength::LengthOrPercentageOrAuto(lop_or_auto),
+        Width => MozLength::LengthOrPercentageOrAuto(lop_or_auto),
         BorderTopWidth => BorderSideWidth::Length(nocalc.into()),
         BorderRightWidth => BorderSideWidth::Length(nocalc.into()),
         BorderBottomWidth => BorderSideWidth::Length(nocalc.into()),
         BorderLeftWidth => BorderSideWidth::Length(nocalc.into()),
-        MarginTop => nocalc.into(),
-        MarginRight => nocalc.into(),
-        MarginBottom => nocalc.into(),
-        MarginLeft => nocalc.into(),
-        PaddingTop => nocalc.into(),
-        PaddingRight => nocalc.into(),
-        PaddingBottom => nocalc.into(),
-        PaddingLeft => nocalc.into(),
+        MarginTop => lop_or_auto,
+        MarginRight => lop_or_auto,
+        MarginBottom => lop_or_auto,
+        MarginLeft => lop_or_auto,
+        PaddingTop => NonNegative(lop),
+        PaddingRight => NonNegative(lop),
+        PaddingBottom => NonNegative(lop),
+        PaddingLeft => NonNegative(lop),
         BorderSpacing => {
             let v = NonNegativeLength::from(nocalc);
             Box::new(BorderSpacing::new(v.clone(), v))
         },
         BorderTopLeftRadius => {
             let length = NonNegativeLengthOrPercentage::from(nocalc);
             Box::new(BorderCornerRadius::new(length.clone(), length))
         },
@@ -4517,17 +4520,17 @@ pub extern "C" fn Servo_DeclarationBlock
     property: nsCSSPropertyID,
     value: f32,
     unit: structs::nsCSSUnit,
 ) {
     use style::properties::longhands::_moz_script_min_size::SpecifiedValue as MozScriptMinSize;
     use style::properties::{LonghandId, PropertyDeclaration};
     use style::values::generics::length::MozLength;
     use style::values::specified::length::{AbsoluteLength, FontRelativeLength};
-    use style::values::specified::length::{LengthOrPercentage, NoCalcLength};
+    use style::values::specified::length::{LengthOrPercentage, LengthOrPercentageOrAuto, NoCalcLength};
 
     let long = get_longhand_from_id!(property);
     let nocalc = match unit {
         structs::nsCSSUnit::eCSSUnit_EM => {
             NoCalcLength::FontRelative(FontRelativeLength::Em(value))
         },
         structs::nsCSSUnit::eCSSUnit_XHeight => {
             NoCalcLength::FontRelative(FontRelativeLength::Ex(value))
@@ -4542,17 +4545,19 @@ pub extern "C" fn Servo_DeclarationBlock
         },
         structs::nsCSSUnit::eCSSUnit_Point => NoCalcLength::Absolute(AbsoluteLength::Pt(value)),
         structs::nsCSSUnit::eCSSUnit_Pica => NoCalcLength::Absolute(AbsoluteLength::Pc(value)),
         structs::nsCSSUnit::eCSSUnit_Quarter => NoCalcLength::Absolute(AbsoluteLength::Q(value)),
         _ => unreachable!("Unknown unit passed to SetLengthValue"),
     };
 
     let prop = match_wrap_declared! { long,
-        Width => MozLength::LengthOrPercentageOrAuto(nocalc.into()),
+        Width => MozLength::LengthOrPercentageOrAuto(LengthOrPercentageOrAuto::LengthOrPercentage(
+                LengthOrPercentage::Length(nocalc)
+        )),
         FontSize => LengthOrPercentage::from(nocalc).into(),
         MozScriptMinSize => MozScriptMinSize(nocalc),
     };
     write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
         decls.push(prop, Importance::Normal);
     })
 }
 
@@ -4582,28 +4587,30 @@ pub extern "C" fn Servo_DeclarationBlock
 pub extern "C" fn Servo_DeclarationBlock_SetPercentValue(
     declarations: RawServoDeclarationBlockBorrowed,
     property: nsCSSPropertyID,
     value: f32,
 ) {
     use style::properties::{LonghandId, PropertyDeclaration};
     use style::values::computed::Percentage;
     use style::values::generics::length::MozLength;
-    use style::values::specified::length::LengthOrPercentage;
+    use style::values::specified::length::{LengthOrPercentageOrAuto, LengthOrPercentage};
 
     let long = get_longhand_from_id!(property);
     let pc = Percentage(value);
+    let lop_or_auto =
+        LengthOrPercentageOrAuto::LengthOrPercentage(LengthOrPercentage::Percentage(pc));
 
     let prop = match_wrap_declared! { long,
-        Height => MozLength::LengthOrPercentageOrAuto(pc.into()),
-        Width => MozLength::LengthOrPercentageOrAuto(pc.into()),
-        MarginTop => pc.into(),
-        MarginRight => pc.into(),
-        MarginBottom => pc.into(),
-        MarginLeft => pc.into(),
+        Height => MozLength::LengthOrPercentageOrAuto(lop_or_auto),
+        Width => MozLength::LengthOrPercentageOrAuto(lop_or_auto),
+        MarginTop => lop_or_auto,
+        MarginRight => lop_or_auto,
+        MarginBottom => lop_or_auto,
+        MarginLeft => lop_or_auto,
         FontSize => LengthOrPercentage::from(pc).into(),
     };
     write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
         decls.push(prop, Importance::Normal);
     })
 }
 
 #[no_mangle]
--- a/testing/web-platform/tests/web-animations/animation-model/animation-types/property-types.js
+++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/property-types.js
@@ -1880,17 +1880,17 @@ const translateListType = {
       const target = createTestElement(t, setup);
       const animation = target.animate(
         {
           [idlName]: ['0% -101px 600px', '400px 50% -200px'],
         },
         1000
       );
       testAnimationSamples(animation, idlName,
-        [{ time: 500,  expected: '200px calc(25% - 50.5px) 200px' }]);
+        [{ time: 500,  expected: 'calc(0% + 200px) calc(25% - 50.5px) 200px' }]);
     }, `${property} with combination of percentages and lengths`);
   },
   testAddition: function(property, setup) {
     test(t => {
       const idlName = propertyToIDL(property);
       const target = createTestElement(t, setup);
       target.style[idlName] = '100px';
       const animation = target.animate({ [idlName]: ['-200px', '500px'] },