servo: Merge #16728 - style: Rewrite calc to be cleaner and support arbitrary expressions (from emilio:recalc); r=waffles
authorEmilio Cobos Álvarez <emilio@crisal.io>
Fri, 05 May 2017 05:09:14 -0500
changeset 356804 d5aa4f2a3c9bd947292ce93a82c37c552cfe6b1e
parent 356803 c8e67ddc6f34278a29e50fa4e8bf9935d630032c
child 356805 7924b351f42e2aebd9d5f9583312594ae0f5ea9d
push id89970
push userkwierso@gmail.com
push dateFri, 05 May 2017 21:20:56 +0000
treeherdermozilla-inbound@c3d254b2070d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswaffles
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #16728 - style: Rewrite calc to be cleaner and support arbitrary expressions (from emilio:recalc); r=waffles This improves Servo's calc support compliant with[1], and makes it cleaner and more straight-forward. (also fixes #15192) [1]: https://github.com/w3c/csswg-drafts/issues/1241 Source-Repo: https://github.com/servo/servo Source-Revision: 7fc01437f4c8935951add61a76230131134382f8
servo/components/style/values/specified/calc.rs
servo/components/style/values/specified/length.rs
servo/components/style/values/specified/mod.rs
servo/tests/unit/style/parsing/inherited_box.rs
servo/tests/unit/style/parsing/length.rs
servo/tests/unit/style/parsing/value.rs
servo/tests/unit/style/properties/serialization.rs
new file mode 100644
--- /dev/null
+++ b/servo/components/style/values/specified/calc.rs
@@ -0,0 +1,547 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! [Calc expressions][calc].
+//!
+//! [calc]: https://drafts.csswg.org/css-values/#calc-notation
+
+use app_units::Au;
+use cssparser::{Parser, Token};
+use parser::ParserContext;
+use std::ascii::AsciiExt;
+use std::fmt;
+use style_traits::ToCss;
+use values::{CSSInteger, CSSFloat, HasViewportPercentage};
+use values::specified::{Angle, Time};
+use values::specified::length::{FontRelativeLength, NoCalcLength, ViewportPercentageLength};
+
+/// A node inside a `Calc` expression's AST.
+#[derive(Clone, Debug)]
+pub enum CalcNode {
+    /// `<length>`
+    Length(NoCalcLength),
+    /// `<angle>`
+    Angle(Angle),
+    /// `<time>`
+    Time(Time),
+    /// `<percentage>`
+    Percentage(CSSFloat),
+    /// `<number>`
+    Number(CSSFloat),
+    /// An expression of the form `x + y`
+    Sum(Box<CalcNode>, Box<CalcNode>),
+    /// An expression of the form `x - y`
+    Sub(Box<CalcNode>, Box<CalcNode>),
+    /// An expression of the form `x * y`
+    Mul(Box<CalcNode>, Box<CalcNode>),
+    /// An expression of the form `x / y`
+    Div(Box<CalcNode>, Box<CalcNode>),
+}
+
+/// An expected unit we intend to parse within a `calc()` expression.
+///
+/// This is used as a hint for the parser to fast-reject invalid expressions.
+#[derive(Clone, Copy, PartialEq)]
+pub enum CalcUnit {
+    /// `<number>`
+    Number,
+    /// `<integer>`
+    Integer,
+    /// `<length>`
+    Length,
+    /// `<length> | <percentage>`
+    LengthOrPercentage,
+    /// `<angle>`
+    Angle,
+    /// `<time>`
+    Time,
+}
+
+/// A struct to hold a simplified `<length>` or `<percentage>` expression.
+#[derive(Clone, PartialEq, Copy, Debug, Default)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+#[allow(missing_docs)]
+pub struct CalcLengthOrPercentage {
+    pub absolute: Option<Au>,
+    pub vw: Option<CSSFloat>,
+    pub vh: Option<CSSFloat>,
+    pub vmin: Option<CSSFloat>,
+    pub vmax: Option<CSSFloat>,
+    pub em: Option<CSSFloat>,
+    pub ex: Option<CSSFloat>,
+    pub ch: Option<CSSFloat>,
+    pub rem: Option<CSSFloat>,
+    pub percentage: Option<CSSFloat>,
+    #[cfg(feature = "gecko")]
+    pub mozmm: Option<CSSFloat>,
+}
+
+impl HasViewportPercentage for CalcLengthOrPercentage {
+    fn has_viewport_percentage(&self) -> bool {
+        self.vw.is_some() || self.vh.is_some() ||
+        self.vmin.is_some() || self.vmax.is_some()
+    }
+}
+
+impl ToCss for CalcLengthOrPercentage {
+    #[allow(unused_assignments)]
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        let mut first_value = true;
+        macro_rules! first_value_check {
+            () => {
+                if !first_value {
+                    try!(dest.write_str(" + "));
+                } else {
+                    first_value = false;
+                }
+            };
+        }
+
+        macro_rules! serialize {
+            ( $( $val:ident ),* ) => {
+                $(
+                    if let Some(val) = self.$val {
+                        first_value_check!();
+                        try!(val.to_css(dest));
+                        try!(dest.write_str(stringify!($val)));
+                    }
+                )*
+            };
+        }
+
+        try!(dest.write_str("calc("));
+
+        serialize!(ch, em, ex, rem, vh, vmax, vmin, vw);
+
+        #[cfg(feature = "gecko")]
+        {
+            serialize!(mozmm);
+        }
+
+        if let Some(val) = self.absolute {
+            first_value_check!();
+            try!(val.to_css(dest));
+        }
+
+        if let Some(val) = self.percentage {
+            first_value_check!();
+            try!(write!(dest, "{}%", val * 100.));
+        }
+
+        write!(dest, ")")
+    }
+}
+
+impl CalcNode {
+    /// Tries to parse a single element in the expression, that is, a
+    /// `<length>`, `<angle>`, `<time>`, `<percentage>`, according to
+    /// `expected_unit`.
+    ///
+    /// May return a "complex" `CalcNode`, in the presence of a parenthesized
+    /// expression, for example.
+    fn parse_one(
+        context: &ParserContext,
+        input: &mut Parser,
+        expected_unit: CalcUnit)
+        -> Result<Self, ()>
+    {
+        match (try!(input.next()), expected_unit) {
+            (Token::Number(ref value), _) => Ok(CalcNode::Number(value.value)),
+            (Token::Dimension(ref value, ref unit), CalcUnit::Length) |
+            (Token::Dimension(ref value, ref unit), CalcUnit::LengthOrPercentage) => {
+                NoCalcLength::parse_dimension(context, value.value, unit)
+                    .map(CalcNode::Length)
+            }
+            (Token::Dimension(ref value, ref unit), CalcUnit::Angle) => {
+                Angle::parse_dimension(value.value,
+                                       unit,
+                                       /* from_calc = */ true)
+                    .map(CalcNode::Angle)
+            }
+            (Token::Dimension(ref value, ref unit), CalcUnit::Time) => {
+                Time::parse_dimension(value.value,
+                                      unit,
+                                      /* from_calc = */ true)
+                    .map(CalcNode::Time)
+            }
+            (Token::Percentage(ref value), CalcUnit::LengthOrPercentage) => {
+                Ok(CalcNode::Percentage(value.unit_value))
+            }
+            (Token::ParenthesisBlock, _) => {
+                input.parse_nested_block(|i| {
+                    CalcNode::parse(context, i, expected_unit)
+                })
+            }
+            (Token::Function(ref name), _) if name.eq_ignore_ascii_case("calc") => {
+                input.parse_nested_block(|i| {
+                    CalcNode::parse(context, i, expected_unit)
+                })
+            }
+            _ => Err(())
+        }
+    }
+
+    /// Parse a top-level `calc` expression, with all nested sub-expressions.
+    ///
+    /// This is in charge of parsing, for example, `2 + 3 * 100%`.
+    fn parse(
+        context: &ParserContext,
+        input: &mut Parser,
+        expected_unit: CalcUnit)
+        -> Result<Self, ()>
+    {
+        let mut root = Self::parse_product(context, input, expected_unit)?;
+
+        loop {
+            let position = input.position();
+            match input.next_including_whitespace() {
+                Ok(Token::WhiteSpace(_)) => {
+                    if input.is_exhausted() {
+                        break; // allow trailing whitespace
+                    }
+                    match input.next()? {
+                        Token::Delim('+') => {
+                            let rhs =
+                                Self::parse_product(context, input, expected_unit)?;
+                            let new_root =
+                                CalcNode::Sum(Box::new(root), Box::new(rhs));
+                            root = new_root;
+                        }
+                        Token::Delim('-') => {
+                            let rhs =
+                                Self::parse_product(context, input, expected_unit)?;
+                            let new_root =
+                                CalcNode::Sub(Box::new(root), Box::new(rhs));
+                            root = new_root;
+                        }
+                        _ => return Err(()),
+                    }
+                }
+                _ => {
+                    input.reset(position);
+                    break
+                }
+            }
+        }
+
+        Ok(root)
+    }
+
+    /// Parse a top-level `calc` expression, and all the products that may
+    /// follow, and stop as soon as a non-product expression is found.
+    ///
+    /// This should parse correctly:
+    ///
+    ///     * `2`
+    ///     * `2 * 2`
+    ///     * `2 * 2 + 2` (but will leave the `+ 2` unparsed).
+    ///
+    fn parse_product(
+        context: &ParserContext,
+        input: &mut Parser,
+        expected_unit: CalcUnit)
+        -> Result<Self, ()>
+    {
+        let mut root = Self::parse_one(context, input, expected_unit)?;
+
+        loop {
+            let position = input.position();
+            match input.next() {
+                Ok(Token::Delim('*')) => {
+                    let rhs = Self::parse_one(context, input, expected_unit)?;
+                    let new_root = CalcNode::Mul(Box::new(root), Box::new(rhs));
+                    root = new_root;
+                }
+                // TODO(emilio): Figure out why the `Integer` check.
+                Ok(Token::Delim('/')) if expected_unit != CalcUnit::Integer => {
+                    let rhs = Self::parse_one(context, input, expected_unit)?;
+                    let new_root = CalcNode::Div(Box::new(root), Box::new(rhs));
+                    root = new_root;
+                }
+                _ => {
+                    input.reset(position);
+                    break
+                }
+            }
+        }
+
+        Ok(root)
+    }
+
+    /// Tries to simplify this expression into a `<length>` or `<percentage`>
+    /// value.
+    fn to_length_or_percentage(&self) -> Result<CalcLengthOrPercentage, ()> {
+        let mut ret = CalcLengthOrPercentage::default();
+        self.add_length_or_percentage_to(&mut ret, 1.0)?;
+        Ok(ret)
+    }
+
+    /// Puts this `<length>` or `<percentage>` into `ret`, or error.
+    ///
+    /// `factor` is the sign or multiplicative factor to account for the sign
+    /// (this allows adding and substracting into the return value).
+    fn add_length_or_percentage_to(
+        &self,
+        ret: &mut CalcLengthOrPercentage,
+        factor: CSSFloat)
+        -> Result<(), ()>
+    {
+        match *self {
+            CalcNode::Percentage(pct) => {
+                ret.percentage = Some(ret.percentage.unwrap_or(0.) + pct * factor)
+            }
+            CalcNode::Length(ref l) => {
+                match *l {
+                    NoCalcLength::Absolute(abs) => {
+                        ret.absolute = Some(
+                            ret.absolute.unwrap_or(Au(0)) +
+                            Au::from(abs).scale_by(factor)
+                        );
+                    }
+                    NoCalcLength::FontRelative(rel) => {
+                        match rel {
+                            FontRelativeLength::Em(em) => {
+                                ret.em = Some(ret.em.unwrap_or(0.) + em * factor);
+                            }
+                            FontRelativeLength::Ex(ex) => {
+                                ret.ex = Some(ret.em.unwrap_or(0.) + ex * factor);
+                            }
+                            FontRelativeLength::Ch(ch) => {
+                                ret.ch = Some(ret.ch.unwrap_or(0.) + ch * factor);
+                            }
+                            FontRelativeLength::Rem(rem) => {
+                                ret.rem = Some(ret.rem.unwrap_or(0.) + rem * factor);
+                            }
+                        }
+                    }
+                    NoCalcLength::ViewportPercentage(rel) => {
+                        match rel {
+                            ViewportPercentageLength::Vh(vh) => {
+                                ret.vh = Some(ret.vh.unwrap_or(0.) + vh * factor)
+                            }
+                            ViewportPercentageLength::Vw(vw) => {
+                                ret.vw = Some(ret.vw.unwrap_or(0.) + vw * factor)
+                            }
+                            ViewportPercentageLength::Vmax(vmax) => {
+                                ret.vmax = Some(ret.vmax.unwrap_or(0.) + vmax * factor)
+                            }
+                            ViewportPercentageLength::Vmin(vmin) => {
+                                ret.vmin = Some(ret.vmin.unwrap_or(0.) + vmin * factor)
+                            }
+                        }
+                    }
+                    NoCalcLength::ServoCharacterWidth(..) => unreachable!(),
+                    #[cfg(feature = "gecko")]
+                    NoCalcLength::Physical(physical) => {
+                        ret.mozmm = Some(ret.mozmm.unwrap_or(0.) + physical.0 * factor);
+                    }
+                }
+            }
+            CalcNode::Sub(ref a, ref b) => {
+                a.add_length_or_percentage_to(ret, factor)?;
+                b.add_length_or_percentage_to(ret, factor * -1.0)?;
+            }
+            CalcNode::Sum(ref a, ref b) => {
+                a.add_length_or_percentage_to(ret, factor)?;
+                b.add_length_or_percentage_to(ret, factor)?;
+            }
+            CalcNode::Mul(ref a, ref b) => {
+                match b.to_number() {
+                    Ok(rhs) => {
+                        a.add_length_or_percentage_to(ret, factor * rhs)?;
+                    }
+                    Err(..) => {
+                        let lhs = a.to_number()?;
+                        b.add_length_or_percentage_to(ret, factor * lhs)?;
+                    }
+                }
+            }
+            CalcNode::Div(ref a, ref b) => {
+                let new_factor = b.to_number()?;
+                if new_factor == 0. {
+                    return Err(());
+                }
+                a.add_length_or_percentage_to(ret, factor / new_factor)?;
+            }
+            CalcNode::Angle(..) |
+            CalcNode::Time(..) |
+            CalcNode::Number(..) => return Err(()),
+        }
+
+        Ok(())
+    }
+
+    /// Tries to simplify this expression into a `<time>` value.
+    fn to_time(&self) -> Result<Time, ()> {
+        Ok(match *self {
+            CalcNode::Time(ref time) => time.clone(),
+            CalcNode::Sub(ref a, ref b) => {
+                let lhs = a.to_time()?;
+                let rhs = b.to_time()?;
+                Time::from_calc(lhs.seconds() - rhs.seconds())
+            }
+            CalcNode::Sum(ref a, ref b) => {
+                let lhs = a.to_time()?;
+                let rhs = b.to_time()?;
+                Time::from_calc(lhs.seconds() + rhs.seconds())
+            }
+            CalcNode::Mul(ref a, ref b) => {
+                match b.to_number() {
+                    Ok(rhs) => {
+                        let lhs = a.to_time()?;
+                        Time::from_calc(lhs.seconds() * rhs)
+                    }
+                    Err(()) => {
+                        let lhs = a.to_number()?;
+                        let rhs = b.to_time()?;
+                        Time::from_calc(lhs * rhs.seconds())
+                    }
+                }
+            }
+            CalcNode::Div(ref a, ref b) => {
+                let lhs = a.to_time()?;
+                let rhs = b.to_number()?;
+                if rhs == 0. {
+                    return Err(())
+                }
+                Time::from_calc(lhs.seconds() / rhs)
+            }
+            CalcNode::Number(..) |
+            CalcNode::Length(..) |
+            CalcNode::Percentage(..) |
+            CalcNode::Angle(..) => return Err(()),
+        })
+    }
+
+    /// Tries to simplify this expression into an `Angle` value.
+    fn to_angle(&self) -> Result<Angle, ()> {
+        Ok(match *self {
+            CalcNode::Angle(ref angle) => angle.clone(),
+            CalcNode::Sub(ref a, ref b) => {
+                let lhs = a.to_angle()?;
+                let rhs = b.to_angle()?;
+                Angle::from_calc(lhs.radians() - rhs.radians())
+            }
+            CalcNode::Sum(ref a, ref b) => {
+                let lhs = a.to_angle()?;
+                let rhs = b.to_angle()?;
+                Angle::from_calc(lhs.radians() + rhs.radians())
+            }
+            CalcNode::Mul(ref a, ref b) => {
+                match a.to_angle() {
+                    Ok(lhs) => {
+                        let rhs = b.to_number()?;
+                        Angle::from_calc(lhs.radians() * rhs)
+                    }
+                    Err(..) => {
+                        let lhs = a.to_number()?;
+                        let rhs = b.to_angle()?;
+                        Angle::from_calc(lhs * rhs.radians())
+                    }
+                }
+            }
+            CalcNode::Div(ref a, ref b) => {
+                let lhs = a.to_angle()?;
+                let rhs = b.to_number()?;
+                if rhs == 0. {
+                    return Err(())
+                }
+                Angle::from_calc(lhs.radians() / rhs)
+            }
+            CalcNode::Number(..) |
+            CalcNode::Length(..) |
+            CalcNode::Percentage(..) |
+            CalcNode::Time(..) => return Err(()),
+        })
+    }
+
+    /// Tries to simplify this expression into a `<number>` value.
+    fn to_number(&self) -> Result<CSSFloat, ()> {
+        Ok(match *self {
+            CalcNode::Number(n) => n,
+            CalcNode::Sum(ref a, ref b) => {
+                a.to_number()? + b.to_number()?
+            }
+            CalcNode::Sub(ref a, ref b) => {
+                a.to_number()? - b.to_number()?
+            }
+            CalcNode::Mul(ref a, ref b) => {
+                a.to_number()? * b.to_number()?
+            }
+            CalcNode::Div(ref a, ref b) => {
+                let lhs = a.to_number()?;
+                let rhs = b.to_number()?;
+                if rhs == 0. {
+                    return Err(())
+                }
+                lhs / rhs
+            }
+            CalcNode::Length(..) |
+            CalcNode::Percentage(..) |
+            CalcNode::Angle(..) |
+            CalcNode::Time(..) => return Err(()),
+        })
+    }
+
+    /// Convenience parsing function for integers.
+    pub fn parse_integer(
+        context: &ParserContext,
+        input: &mut Parser)
+        -> Result<CSSInteger, ()>
+    {
+        Self::parse(context, input, CalcUnit::Integer)?
+            .to_number()
+            .map(|n| n as CSSInteger)
+    }
+
+    /// Convenience parsing function for `<length> | <percentage>`.
+    pub fn parse_length_or_percentage(
+        context: &ParserContext,
+        input: &mut Parser)
+        -> Result<CalcLengthOrPercentage, ()>
+    {
+        Self::parse(context, input, CalcUnit::LengthOrPercentage)?
+            .to_length_or_percentage()
+    }
+
+    /// Convenience parsing function for `<length>`.
+    pub fn parse_length(
+        context: &ParserContext,
+        input: &mut Parser)
+        -> Result<CalcLengthOrPercentage, ()>
+    {
+        Self::parse(context, input, CalcUnit::Length)?
+            .to_length_or_percentage()
+    }
+
+    /// Convenience parsing function for `<number>`.
+    pub fn parse_number(
+        context: &ParserContext,
+        input: &mut Parser)
+        -> Result<CSSFloat, ()>
+    {
+        Self::parse(context, input, CalcUnit::Number)?
+            .to_number()
+    }
+
+    /// Convenience parsing function for `<angle>`.
+    pub fn parse_angle(
+        context: &ParserContext,
+        input: &mut Parser)
+        -> Result<Angle, ()>
+    {
+        Self::parse(context, input, CalcUnit::Angle)?
+            .to_angle()
+    }
+
+    /// Convenience parsing function for `<time>`.
+    pub fn parse_time(
+        context: &ParserContext,
+        input: &mut Parser)
+        -> Result<Time, ()>
+    {
+        Self::parse(context, input, CalcUnit::Time)?
+            .to_time()
+    }
+}
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -12,21 +12,23 @@ use euclid::size::Size2D;
 use font_metrics::FontMetricsQueryResult;
 use parser::{Parse, ParserContext};
 use std::{cmp, fmt, mem};
 use std::ascii::AsciiExt;
 use std::ops::Mul;
 use style_traits::ToCss;
 use style_traits::values::specified::AllowedLengthType;
 use stylesheets::CssRuleType;
-use super::{AllowQuirks, Angle, Number, SimplifiedValueNode, SimplifiedSumNode, Time, ToComputedValue};
+use super::{AllowQuirks, Number, ToComputedValue};
 use values::{Auto, CSSFloat, Either, FONT_MEDIUM_PX, HasViewportPercentage, None_, Normal};
 use values::ExtremumLength;
 use values::computed::{ComputedValueAsSpecified, Context};
+use values::specified::calc::CalcNode;
 
+pub use values::specified::calc::CalcLengthOrPercentage;
 pub use super::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient};
 pub use super::image::{GradientKind, HorizontalDirection, Image, LengthOrKeyword, LengthOrPercentageOrKeyword};
 pub use super::image::{SizeKeyword, VerticalDirection};
 
 /// Number of app units per pixel
 pub const AU_PER_PX: CSSFloat = 60.;
 /// Number of app units per inch
 pub const AU_PER_IN: CSSFloat = AU_PER_PX * 96.;
@@ -632,17 +634,20 @@ impl Length {
                 if value.value != 0. && !context.length_parsing_mode.allows_unitless_lengths() &&
                    !allow_quirks.allowed(context.quirks_mode) {
                     return Err(())
                 }
                 Ok(Length::NoCalc(NoCalcLength::Absolute(AbsoluteLength::Px(value.value))))
             },
             Token::Function(ref name) if name.eq_ignore_ascii_case("calc") =>
                 input.parse_nested_block(|input| {
-                    CalcLengthOrPercentage::parse_length(context, input, num_context)
+                    CalcNode::parse_length(context, input)
+                        .map(|calc| {
+                            Length::Calc(num_context, Box::new(calc))
+                        })
                 }),
             _ => Err(())
         }
     }
 
     /// Parse a non-negative length
     #[inline]
     pub fn parse_non_negative(context: &ParserContext, input: &mut Parser) -> Result<Length, ()> {
@@ -695,442 +700,16 @@ impl<T: Parse> Either<Length, T> {
     pub fn parse_non_negative_length(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         if let Ok(v) = input.try(|input| T::parse(context, input)) {
             return Ok(Either::Second(v));
         }
         Length::parse_internal(context, input, AllowedLengthType::NonNegative, AllowQuirks::No).map(Either::First)
     }
 }
 
-/// A calc sum expression node.
-#[derive(Clone, Debug)]
-pub struct CalcSumNode {
-    /// The products of this node.
-    pub products: Vec<CalcProductNode>,
-}
-
-/// A calc product expression node.
-#[derive(Clone, Debug)]
-pub struct CalcProductNode {
-    /// The values inside this product node.
-    values: Vec<CalcValueNode>
-}
-
-/// A value inside a `Calc` expression.
-#[derive(Clone, Debug)]
-#[allow(missing_docs)]
-pub enum CalcValueNode {
-    Length(NoCalcLength),
-    Angle(CSSFloat),
-    Time(CSSFloat),
-    Percentage(CSSFloat),
-    Number(CSSFloat),
-    Sum(Box<CalcSumNode>),
-}
-
-#[derive(Clone, Copy, PartialEq)]
-#[allow(missing_docs)]
-pub enum CalcUnit {
-    Number,
-    Integer,
-    Length,
-    LengthOrPercentage,
-    Angle,
-    Time,
-}
-
-#[derive(Clone, PartialEq, Copy, Debug, Default)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[allow(missing_docs)]
-pub struct CalcLengthOrPercentage {
-    pub absolute: Option<Au>,
-    pub vw: Option<CSSFloat>,
-    pub vh: Option<CSSFloat>,
-    pub vmin: Option<CSSFloat>,
-    pub vmax: Option<CSSFloat>,
-    pub em: Option<CSSFloat>,
-    pub ex: Option<CSSFloat>,
-    pub ch: Option<CSSFloat>,
-    pub rem: Option<CSSFloat>,
-    pub percentage: Option<CSSFloat>,
-}
-
-impl CalcLengthOrPercentage {
-    /// Parse a calc sum node.
-    pub fn parse_sum(context: &ParserContext, input: &mut Parser, expected_unit: CalcUnit) -> Result<CalcSumNode, ()> {
-        let mut products = Vec::new();
-        products.push(try!(CalcLengthOrPercentage::parse_product(context, input, expected_unit)));
-
-        loop {
-            let position = input.position();
-            match input.next_including_whitespace() {
-                Ok(Token::WhiteSpace(_)) => {
-                    if input.is_exhausted() {
-                        break; // allow trailing whitespace
-                    }
-                    match input.next() {
-                        Ok(Token::Delim('+')) => {
-                            products.push(try!(CalcLengthOrPercentage::parse_product(context, input, expected_unit)));
-                        }
-                        Ok(Token::Delim('-')) => {
-                            let mut right = try!(CalcLengthOrPercentage::parse_product(context, input, expected_unit));
-                            right.values.push(CalcValueNode::Number(-1.));
-                            products.push(right);
-                        }
-                        _ => {
-                            return Err(());
-                        }
-                    }
-                }
-                _ => {
-                    input.reset(position);
-                    break
-                }
-            }
-        }
-        Ok(CalcSumNode { products: products })
-    }
-
-    fn parse_product(context: &ParserContext, input: &mut Parser, expected_unit: CalcUnit)
-                     -> Result<CalcProductNode, ()> {
-        let mut values = Vec::new();
-        values.push(try!(CalcLengthOrPercentage::parse_value(context, input, expected_unit)));
-
-        loop {
-            let position = input.position();
-            match input.next() {
-                Ok(Token::Delim('*')) => {
-                    values.push(try!(CalcLengthOrPercentage::parse_value(context, input, expected_unit)));
-                }
-                Ok(Token::Delim('/')) if expected_unit != CalcUnit::Integer => {
-                    if let Ok(Token::Number(ref value)) = input.next() {
-                        if value.value == 0. {
-                            return Err(());
-                        }
-                        values.push(CalcValueNode::Number(1. / value.value));
-                    } else {
-                        return Err(());
-                    }
-                }
-                _ => {
-                    input.reset(position);
-                    break
-                }
-            }
-        }
-
-        Ok(CalcProductNode { values: values })
-    }
-
-    fn parse_value(context: &ParserContext, input: &mut Parser, expected_unit: CalcUnit) -> Result<CalcValueNode, ()> {
-        match (try!(input.next()), expected_unit) {
-            (Token::Number(ref value), _) => Ok(CalcValueNode::Number(value.value)),
-            (Token::Dimension(ref value, ref unit), CalcUnit::Length) |
-            (Token::Dimension(ref value, ref unit), CalcUnit::LengthOrPercentage) => {
-                NoCalcLength::parse_dimension(context, value.value, unit).map(CalcValueNode::Length)
-            }
-            (Token::Dimension(ref value, ref unit), CalcUnit::Angle) => {
-                Angle::parse_dimension(value.value, unit).map(|angle| {
-                    CalcValueNode::Angle(angle.radians())
-                })
-            }
-            (Token::Dimension(ref value, ref unit), CalcUnit::Time) => {
-                Time::parse_dimension(value.value, unit).map(|time| {
-                    CalcValueNode::Time(time.seconds())
-                })
-            }
-            (Token::Percentage(ref value), CalcUnit::LengthOrPercentage) =>
-                Ok(CalcValueNode::Percentage(value.unit_value)),
-            (Token::ParenthesisBlock, _) => {
-                input.parse_nested_block(|i| CalcLengthOrPercentage::parse_sum(context, i, expected_unit))
-                     .map(|result| CalcValueNode::Sum(Box::new(result)))
-            },
-            (Token::Function(ref name), _) if name.eq_ignore_ascii_case("calc") => {
-                input.parse_nested_block(|i| CalcLengthOrPercentage::parse_sum(context, i, expected_unit))
-                     .map(|result| CalcValueNode::Sum(Box::new(result)))
-            }
-            _ => Err(())
-        }
-    }
-
-    fn simplify_value_to_number(node: &CalcValueNode) -> Option<CSSFloat> {
-        match *node {
-            CalcValueNode::Number(number) => Some(number),
-            CalcValueNode::Sum(ref sum) => CalcLengthOrPercentage::simplify_sum_to_number(sum),
-            _ => None
-        }
-    }
-
-    fn simplify_sum_to_number(node: &CalcSumNode) -> Option<CSSFloat> {
-        let mut sum = 0.;
-        for ref product in &node.products {
-            match CalcLengthOrPercentage::simplify_product_to_number(product) {
-                Some(number) => sum += number,
-                _ => return None
-            }
-        }
-        Some(sum)
-    }
-
-    fn simplify_product_to_number(node: &CalcProductNode) -> Option<CSSFloat> {
-        let mut product = 1.;
-        for ref value in &node.values {
-            match CalcLengthOrPercentage::simplify_value_to_number(value) {
-                Some(number) => product *= number,
-                _ => return None
-            }
-        }
-        Some(product)
-    }
-
-    fn simplify_products_in_sum(node: &CalcSumNode) -> Result<SimplifiedValueNode, ()> {
-        let mut simplified = Vec::new();
-        for product in &node.products {
-            match try!(CalcLengthOrPercentage::simplify_product(product)) {
-                SimplifiedValueNode::Sum(ref sum) => simplified.extend_from_slice(&sum.values),
-                val => simplified.push(val),
-            }
-        }
-
-        if simplified.len() == 1 {
-            Ok(simplified[0].clone())
-        } else {
-            Ok(SimplifiedValueNode::Sum(Box::new(SimplifiedSumNode { values: simplified })))
-        }
-    }
-
-    #[allow(missing_docs)]
-    pub fn simplify_product(node: &CalcProductNode) -> Result<SimplifiedValueNode, ()> {
-        let mut multiplier = 1.;
-        let mut node_with_unit = None;
-        for node in &node.values {
-            match CalcLengthOrPercentage::simplify_value_to_number(&node) {
-                Some(number) => multiplier *= number,
-                _ if node_with_unit.is_none() => {
-                    node_with_unit = Some(match *node {
-                        CalcValueNode::Sum(ref sum) =>
-                            try!(CalcLengthOrPercentage::simplify_products_in_sum(sum)),
-                        CalcValueNode::Length(ref l) => SimplifiedValueNode::Length(l.clone()),
-                        CalcValueNode::Angle(a) => SimplifiedValueNode::Angle(a),
-                        CalcValueNode::Time(t) => SimplifiedValueNode::Time(t),
-                        CalcValueNode::Percentage(p) => SimplifiedValueNode::Percentage(p),
-                        _ => unreachable!("Numbers should have been handled by simplify_value_to_nubmer")
-                    })
-                },
-                _ => return Err(()),
-            }
-        }
-
-        match node_with_unit {
-            None => Ok(SimplifiedValueNode::Number(multiplier)),
-            Some(ref value) => Ok(value * multiplier)
-        }
-    }
-
-    fn parse_length(context: &ParserContext,
-                    input: &mut Parser,
-                    num_context: AllowedLengthType) -> Result<Length, ()> {
-        CalcLengthOrPercentage::parse(context, input, CalcUnit::Length).map(|calc| {
-            Length::Calc(num_context, Box::new(calc))
-        })
-    }
-
-    fn parse_length_or_percentage(context: &ParserContext, input: &mut Parser) -> Result<CalcLengthOrPercentage, ()> {
-        CalcLengthOrPercentage::parse(context, input, CalcUnit::LengthOrPercentage)
-    }
-
-    #[allow(missing_docs)]
-    pub fn parse(context: &ParserContext,
-                 input: &mut Parser,
-                 expected_unit: CalcUnit) -> Result<CalcLengthOrPercentage, ()> {
-        let ast = try!(CalcLengthOrPercentage::parse_sum(context, input, expected_unit));
-
-        let mut simplified = Vec::new();
-        for ref node in ast.products {
-            match try!(CalcLengthOrPercentage::simplify_product(node)) {
-                SimplifiedValueNode::Sum(sum) => simplified.extend_from_slice(&sum.values),
-                value => simplified.push(value),
-            }
-        }
-
-        let mut absolute = None;
-        let mut vw = None;
-        let mut vh = None;
-        let mut vmax = None;
-        let mut vmin = None;
-        let mut em = None;
-        let mut ex = None;
-        let mut ch = None;
-        let mut rem = None;
-        let mut percentage = None;
-
-        for value in simplified {
-            match value {
-                SimplifiedValueNode::Percentage(p) =>
-                    percentage = Some(percentage.unwrap_or(0.) + p),
-                SimplifiedValueNode::Length(NoCalcLength::Absolute(length)) =>
-                    absolute = Some(absolute.unwrap_or(0.) + Au::from(length).to_f32_px()),
-                SimplifiedValueNode::Length(NoCalcLength::ViewportPercentage(v)) =>
-                    match v {
-                        ViewportPercentageLength::Vw(val) =>
-                            vw = Some(vw.unwrap_or(0.) + val),
-                        ViewportPercentageLength::Vh(val) =>
-                            vh = Some(vh.unwrap_or(0.) + val),
-                        ViewportPercentageLength::Vmin(val) =>
-                            vmin = Some(vmin.unwrap_or(0.) + val),
-                        ViewportPercentageLength::Vmax(val) =>
-                            vmax = Some(vmax.unwrap_or(0.) + val),
-                    },
-                SimplifiedValueNode::Length(NoCalcLength::FontRelative(f)) =>
-                    match f {
-                        FontRelativeLength::Em(val) =>
-                            em = Some(em.unwrap_or(0.) + val),
-                        FontRelativeLength::Ex(val) =>
-                            ex = Some(ex.unwrap_or(0.) + val),
-                        FontRelativeLength::Ch(val) =>
-                            ch = Some(ch.unwrap_or(0.) + val),
-                        FontRelativeLength::Rem(val) =>
-                            rem = Some(rem.unwrap_or(0.) + val),
-                    },
-                // TODO Add support for top level number in calc(). See servo/servo#14421.
-                _ => return Err(()),
-            }
-        }
-
-        Ok(CalcLengthOrPercentage {
-            absolute: absolute.map(Au::from_f32_px),
-            vw: vw,
-            vh: vh,
-            vmax: vmax,
-            vmin: vmin,
-            em: em,
-            ex: ex,
-            ch: ch,
-            rem: rem,
-            percentage: percentage,
-        })
-    }
-
-    #[allow(missing_docs)]
-    pub fn parse_time(context: &ParserContext, input: &mut Parser) -> Result<Time, ()> {
-        let ast = try!(CalcLengthOrPercentage::parse_sum(context, input, CalcUnit::Time));
-
-        let mut simplified = Vec::new();
-        for ref node in ast.products {
-            match try!(CalcLengthOrPercentage::simplify_product(node)) {
-                SimplifiedValueNode::Sum(sum) => simplified.extend_from_slice(&sum.values),
-                value => simplified.push(value),
-            }
-        }
-
-        let mut time = None;
-
-        for value in simplified {
-            match value {
-                SimplifiedValueNode::Time(val) =>
-                    time = Some(time.unwrap_or(0.) + val),
-                _ => return Err(()),
-            }
-        }
-
-        match time {
-            Some(time) => Ok(Time::from_calc(time)),
-            _ => Err(())
-        }
-    }
-
-    #[allow(missing_docs)]
-    pub fn parse_angle(context: &ParserContext, input: &mut Parser) -> Result<Angle, ()> {
-        let ast = try!(CalcLengthOrPercentage::parse_sum(context, input, CalcUnit::Angle));
-
-        let mut simplified = Vec::new();
-        for ref node in ast.products {
-            match try!(CalcLengthOrPercentage::simplify_product(node)) {
-                SimplifiedValueNode::Sum(sum) => simplified.extend_from_slice(&sum.values),
-                value => simplified.push(value),
-            }
-        }
-
-        let mut angle = None;
-        let mut number = None;
-
-        for value in simplified {
-            match value {
-                SimplifiedValueNode::Angle(val) => {
-                    angle = Some(angle.unwrap_or(0.) + val)
-                }
-                // TODO(emilio): This `Number` logic looks fishy.
-                //
-                // In particular, this allows calc(2 - 2) to parse as an
-                // `Angle`, which doesn't seem desired to me.
-                SimplifiedValueNode::Number(val) => {
-                    number = Some(number.unwrap_or(0.) + val)
-                }
-                _ => unreachable!()
-            }
-        }
-
-        match (angle, number) {
-            (Some(angle), None) => Ok(Angle::from_calc(angle)),
-            (None, Some(value)) if value == 0. => Ok(Angle::from_calc(0.)),
-            _ => Err(())
-        }
-    }
-}
-
-impl HasViewportPercentage for CalcLengthOrPercentage {
-    fn has_viewport_percentage(&self) -> bool {
-        self.vw.is_some() || self.vh.is_some() ||
-            self.vmin.is_some() || self.vmax.is_some()
-    }
-}
-
-impl ToCss for CalcLengthOrPercentage {
-    #[allow(unused_assignments)]
-    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-        let mut first_value = true;
-        macro_rules! first_value_check {
-            () => {
-                if !first_value {
-                    try!(dest.write_str(" + "));
-                } else {
-                    first_value = false;
-                }
-            };
-        }
-
-        macro_rules! serialize {
-            ( $( $val:ident ),* ) => {
-                $(
-                    if let Some(val) = self.$val {
-                        first_value_check!();
-                        try!(val.to_css(dest));
-                        try!(dest.write_str(stringify!($val)));
-                    }
-                )*
-            };
-        }
-
-        try!(write!(dest, "calc("));
-
-        serialize!(ch, em, ex, rem, vh, vmax, vmin, vw);
-        if let Some(val) = self.absolute {
-            first_value_check!();
-            try!(val.to_css(dest));
-        }
-
-        if let Some(val) = self.percentage {
-            first_value_check!();
-            try!(write!(dest, "{}%", val * 100.));
-        }
-
-        write!(dest, ")")
-    }
-}
-
 /// A percentage value.
 ///
 /// [0 .. 100%] maps to [0.0 .. 1.0]
 ///
 /// FIXME(emilio): There's no standard property that requires a `<percentage>`
 /// without requiring also a `<length>`. If such a property existed, we'd need
 /// to add special handling for `calc()` and percentages in here in the same way
 /// as for `Angle` and `Time`, but the lack of this this is otherwise
@@ -1172,18 +751,16 @@ impl Parse for Percentage {
     fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         Self::parse_internal(input, AllowedLengthType::All)
     }
 }
 
 impl ComputedValueAsSpecified for Percentage {}
 
 /// A length or a percentage value.
-///
-/// TODO(emilio): Does this make any sense vs. CalcLengthOrPercentage?
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 pub enum LengthOrPercentage {
     Length(NoCalcLength),
     Percentage(Percentage),
     Calc(Box<CalcLengthOrPercentage>),
 }
@@ -1249,17 +826,17 @@ impl LengthOrPercentage {
                 NoCalcLength::parse_dimension(context, value.value, unit).map(LengthOrPercentage::Length),
             Token::Percentage(ref value) if num_context.is_ok(value.unit_value) =>
                 Ok(LengthOrPercentage::Percentage(Percentage(value.unit_value))),
             Token::Number(value) if value.value == 0. ||
                                     (num_context.is_ok(value.value) && allow_quirks.allowed(context.quirks_mode)) =>
                 Ok(LengthOrPercentage::Length(NoCalcLength::from_px(value.value))),
             Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
                 let calc = try!(input.parse_nested_block(|i| {
-                    CalcLengthOrPercentage::parse_length_or_percentage(context, i)
+                    CalcNode::parse_length_or_percentage(context, i)
                 }));
                 Ok(LengthOrPercentage::Calc(Box::new(calc)))
             },
             _ => Err(())
         }
     }
 
     /// Parse a non-negative length.
@@ -1333,29 +910,27 @@ impl LengthOrPercentage {
     #[inline]
     pub fn parse_quirky(context: &ParserContext,
                         input: &mut Parser,
                         allow_quirks: AllowQuirks) -> Result<Self, ()> {
         Self::parse_internal(context, input, AllowedLengthType::All, allow_quirks)
     }
 }
 
-/// TODO(emilio): Do the Length and Percentage variants make any sense with
-/// CalcLengthOrPercentage?
+/// Either a `<length>`, a `<percentage>`, or the `auto` keyword.
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 pub enum LengthOrPercentageOrAuto {
     Length(NoCalcLength),
     Percentage(Percentage),
     Auto,
     Calc(Box<CalcLengthOrPercentage>),
 }
 
-
 impl From<NoCalcLength> for LengthOrPercentageOrAuto {
     #[inline]
     fn from(len: NoCalcLength) -> Self {
         LengthOrPercentageOrAuto::Length(len)
     }
 }
 
 impl From<Percentage> for LengthOrPercentageOrAuto {
@@ -1405,17 +980,17 @@ impl LengthOrPercentageOrAuto {
                 Ok(LengthOrPercentageOrAuto::Length(
                     NoCalcLength::Absolute(AbsoluteLength::Px(value.value))
                 ))
             }
             Token::Ident(ref value) if value.eq_ignore_ascii_case("auto") =>
                 Ok(LengthOrPercentageOrAuto::Auto),
             Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
                 let calc = try!(input.parse_nested_block(|i| {
-                    CalcLengthOrPercentage::parse_length_or_percentage(context, i)
+                    CalcNode::parse_length_or_percentage(context, i)
                 }));
                 Ok(LengthOrPercentageOrAuto::Calc(Box::new(calc)))
             },
             _ => Err(())
         }
     }
 
     /// Parse a non-negative length, percentage, or auto.
@@ -1457,18 +1032,17 @@ impl LengthOrPercentageOrAuto {
     pub fn parse_quirky(context: &ParserContext,
                         input: &mut Parser,
                         allow_quirks: AllowQuirks)
                         -> Result<Self, ()> {
         Self::parse_internal(context, input, AllowedLengthType::All, allow_quirks)
     }
 }
 
-/// TODO(emilio): Do the Length and Percentage variants make any sense with
-/// CalcLengthOrPercentage?
+/// Either a `<length>`, a `<percentage>`, or the `none` keyword.
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[allow(missing_docs)]
 pub enum LengthOrPercentageOrNone {
     Length(NoCalcLength),
     Percentage(Percentage),
     Calc(Box<CalcLengthOrPercentage>),
     None,
@@ -1512,17 +1086,17 @@ impl LengthOrPercentageOrNone {
                     return Err(())
                 }
                 Ok(LengthOrPercentageOrNone::Length(
                     NoCalcLength::Absolute(AbsoluteLength::Px(value.value))
                 ))
             }
             Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
                 let calc = try!(input.parse_nested_block(|i| {
-                    CalcLengthOrPercentage::parse_length_or_percentage(context, i)
+                    CalcNode::parse_length_or_percentage(context, i)
                 }));
                 Ok(LengthOrPercentageOrNone::Calc(Box::new(calc)))
             },
             Token::Ident(ref value) if value.eq_ignore_ascii_case("none") =>
                 Ok(LengthOrPercentageOrNone::None),
             _ => Err(())
         }
     }
@@ -1556,18 +1130,16 @@ pub type LengthOrNone = Either<Length, N
 /// Either a `<length>` or the `normal` keyword.
 pub type LengthOrNormal = Either<Length, Normal>;
 
 /// Either a `<length>` or the `auto` keyword.
 pub type LengthOrAuto = Either<Length, Auto>;
 
 /// Either a `<length>` or a `<percentage>` or the `auto` keyword or the
 /// `content` keyword.
-///
-/// TODO(emilio): Do the Length and Percentage variants make any sense with
 #[derive(Clone, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum LengthOrPercentageOrAutoOrContent {
     /// A `<length>`.
     Length(NoCalcLength),
     /// A percentage.
     Percentage(Percentage),
     /// A `calc` node.
@@ -1591,17 +1163,17 @@ impl LengthOrPercentageOrAutoOrContent {
             Token::Number(ref value) if value.value == 0. =>
                 Ok(Self::zero()),
             Token::Ident(ref value) if value.eq_ignore_ascii_case("auto") =>
                 Ok(LengthOrPercentageOrAutoOrContent::Auto),
             Token::Ident(ref value) if value.eq_ignore_ascii_case("content") =>
                 Ok(LengthOrPercentageOrAutoOrContent::Content),
             Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
                 let calc = try!(input.parse_nested_block(|i| {
-                    CalcLengthOrPercentage::parse_length_or_percentage(context, i)
+                    CalcNode::parse_length_or_percentage(context, i)
                 }));
                 Ok(LengthOrPercentageOrAutoOrContent::Calc(Box::new(calc)))
             },
             _ => Err(())
         }
     }
 
     /// Returns the `auto` value.
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -11,41 +11,42 @@ use context::QuirksMode;
 use cssparser::{self, Parser, Token};
 use euclid::size::Size2D;
 use parser::{ParserContext, Parse};
 use self::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as GenericTrackSize};
 use self::url::SpecifiedUrl;
 use std::ascii::AsciiExt;
 use std::f32;
 use std::fmt;
-use std::ops::Mul;
 use style_traits::ToCss;
 use style_traits::values::specified::AllowedNumericType;
 use super::{Auto, CSSFloat, CSSInteger, HasViewportPercentage, Either, None_};
 use super::computed::{self, Context};
 use super::computed::{Shadow as ComputedShadow, ToComputedValue};
 use super::generics::BorderRadiusSize as GenericBorderRadiusSize;
+use values::specified::calc::CalcNode;
 
 #[cfg(feature = "gecko")]
 pub use self::align::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
 pub use self::color::Color;
 pub use self::grid::{GridLine, TrackKeyword};
 pub use self::image::{AngleOrCorner, ColorStop, EndingShape as GradientEndingShape, Gradient};
 pub use self::image::{GradientItem, GradientKind, HorizontalDirection, Image, ImageRect, LayerImage};
 pub use self::image::{LengthOrKeyword, LengthOrPercentageOrKeyword, SizeKeyword, VerticalDirection};
 pub use self::length::AbsoluteLength;
 pub use self::length::{FontRelativeLength, ViewportPercentageLength, CharacterWidth, Length, CalcLengthOrPercentage};
 pub use self::length::{Percentage, LengthOrNone, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto};
-pub use self::length::{LengthOrPercentageOrNone, LengthOrPercentageOrAutoOrContent, NoCalcLength, CalcUnit};
+pub use self::length::{LengthOrPercentageOrNone, LengthOrPercentageOrAutoOrContent, NoCalcLength};
 pub use self::length::{MaxLength, MinLength};
 pub use self::position::{HorizontalPosition, Position, VerticalPosition};
 
 #[cfg(feature = "gecko")]
 pub mod align;
 pub mod basic_shape;
+pub mod calc;
 pub mod color;
 pub mod grid;
 pub mod image;
 pub mod length;
 pub mod position;
 
 /// Common handling for the specified value CSS url() values.
 pub mod url {
@@ -148,141 +149,58 @@ impl ToCss for CSSRGBA {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match self.authored {
             Some(ref s) => dest.write_str(s),
             None => self.parsed.to_css(dest),
         }
     }
 }
 
-#[derive(Clone, Debug)]
-#[allow(missing_docs)]
-pub struct SimplifiedSumNode {
-    values: Vec<SimplifiedValueNode>,
-}
-impl<'a> Mul<CSSFloat> for &'a SimplifiedSumNode {
-    type Output = SimplifiedSumNode;
-
-    #[inline]
-    fn mul(self, scalar: CSSFloat) -> SimplifiedSumNode {
-        SimplifiedSumNode {
-            values: self.values.iter().map(|p| p * scalar).collect()
-        }
-    }
-}
-
-#[derive(Clone, Debug)]
-#[allow(missing_docs)]
-pub enum SimplifiedValueNode {
-    Length(NoCalcLength),
-    Angle(CSSFloat),
-    Time(CSSFloat),
-    Percentage(CSSFloat),
-    Number(CSSFloat),
-    Sum(Box<SimplifiedSumNode>),
-}
-
-impl<'a> Mul<CSSFloat> for &'a SimplifiedValueNode {
-    type Output = SimplifiedValueNode;
-
-    #[inline]
-    fn mul(self, scalar: CSSFloat) -> SimplifiedValueNode {
-        match *self {
-            SimplifiedValueNode::Length(ref l) => {
-                SimplifiedValueNode::Length(l.clone() * scalar)
-            },
-            SimplifiedValueNode::Percentage(p) => {
-                SimplifiedValueNode::Percentage(p * scalar)
-            },
-            SimplifiedValueNode::Angle(a) => {
-                SimplifiedValueNode::Angle(a * scalar)
-            },
-            SimplifiedValueNode::Time(t) => {
-                SimplifiedValueNode::Time(t * scalar)
-            },
-            SimplifiedValueNode::Number(n) => {
-                SimplifiedValueNode::Number(n * scalar)
-            },
-            SimplifiedValueNode::Sum(ref s) => {
-                let sum = &**s * scalar;
-                SimplifiedValueNode::Sum(Box::new(sum))
-            },
-        }
-    }
-}
-
-#[allow(missing_docs)]
+/// Parse an `<integer>` value, handling `calc()` correctly.
 pub fn parse_integer(context: &ParserContext, input: &mut Parser) -> Result<Integer, ()> {
     match try!(input.next()) {
         Token::Number(ref value) => value.int_value.ok_or(()).map(Integer::new),
         Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
-            let ast = try!(input.parse_nested_block(|i| {
-                CalcLengthOrPercentage::parse_sum(context, i, CalcUnit::Integer)
+            let result = try!(input.parse_nested_block(|i| {
+                CalcNode::parse_integer(context, i)
             }));
 
-            let mut result = None;
-
-            for ref node in ast.products {
-                match try!(CalcLengthOrPercentage::simplify_product(node)) {
-                    SimplifiedValueNode::Number(val) =>
-                        result = Some(result.unwrap_or(0) + val as CSSInteger),
-                    _ => unreachable!()
-                }
-            }
-
-            match result {
-                Some(result) => Ok(Integer::from_calc(result)),
-                _ => Err(())
-            }
+            Ok(Integer::from_calc(result))
         }
         _ => Err(())
     }
 }
 
-#[allow(missing_docs)]
+/// Parse a `<number>` value, handling `calc()` correctly, and without length
+/// limitations.
 pub fn parse_number(context: &ParserContext, input: &mut Parser) -> Result<Number, ()> {
     parse_number_with_clamping_mode(context, input, AllowedNumericType::All)
 }
 
-#[allow(missing_docs)]
+/// Parse a `<number>` value, with a given clamping mode.
 pub fn parse_number_with_clamping_mode(context: &ParserContext,
                                        input: &mut Parser,
                                        clamping_mode: AllowedNumericType)
                                        -> Result<Number, ()> {
     match try!(input.next()) {
         Token::Number(ref value) if clamping_mode.is_ok(value.value) => {
             Ok(Number {
                 value: value.value.min(f32::MAX).max(f32::MIN),
                 calc_clamping_mode: None,
             })
         },
         Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
-            let ast = try!(input.parse_nested_block(|i| {
-                CalcLengthOrPercentage::parse_sum(context, i, CalcUnit::Number)
+            let result = try!(input.parse_nested_block(|i| {
+                CalcNode::parse_number(context, i)
             }));
 
-            let mut result = None;
-
-            for ref node in ast.products {
-                match try!(CalcLengthOrPercentage::simplify_product(node)) {
-                    SimplifiedValueNode::Number(val) =>
-                        result = Some(result.unwrap_or(0.) + val),
-                    _ => unreachable!()
-                }
-            }
-
-            match result {
-                Some(result) => {
-                    Ok(Number {
-                        value: result.min(f32::MAX).max(f32::MIN),
-                        calc_clamping_mode: Some(clamping_mode),
-                    })
-                },
-                _ => Err(())
-            }
+            Ok(Number {
+                value: result.min(f32::MAX).max(f32::MIN),
+                calc_clamping_mode: Some(clamping_mode),
+            })
         }
         _ => Err(())
     }
 }
 
 /// The specified value of `BorderRadiusSize`
 pub type BorderRadiusSize = GenericBorderRadiusSize<LengthOrPercentage>;
 
@@ -333,90 +251,108 @@ impl ToComputedValue for Angle {
             value: *computed,
             was_calc: false,
         }
     }
 }
 
 impl Angle {
     /// Returns an angle with the given value in degrees.
-    pub fn from_degrees(value: CSSFloat) -> Self {
-        Angle { value: computed::Angle::Degree(value), was_calc: false }
+    pub fn from_degrees(value: CSSFloat, was_calc: bool) -> Self {
+        Angle { value: computed::Angle::Degree(value), was_calc: was_calc }
     }
+
     /// Returns an angle with the given value in gradians.
-    pub fn from_gradians(value: CSSFloat) -> Self {
-        Angle { value: computed::Angle::Gradian(value), was_calc: false }
+    pub fn from_gradians(value: CSSFloat, was_calc: bool) -> Self {
+        Angle { value: computed::Angle::Gradian(value), was_calc: was_calc }
     }
+
     /// Returns an angle with the given value in turns.
-    pub fn from_turns(value: CSSFloat) -> Self {
-        Angle { value: computed::Angle::Turn(value), was_calc: false }
+    pub fn from_turns(value: CSSFloat, was_calc: bool) -> Self {
+        Angle { value: computed::Angle::Turn(value), was_calc: was_calc }
     }
+
     /// Returns an angle with the given value in radians.
-    pub fn from_radians(value: CSSFloat) -> Self {
-        Angle { value: computed::Angle::Radian(value), was_calc: false }
+    pub fn from_radians(value: CSSFloat, was_calc: bool) -> Self {
+        Angle { value: computed::Angle::Radian(value), was_calc: was_calc }
     }
 
     #[inline]
     #[allow(missing_docs)]
     pub fn radians(self) -> f32 {
         self.value.radians()
     }
 
     /// Returns an angle value that represents zero.
     pub fn zero() -> Self {
-        Self::from_degrees(0.0)
+        Self::from_degrees(0.0, false)
     }
 
     /// Returns an `Angle` parsed from a `calc()` expression.
     pub fn from_calc(radians: CSSFloat) -> Self {
         Angle {
             value: computed::Angle::Radian(radians),
             was_calc: true,
         }
     }
 }
 
 impl Parse for Angle {
     /// Parses an angle according to CSS-VALUES § 6.1.
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         match try!(input.next()) {
-            Token::Dimension(ref value, ref unit) => Angle::parse_dimension(value.value, unit),
+            Token::Dimension(ref value, ref unit) => {
+                Angle::parse_dimension(value.value,
+                                       unit,
+                                       /* from_calc = */ false)
+            }
             Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
-                input.parse_nested_block(|i| CalcLengthOrPercentage::parse_angle(context, i))
-            },
+                input.parse_nested_block(|i| CalcNode::parse_angle(context, i))
+            }
             _ => Err(())
         }
     }
 }
 
 impl Angle {
-    #[allow(missing_docs)]
-    pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Angle, ()> {
+    /// Parse an `<angle>` value given a value and an unit.
+    pub fn parse_dimension(
+        value: CSSFloat,
+        unit: &str,
+        from_calc: bool)
+        -> Result<Angle, ()>
+    {
         let angle = match_ignore_ascii_case! { unit,
-            "deg" => Angle::from_degrees(value),
-            "grad" => Angle::from_gradians(value),
-            "turn" => Angle::from_turns(value),
-            "rad" => Angle::from_radians(value),
+            "deg" => Angle::from_degrees(value, from_calc),
+            "grad" => Angle::from_gradians(value, from_calc),
+            "turn" => Angle::from_turns(value, from_calc),
+            "rad" => Angle::from_radians(value, from_calc),
              _ => return Err(())
         };
         Ok(angle)
     }
     /// Parse an angle, including unitless 0 degree.
-    /// Note that numbers without any AngleUnit, including unitless 0
-    /// angle, should be invalid. However, some properties still accept
-    /// unitless 0 angle and stores it as '0deg'. We can remove this and
-    /// get back to the unified version Angle::parse once
+    ///
+    /// Note that numbers without any AngleUnit, including unitless 0 angle,
+    /// should be invalid. However, some properties still accept unitless 0
+    /// angle and stores it as '0deg'.
+    ///
+    /// We can remove this and get back to the unified version Angle::parse once
     /// https://github.com/w3c/csswg-drafts/issues/1162 is resolved.
     pub fn parse_with_unitless(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         match try!(input.next()) {
-            Token::Dimension(ref value, ref unit) => Angle::parse_dimension(value.value, unit),
+            Token::Dimension(ref value, ref unit) => {
+                Angle::parse_dimension(value.value,
+                                       unit,
+                                       /* from_calc = */ false)
+            }
             Token::Number(ref value) if value.value == 0. => Ok(Angle::zero()),
             Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
-                input.parse_nested_block(|i| CalcLengthOrPercentage::parse_angle(context, i))
-            },
+                input.parse_nested_block(|i| CalcNode::parse_angle(context, i))
+            }
             _ => Err(())
         }
     }
 }
 
 #[allow(missing_docs)]
 pub fn parse_border_radius(context: &ParserContext, input: &mut Parser) -> Result<BorderRadiusSize, ()> {
     input.try(|i| BorderRadiusSize::parse(context, i)).or_else(|_| {
@@ -574,26 +510,31 @@ impl Time {
     }
 
     /// Returns the time in fractional seconds.
     pub fn seconds(self) -> CSSFloat {
         self.seconds
     }
 
     /// Parses a time according to CSS-VALUES § 6.2.
-    fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Time, ()> {
+    pub fn parse_dimension(
+        value: CSSFloat,
+        unit: &str,
+        from_calc: bool)
+        -> Result<Time, ()>
+    {
         let seconds = match_ignore_ascii_case! { unit,
             "s" => value,
             "ms" => value / 1000.0,
             _ => return Err(()),
         };
 
         Ok(Time {
             seconds: seconds,
-            was_calc: false,
+            was_calc: from_calc,
         })
     }
 
     /// Returns a `Time` value from a CSS `calc()` expression.
     pub fn from_calc(seconds: CSSFloat) -> Self {
         Time {
             seconds: seconds,
             was_calc: true,
@@ -615,20 +556,20 @@ impl ToComputedValue for Time {
         }
     }
 }
 
 impl Parse for Time {
     fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ()> {
         match input.next() {
             Ok(Token::Dimension(ref value, ref unit)) => {
-                Time::parse_dimension(value.value, &unit)
+                Time::parse_dimension(value.value, &unit, /* from_calc = */ false)
             }
             Ok(Token::Function(ref name)) if name.eq_ignore_ascii_case("calc") => {
-                input.parse_nested_block(|i| CalcLengthOrPercentage::parse_time(context, i))
+                input.parse_nested_block(|i| CalcNode::parse_time(context, i))
             }
             _ => Err(())
         }
     }
 }
 
 impl ToCss for Time {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
@@ -799,17 +740,17 @@ impl Integer {
     }
 
     /// Returns the integer value associated with this value.
     pub fn value(&self) -> CSSInteger {
         self.value
     }
 
     /// Trivially constructs a new integer value from a `calc()` expression.
-    pub fn from_calc(val: CSSInteger) -> Self {
+    fn from_calc(val: CSSInteger) -> Self {
         Integer {
             value: val,
             was_calc: true,
         }
     }
 }
 
 no_viewport_percentage!(Integer);
--- a/servo/tests/unit/style/parsing/inherited_box.rs
+++ b/servo/tests/unit/style/parsing/inherited_box.rs
@@ -9,19 +9,19 @@ fn image_orientation_longhand_should_par
     use style::properties::longhands::image_orientation;
     use style::properties::longhands::image_orientation::SpecifiedValue;
     use style::values::specified::Angle;
 
     let from_image = parse_longhand!(image_orientation, "from-image");
     assert_eq!(from_image, SpecifiedValue { angle: None, flipped: false });
 
     let flip = parse_longhand!(image_orientation, "flip");
-    assert_eq!(flip, SpecifiedValue { angle: Some(Angle::from_degrees(0.0)), flipped: true });
+    assert_eq!(flip, SpecifiedValue { angle: Some(Angle::zero()), flipped: true });
 
     let zero = parse_longhand!(image_orientation, "0deg");
-    assert_eq!(zero, SpecifiedValue { angle: Some(Angle::from_degrees(0.0)), flipped: false });
+    assert_eq!(zero, SpecifiedValue { angle: Some(Angle::zero()), flipped: false });
 
     let negative_rad = parse_longhand!(image_orientation, "-1rad");
-    assert_eq!(negative_rad, SpecifiedValue { angle: Some(Angle::from_radians(-1.0)), flipped: false });
+    assert_eq!(negative_rad, SpecifiedValue { angle: Some(Angle::from_radians(-1.0, false)), flipped: false });
 
     let flip_with_180 = parse_longhand!(image_orientation, "180deg flip");
-    assert_eq!(flip_with_180, SpecifiedValue { angle: Some(Angle::from_degrees(180.0)), flipped: true });
+    assert_eq!(flip_with_180, SpecifiedValue { angle: Some(Angle::from_degrees(180.0, false)), flipped: true });
 }
--- a/servo/tests/unit/style/parsing/length.rs
+++ b/servo/tests/unit/style/parsing/length.rs
@@ -15,16 +15,17 @@ use style_traits::ToCss;
 
 #[test]
 fn test_calc() {
     assert!(parse(Length::parse, "calc(1px+ 2px)").is_err());
     assert!(parse(Length::parse, "calc(calc(1px) + calc(1px + 4px))").is_ok());
     assert!(parse(Length::parse, "calc( 1px + 2px )").is_ok());
     assert!(parse(Length::parse, "calc(1px + 2px )").is_ok());
     assert!(parse(Length::parse, "calc( 1px + 2px)").is_ok());
+    assert!(parse(Length::parse, "calc( 1px + 2px / ( 1 + 2 - 1))").is_ok());
 }
 
 #[test]
 fn test_length_literals() {
     assert_roundtrip_with_context!(Length::parse, "0.33px", "0.33px");
     assert_roundtrip_with_context!(Length::parse, "0.33in", "0.33in");
     assert_roundtrip_with_context!(Length::parse, "0.33cm", "0.33cm");
     assert_roundtrip_with_context!(Length::parse, "0.33mm", "0.33mm");
--- a/servo/tests/unit/style/parsing/value.rs
+++ b/servo/tests/unit/style/parsing/value.rs
@@ -1,35 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
 use parsing::parse;
 use style::values::HasViewportPercentage;
 use style::values::specified::{AbsoluteLength, NoCalcLength, ViewportPercentageLength};
-use style::values::specified::length::{CalcLengthOrPercentage, CalcUnit};
 
 #[test]
 fn length_has_viewport_percentage() {
     let l = NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(100.));
     assert!(l.has_viewport_percentage());
     let l = NoCalcLength::Absolute(AbsoluteLength::Px(Au(100).to_f32_px()));
     assert!(!l.has_viewport_percentage());
 }
-
-#[test]
-fn calc_top_level_number_with_unit() {
-    fn parse_value(text: &str, unit: CalcUnit) -> Result<CalcLengthOrPercentage, ()> {
-        parse(|context, input| CalcLengthOrPercentage::parse(context, input, unit), text)
-    }
-    assert_eq!(parse_value("1", CalcUnit::Length), Err(()));
-    assert_eq!(parse_value("1", CalcUnit::LengthOrPercentage), Err(()));
-    assert_eq!(parse_value("1", CalcUnit::Angle), Err(()));
-    assert_eq!(parse_value("1", CalcUnit::Time), Err(()));
-    assert_eq!(parse_value("1px  + 1", CalcUnit::Length), Err(()));
-    assert_eq!(parse_value("1em  + 1", CalcUnit::Length), Err(()));
-    assert_eq!(parse_value("1px  + 1", CalcUnit::LengthOrPercentage), Err(()));
-    assert_eq!(parse_value("1%   + 1", CalcUnit::LengthOrPercentage), Err(()));
-    assert_eq!(parse_value("1rad + 1", CalcUnit::Angle), Err(()));
-    assert_eq!(parse_value("1deg + 1", CalcUnit::Angle), Err(()));
-    assert_eq!(parse_value("1s   + 1", CalcUnit::Time), Err(()));
-}
--- a/servo/tests/unit/style/properties/serialization.rs
+++ b/servo/tests/unit/style/properties/serialization.rs
@@ -1038,29 +1038,29 @@ mod shorthand_serialization {
             validate_serialization(
                 &SpecifiedOperation::Scale3D(Number::new(4.0), Number::new(5.0), Number::new(6.0)),
                 "scale3d(4, 5, 6)");
         }
 
         #[test]
         fn transform_skew() {
             validate_serialization(
-                &SpecifiedOperation::Skew(Angle::from_degrees(42.3), None),
+                &SpecifiedOperation::Skew(Angle::from_degrees(42.3, false), None),
                 "skew(42.3deg)");
             validate_serialization(
-                &SpecifiedOperation::Skew(Angle::from_gradians(-50.0), Some(Angle::from_turns(0.73))),
+                &SpecifiedOperation::Skew(Angle::from_gradians(-50.0, false), Some(Angle::from_turns(0.73, false))),
                 "skew(-50grad, 0.73turn)");
             validate_serialization(
-                &SpecifiedOperation::SkewX(Angle::from_radians(0.31)), "skewX(0.31rad)");
+                &SpecifiedOperation::SkewX(Angle::from_radians(0.31, false)), "skewX(0.31rad)");
         }
 
         #[test]
         fn transform_rotate() {
             validate_serialization(
-                &SpecifiedOperation::Rotate(Angle::from_turns(35.0)),
+                &SpecifiedOperation::Rotate(Angle::from_turns(35.0, false)),
                 "rotate(35turn)"
             )
         }
     }
 
     mod quotes {
         pub use super::*;