diff options
Diffstat (limited to 'components/style/values/specified')
-rw-r--r-- | components/style/values/specified/align.rs | 94 | ||||
-rw-r--r-- | components/style/values/specified/angle.rs | 20 | ||||
-rw-r--r-- | components/style/values/specified/box.rs | 73 | ||||
-rw-r--r-- | components/style/values/specified/calc.rs | 760 | ||||
-rw-r--r-- | components/style/values/specified/color.rs | 14 | ||||
-rw-r--r-- | components/style/values/specified/counters.rs | 35 | ||||
-rw-r--r-- | components/style/values/specified/font.rs | 48 | ||||
-rw-r--r-- | components/style/values/specified/gecko.rs | 2 | ||||
-rw-r--r-- | components/style/values/specified/length.rs | 199 | ||||
-rw-r--r-- | components/style/values/specified/mod.rs | 96 | ||||
-rw-r--r-- | components/style/values/specified/percentage.rs | 8 | ||||
-rw-r--r-- | components/style/values/specified/position.rs | 3 | ||||
-rw-r--r-- | components/style/values/specified/svg_path.rs | 17 | ||||
-rw-r--r-- | components/style/values/specified/text.rs | 32 | ||||
-rw-r--r-- | components/style/values/specified/time.rs | 19 |
15 files changed, 962 insertions, 458 deletions
diff --git a/components/style/values/specified/align.rs b/components/style/values/specified/align.rs index 0dc1e422515..d0160a32ae6 100644 --- a/components/style/values/specified/align.rs +++ b/components/style/values/specified/align.rs @@ -6,7 +6,6 @@ //! //! https://drafts.csswg.org/css-align/ -use crate::gecko_bindings::structs; use crate::parser::{Parse, ParserContext}; use cssparser::Parser; use std::fmt::{self, Write}; @@ -14,56 +13,55 @@ use style_traits::{CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, bitflags! { /// Constants shared by multiple CSS Box Alignment properties - /// - /// These constants match Gecko's `NS_STYLE_ALIGN_*` constants. #[derive(MallocSizeOf, ToComputedValue, ToResolvedValue, ToShmem)] + #[repr(C)] pub struct AlignFlags: u8 { // Enumeration stored in the lower 5 bits: - /// 'auto' - const AUTO = structs::NS_STYLE_ALIGN_AUTO as u8; + /// {align,justify}-{content,items,self}: 'auto' + const AUTO = 0; /// 'normal' - const NORMAL = structs::NS_STYLE_ALIGN_NORMAL as u8; + const NORMAL = 1; /// 'start' - const START = structs::NS_STYLE_ALIGN_START as u8; + const START = 2; /// 'end' - const END = structs::NS_STYLE_ALIGN_END as u8; + const END = 3; /// 'flex-start' - const FLEX_START = structs::NS_STYLE_ALIGN_FLEX_START as u8; + const FLEX_START = 4; /// 'flex-end' - const FLEX_END = structs::NS_STYLE_ALIGN_FLEX_END as u8; + const FLEX_END = 5; /// 'center' - const CENTER = structs::NS_STYLE_ALIGN_CENTER as u8; + const CENTER = 6; /// 'left' - const LEFT = structs::NS_STYLE_ALIGN_LEFT as u8; + const LEFT = 7; /// 'right' - const RIGHT = structs::NS_STYLE_ALIGN_RIGHT as u8; + const RIGHT = 8; /// 'baseline' - const BASELINE = structs::NS_STYLE_ALIGN_BASELINE as u8; + const BASELINE = 9; /// 'last-baseline' - const LAST_BASELINE = structs::NS_STYLE_ALIGN_LAST_BASELINE as u8; + const LAST_BASELINE = 10; /// 'stretch' - const STRETCH = structs::NS_STYLE_ALIGN_STRETCH as u8; + const STRETCH = 11; /// 'self-start' - const SELF_START = structs::NS_STYLE_ALIGN_SELF_START as u8; + const SELF_START = 12; /// 'self-end' - const SELF_END = structs::NS_STYLE_ALIGN_SELF_END as u8; + const SELF_END = 13; /// 'space-between' - const SPACE_BETWEEN = structs::NS_STYLE_ALIGN_SPACE_BETWEEN as u8; + const SPACE_BETWEEN = 14; /// 'space-around' - const SPACE_AROUND = structs::NS_STYLE_ALIGN_SPACE_AROUND as u8; + const SPACE_AROUND = 15; /// 'space-evenly' - const SPACE_EVENLY = structs::NS_STYLE_ALIGN_SPACE_EVENLY as u8; + const SPACE_EVENLY = 16; // Additional flags stored in the upper bits: /// 'legacy' (mutually exclusive w. SAFE & UNSAFE) - const LEGACY = structs::NS_STYLE_ALIGN_LEGACY as u8; + const LEGACY = 1 << 5; /// 'safe' - const SAFE = structs::NS_STYLE_ALIGN_SAFE as u8; + const SAFE = 1 << 6; /// 'unsafe' (mutually exclusive w. SAFE) - const UNSAFE = structs::NS_STYLE_ALIGN_UNSAFE as u8; + const UNSAFE = 1 << 7; /// Mask for the additional flags above. - const FLAG_BITS = structs::NS_STYLE_ALIGN_FLAG_BITS as u8; + const FLAG_BITS = 0b11100000; } } @@ -146,6 +144,7 @@ pub enum AxisDirection { ToResolvedValue, ToShmem, )] +#[repr(C)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] pub struct ContentDistribution { primary: AlignFlags, @@ -270,6 +269,7 @@ impl ContentDistribution { ToResolvedValue, ToShmem, )] +#[repr(transparent)] pub struct AlignContent(pub ContentDistribution); impl Parse for AlignContent { @@ -292,20 +292,6 @@ impl SpecifiedValueInfo for AlignContent { } } -#[cfg(feature = "gecko")] -impl From<u16> for AlignContent { - fn from(bits: u16) -> Self { - AlignContent(ContentDistribution::from_bits(bits)) - } -} - -#[cfg(feature = "gecko")] -impl From<AlignContent> for u16 { - fn from(v: AlignContent) -> u16 { - v.0.as_bits() - } -} - /// Value for the `justify-content` property. /// /// <https://drafts.csswg.org/css-align/#propdef-justify-content> @@ -321,6 +307,7 @@ impl From<AlignContent> for u16 { ToResolvedValue, ToShmem, )] +#[repr(transparent)] pub struct JustifyContent(pub ContentDistribution); impl Parse for JustifyContent { @@ -370,6 +357,7 @@ impl From<JustifyContent> for u16 { ToResolvedValue, ToShmem, )] +#[repr(transparent)] pub struct SelfAlignment(pub AlignFlags); impl SelfAlignment { @@ -441,6 +429,7 @@ impl SelfAlignment { ToResolvedValue, ToShmem, )] +#[repr(C)] pub struct AlignSelf(pub SelfAlignment); impl Parse for AlignSelf { @@ -463,18 +452,6 @@ impl SpecifiedValueInfo for AlignSelf { } } -impl From<u8> for AlignSelf { - fn from(bits: u8) -> Self { - AlignSelf(SelfAlignment(AlignFlags::from_bits_truncate(bits))) - } -} - -impl From<AlignSelf> for u8 { - fn from(align: AlignSelf) -> u8 { - (align.0).0.bits() - } -} - /// The specified value of the justify-self property. /// /// <https://drafts.csswg.org/css-align/#propdef-justify-self> @@ -490,6 +467,7 @@ impl From<AlignSelf> for u8 { ToResolvedValue, ToShmem, )] +#[repr(C)] pub struct JustifySelf(pub SelfAlignment); impl Parse for JustifySelf { @@ -512,18 +490,6 @@ impl SpecifiedValueInfo for JustifySelf { } } -impl From<u8> for JustifySelf { - fn from(bits: u8) -> Self { - JustifySelf(SelfAlignment(AlignFlags::from_bits_truncate(bits))) - } -} - -impl From<JustifySelf> for u8 { - fn from(justify: JustifySelf) -> u8 { - (justify.0).0.bits() - } -} - /// Value of the `align-items` property /// /// <https://drafts.csswg.org/css-align/#propdef-align-items> @@ -539,6 +505,7 @@ impl From<JustifySelf> for u8 { ToResolvedValue, ToShmem, )] +#[repr(C)] pub struct AlignItems(pub AlignFlags); impl AlignItems { @@ -590,6 +557,7 @@ impl SpecifiedValueInfo for AlignItems { /// /// <https://drafts.csswg.org/css-align/#justify-items-property> #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)] +#[repr(C)] pub struct JustifyItems(pub AlignFlags); impl JustifyItems { diff --git a/components/style/values/specified/angle.rs b/components/style/values/specified/angle.rs index 458f4178e59..fa60f66507d 100644 --- a/components/style/values/specified/angle.rs +++ b/components/style/values/specified/angle.rs @@ -163,7 +163,8 @@ impl Angle { /// https://github.com/w3c/fxtf-drafts/issues/228 /// /// See also: https://github.com/w3c/csswg-drafts/issues/1162. -enum AllowUnitlessZeroAngle { +#[allow(missing_docs)] +pub enum AllowUnitlessZeroAngle { Yes, No, } @@ -203,12 +204,14 @@ impl Angle { Self::parse_internal(context, input, AllowUnitlessZeroAngle::Yes) } - fn parse_internal<'i, 't>( + pub(super) fn parse_internal<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, allow_unitless_zero: AllowUnitlessZeroAngle, ) -> Result<Self, ParseError<'i>> { + let location = input.current_source_location(); let t = input.next()?; + let allow_unitless_zero = matches!(allow_unitless_zero, AllowUnitlessZeroAngle::Yes); match *t { Token::Dimension { value, ref unit, .. @@ -221,16 +224,11 @@ impl Angle { }, } }, - Token::Number { value, .. } if value == 0. => match allow_unitless_zero { - AllowUnitlessZeroAngle::Yes => Ok(Angle::zero()), - AllowUnitlessZeroAngle::No => { - let t = t.clone(); - Err(input.new_unexpected_token_error(t)) - }, - }, - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { - return input.parse_nested_block(|i| CalcNode::parse_angle(context, i)); + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + CalcNode::parse_angle(context, input, function) }, + Token::Number { value, .. } if value == 0. && allow_unitless_zero => Ok(Angle::zero()), ref t => { let t = t.clone(); Err(input.new_unexpected_token_error(t)) diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index ad8602ac431..fa5d5a2d43a 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -61,12 +61,9 @@ pub enum DisplayInside { None = 0, #[cfg(any(feature = "servo-layout-2020", feature = "gecko"))] Contents, - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - Block, + Flow, FlowRoot, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - Inline, - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] Flex, #[cfg(feature = "gecko")] Grid, @@ -101,8 +98,6 @@ pub enum DisplayInside { #[cfg(feature = "gecko")] MozBox, #[cfg(feature = "gecko")] - MozInlineBox, - #[cfg(feature = "gecko")] MozGrid, #[cfg(feature = "gecko")] MozGridGroup, @@ -111,10 +106,7 @@ pub enum DisplayInside { #[cfg(feature = "gecko")] MozDeck, #[cfg(feature = "gecko")] - MozGroupbox, - #[cfg(feature = "gecko")] MozPopup, - Flow, // only used for parsing, not computed value } #[allow(missing_docs)] @@ -147,14 +139,8 @@ impl Display { pub const None: Self = Self::new(DisplayOutside::None, DisplayInside::None); #[cfg(any(feature = "servo-layout-2020", feature = "gecko"))] pub const Contents: Self = Self::new(DisplayOutside::None, DisplayInside::Contents); - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - pub const Inline: Self = Self::new(DisplayOutside::Inline, DisplayInside::Inline); - #[cfg(any(feature = "servo-layout-2020"))] pub const Inline: Self = Self::new(DisplayOutside::Inline, DisplayInside::Flow); pub const InlineBlock: Self = Self::new(DisplayOutside::Inline, DisplayInside::FlowRoot); - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - pub const Block: Self = Self::new(DisplayOutside::Block, DisplayInside::Block); - #[cfg(any(feature = "servo-layout-2020"))] pub const Block: Self = Self::new(DisplayOutside::Block, DisplayInside::Flow); #[cfg(feature = "gecko")] pub const FlowRoot: Self = Self::new(DisplayOutside::Block, DisplayInside::FlowRoot); @@ -171,7 +157,7 @@ impl Display { #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] pub const InlineTable: Self = Self::new(DisplayOutside::Inline, DisplayInside::Table); #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - pub const TableCaption: Self = Self::new(DisplayOutside::TableCaption, DisplayInside::Block); + pub const TableCaption: Self = Self::new(DisplayOutside::TableCaption, DisplayInside::Flow); #[cfg(feature = "gecko")] pub const Ruby: Self = Self::new(DisplayOutside::Inline, DisplayInside::Ruby); #[cfg(feature = "gecko")] @@ -231,9 +217,9 @@ impl Display { /// XUL boxes. #[cfg(feature = "gecko")] - pub const MozBox: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozBox); + pub const MozBox: Self = Self::new(DisplayOutside::Block, DisplayInside::MozBox); #[cfg(feature = "gecko")] - pub const MozInlineBox: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozInlineBox); + pub const MozInlineBox: Self = Self::new(DisplayOutside::Inline, DisplayInside::MozBox); #[cfg(feature = "gecko")] pub const MozGrid: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozGrid); #[cfg(feature = "gecko")] @@ -243,8 +229,6 @@ impl Display { #[cfg(feature = "gecko")] pub const MozDeck: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozDeck); #[cfg(feature = "gecko")] - pub const MozGroupbox: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozGroupbox); - #[cfg(feature = "gecko")] pub const MozPopup: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozPopup); /// Make a raw display value from <display-outside> and <display-inside> values. @@ -256,18 +240,8 @@ impl Display { } /// Make a display enum value from <display-outside> and <display-inside> values. - /// We store `flow` as a synthetic `block` or `inline` inside-value to simplify - /// our layout code. #[inline] fn from3(outside: DisplayOutside, inside: DisplayInside, list_item: bool) -> Self { - let inside = match inside { - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - DisplayInside::Flow => match outside { - DisplayOutside::Inline => DisplayInside::Inline, - _ => DisplayInside::Block, - }, - _ => inside, - }; let v = Self::new(outside, inside); if !list_item { return v; @@ -290,6 +264,12 @@ impl Display { .unwrap() } + /// Whether this is `display: inline` (or `inline list-item`). + #[inline] + pub fn is_inline_flow(&self) -> bool { + self.outside() == DisplayOutside::Inline && self.inside() == DisplayInside::Flow + } + /// Returns whether this `display` value is some kind of list-item. #[inline] pub const fn is_list_item(&self) -> bool { @@ -381,19 +361,13 @@ impl Display { match self.outside() { DisplayOutside::Inline => { let inside = match self.inside() { - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - DisplayInside::Inline | DisplayInside::FlowRoot => DisplayInside::Block, - #[cfg(feature = "servo-layout-2020")] + // `inline-block` blockifies to `block` rather than + // `flow-root`, for legacy reasons. DisplayInside::FlowRoot => DisplayInside::Flow, inside => inside, }; Display::from3(DisplayOutside::Block, inside, self.is_list_item()) }, - #[cfg(feature = "gecko")] - DisplayOutside::XUL => match self.inside() { - DisplayInside::MozInlineBox | DisplayInside::MozBox => Display::MozBox, - _ => Display::Block, - }, DisplayOutside::Block | DisplayOutside::None => *self, #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] _ => Display::Block, @@ -407,16 +381,13 @@ impl Display { match self.outside() { DisplayOutside::Block => { let inside = match self.inside() { - DisplayInside::Block => DisplayInside::FlowRoot, + // `display: block` inlinifies to `display: inline-block`, + // rather than `inline`, for legacy reasons. + DisplayInside::Flow => DisplayInside::FlowRoot, inside => inside, }; Display::from3(DisplayOutside::Inline, inside, self.is_list_item()) }, - #[cfg(feature = "gecko")] - DisplayOutside::XUL => match self.inside() { - DisplayInside::MozBox => Display::MozInlineBox, - _ => *self, - }, _ => *self, } } @@ -443,18 +414,8 @@ impl ToCss for Display { where W: fmt::Write, { - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - debug_assert_ne!( - self.inside(), - DisplayInside::Flow, - "`flow` fears in `display` computed value" - ); let outside = self.outside(); - let inside = match self.inside() { - #[cfg(any(feature = "servo-layout-2013", feature = "gecko"))] - DisplayInside::Block | DisplayInside::Inline => DisplayInside::Flow, - inside => inside, - }; + let inside = self.inside(); match *self { Display::Block | Display::Inline => outside.to_css(dest), Display::InlineBlock => dest.write_str("inline-block"), @@ -657,8 +618,6 @@ impl Parse for Display { #[cfg(feature = "gecko")] "-moz-deck" if moz_display_values_enabled(context) => Display::MozDeck, #[cfg(feature = "gecko")] - "-moz-groupbox" if moz_display_values_enabled(context) => Display::MozGroupbox, - #[cfg(feature = "gecko")] "-moz-popup" if moz_display_values_enabled(context) => Display::MozPopup, }) } diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs index f9feb616fff..9736491adce 100644 --- a/components/style/values/specified/calc.rs +++ b/components/style/values/specified/calc.rs @@ -10,15 +10,61 @@ use crate::parser::ParserContext; use crate::values::computed; use crate::values::specified::length::ViewportPercentageLength; use crate::values::specified::length::{AbsoluteLength, FontRelativeLength, NoCalcLength}; -use crate::values::specified::{Angle, Time}; +use crate::values::specified::{self, Angle, Time}; use crate::values::{CSSFloat, CSSInteger}; -use cssparser::{AngleOrNumber, NumberOrPercentage, Parser, Token}; +use cssparser::{AngleOrNumber, CowRcStr, NumberOrPercentage, Parser, Token}; +use smallvec::SmallVec; use std::fmt::{self, Write}; +use std::{cmp, mem}; use style_traits::values::specified::AllowedNumericType; use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; +/// The name of the mathematical function that we're parsing. +#[derive(Clone, Copy, Debug)] +pub enum MathFunction { + /// `calc()`: https://drafts.csswg.org/css-values-4/#funcdef-calc + Calc, + /// `min()`: https://drafts.csswg.org/css-values-4/#funcdef-min + Min, + /// `max()`: https://drafts.csswg.org/css-values-4/#funcdef-max + Max, + /// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp + Clamp, +} + +/// This determines the order in which we serialize members of a calc() +/// sum. +/// +/// See https://drafts.csswg.org/css-values-4/#sort-a-calculations-children +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +enum SortKey { + Number, + Percentage, + Ch, + Deg, + Em, + Ex, + Px, + Rem, + Sec, + Vh, + Vmax, + Vmin, + Vw, + Other, +} + +/// Whether we're a `min` or `max` function. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum MinMaxOp { + /// `min()` + Min, + /// `max()` + Max, +} + /// A node inside a `Calc` expression's AST. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum CalcNode { /// `<length>` Length(NoCalcLength), @@ -30,14 +76,20 @@ pub enum CalcNode { 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 expression of the form `x + y + ...`. Subtraction is represented by + /// the negated expression of the right hand side. + Sum(Box<[CalcNode]>), + /// A `min()` / `max()` function. + MinMax(Box<[CalcNode]>, MinMaxOp), + /// A `clamp()` function. + Clamp { + /// The minimum value. + min: Box<CalcNode>, + /// The central value. + center: Box<CalcNode>, + /// The maximum value. + max: Box<CalcNode>, + }, } /// An expected unit we intend to parse within a `calc()` expression. @@ -150,7 +202,362 @@ impl ToCss for CalcLengthPercentage { impl SpecifiedValueInfo for CalcLengthPercentage {} +macro_rules! impl_generic_to_type { + ($self:ident, $self_variant:ident, $to_self:ident, $to_float:ident, $from_float:path) => {{ + if let Self::$self_variant(ref v) = *$self { + return Ok(v.clone()); + } + + Ok(match *$self { + Self::Sum(ref expressions) => { + let mut sum = 0.; + for sub in &**expressions { + sum += sub.$to_self()?.$to_float(); + } + $from_float(sum) + }, + Self::Clamp { + ref min, + ref center, + ref max, + } => { + let min = min.$to_self()?; + let center = center.$to_self()?; + let max = max.$to_self()?; + + // Equivalent to cmp::max(min, cmp::min(center, max)) + // + // But preserving units when appropriate. + let center_float = center.$to_float(); + let min_float = min.$to_float(); + let max_float = max.$to_float(); + + let mut result = center; + let mut result_float = center_float; + + if result_float > max_float { + result = max; + result_float = max_float; + } + + if result_float < min_float { + min + } else { + result + } + }, + Self::MinMax(ref nodes, op) => { + let mut result = nodes[0].$to_self()?; + let mut result_float = result.$to_float(); + for node in nodes.iter().skip(1) { + let candidate = node.$to_self()?; + let candidate_float = candidate.$to_float(); + let candidate_wins = match op { + MinMaxOp::Min => candidate_float < result_float, + MinMaxOp::Max => candidate_float > result_float, + }; + if candidate_wins { + result = candidate; + result_float = candidate_float; + } + } + result + }, + Self::Length(..) | + Self::Angle(..) | + Self::Time(..) | + Self::Percentage(..) | + Self::Number(..) => return Err(()), + }) + }}; +} + +impl PartialOrd for CalcNode { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { + use self::CalcNode::*; + match (self, other) { + (&Length(ref one), &Length(ref other)) => one.partial_cmp(other), + (&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other), + (&Angle(ref one), &Angle(ref other)) => one.degrees().partial_cmp(&other.degrees()), + (&Time(ref one), &Time(ref other)) => one.seconds().partial_cmp(&other.seconds()), + (&Number(ref one), &Number(ref other)) => one.partial_cmp(other), + _ => None, + } + } +} + impl CalcNode { + fn negate(&mut self) { + self.mul_by(-1.); + } + + fn mul_by(&mut self, scalar: f32) { + match *self { + Self::Length(ref mut l) => { + // FIXME: For consistency this should probably convert absolute + // lengths into pixels. + *l = *l * scalar; + }, + Self::Number(ref mut n) => { + *n *= scalar; + }, + Self::Angle(ref mut a) => { + *a = Angle::from_calc(a.degrees() * scalar); + }, + Self::Time(ref mut t) => { + *t = Time::from_calc(t.seconds() * scalar); + }, + Self::Percentage(ref mut p) => { + *p *= scalar; + }, + // Multiplication is distributive across this. + Self::Sum(ref mut children) => { + for node in &mut **children { + node.mul_by(scalar); + } + }, + // This one is a bit trickier. + Self::MinMax(ref mut children, ref mut op) => { + for node in &mut **children { + node.mul_by(scalar); + } + + // For negatives we need to invert the operation. + if scalar < 0. { + *op = match *op { + MinMaxOp::Min => MinMaxOp::Max, + MinMaxOp::Max => MinMaxOp::Min, + } + } + }, + // Multiplication is distributive across these. + Self::Clamp { + ref mut min, + ref mut center, + ref mut max, + } => { + min.mul_by(scalar); + center.mul_by(scalar); + max.mul_by(scalar); + // For negatives we need to swap min / max. + if scalar < 0. { + mem::swap(min, max); + } + }, + } + } + + fn calc_node_sort_key(&self) -> SortKey { + match *self { + Self::Number(..) => SortKey::Number, + Self::Percentage(..) => SortKey::Percentage, + Self::Time(..) => SortKey::Sec, + Self::Angle(..) => SortKey::Deg, + Self::Length(ref l) => match *l { + NoCalcLength::Absolute(..) => SortKey::Px, + NoCalcLength::FontRelative(ref relative) => match *relative { + FontRelativeLength::Ch(..) => SortKey::Ch, + FontRelativeLength::Em(..) => SortKey::Em, + FontRelativeLength::Ex(..) => SortKey::Ex, + FontRelativeLength::Rem(..) => SortKey::Rem, + }, + NoCalcLength::ViewportPercentage(ref vp) => match *vp { + ViewportPercentageLength::Vh(..) => SortKey::Vh, + ViewportPercentageLength::Vw(..) => SortKey::Vw, + ViewportPercentageLength::Vmax(..) => SortKey::Vmax, + ViewportPercentageLength::Vmin(..) => SortKey::Vmin, + }, + NoCalcLength::ServoCharacterWidth(..) => unreachable!(), + }, + Self::Sum(..) | Self::MinMax(..) | Self::Clamp { .. } => SortKey::Other, + } + } + + /// Tries to merge one sum to another, that is, perform `x` + `y`. + /// + /// Only handles leaf nodes, it's the caller's responsibility to simplify + /// them before calling this if needed. + fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> { + use self::CalcNode::*; + + match (self, other) { + (&mut Number(ref mut one), &Number(ref other)) | + (&mut Percentage(ref mut one), &Percentage(ref other)) => { + *one += *other; + }, + (&mut Angle(ref mut one), &Angle(ref other)) => { + *one = specified::Angle::from_calc(one.degrees() + other.degrees()); + }, + (&mut Time(ref mut one), &Time(ref other)) => { + *one = specified::Time::from_calc(one.seconds() + other.seconds()); + }, + (&mut Length(ref mut one), &Length(ref other)) => { + *one = one.try_sum(other)?; + }, + _ => return Err(()), + } + + Ok(()) + } + + /// Simplifies and sorts the calculation. This is only needed if it's going + /// to be preserved after parsing (so, for `<length-percentage>`). Otherwise + /// we can just evaluate it and we'll come up with a simplified value + /// anyways. + fn simplify_and_sort_children(&mut self) { + macro_rules! replace_self_with { + ($slot:expr) => {{ + let result = mem::replace($slot, Self::Number(0.)); + mem::replace(self, result); + }}; + } + match *self { + Self::Clamp { + ref mut min, + ref mut center, + ref mut max, + } => { + min.simplify_and_sort_children(); + center.simplify_and_sort_children(); + max.simplify_and_sort_children(); + + // NOTE: clamp() is max(min, min(center, max)) + let min_cmp_center = match min.partial_cmp(¢er) { + Some(o) => o, + None => return, + }; + + // So if we can prove that min is more than center, then we won, + // as that's what we should always return. + if matches!(min_cmp_center, cmp::Ordering::Greater) { + return replace_self_with!(&mut **min); + } + + // Otherwise try with max. + let max_cmp_center = match max.partial_cmp(¢er) { + Some(o) => o, + None => return, + }; + + if matches!(max_cmp_center, cmp::Ordering::Less) { + // max is less than center, so we need to return effectively + // `max(min, max)`. + let max_cmp_min = match max.partial_cmp(&min) { + Some(o) => o, + None => { + debug_assert!( + false, + "We compared center with min and max, how are \ + min / max not comparable with each other?" + ); + return; + }, + }; + + if matches!(max_cmp_min, cmp::Ordering::Less) { + return replace_self_with!(&mut **min); + } + + return replace_self_with!(&mut **max); + } + + // Otherwise we're the center node. + return replace_self_with!(&mut **center); + }, + Self::MinMax(ref mut children, op) => { + for child in &mut **children { + child.simplify_and_sort_children(); + } + + let winning_order = match op { + MinMaxOp::Min => cmp::Ordering::Less, + MinMaxOp::Max => cmp::Ordering::Greater, + }; + + let mut result = 0; + for i in 1..children.len() { + let o = match children[i].partial_cmp(&children[result]) { + // We can't compare all the children, so we can't + // know which one will actually win. Bail out and + // keep ourselves as a min / max function. + // + // TODO: Maybe we could simplify compatible children, + // see https://github.com/w3c/csswg-drafts/issues/4756 + None => return, + Some(o) => o, + }; + + if o == winning_order { + result = i; + } + } + + replace_self_with!(&mut children[result]); + }, + Self::Sum(ref mut children_slot) => { + let mut sums_to_merge = SmallVec::<[_; 3]>::new(); + let mut extra_kids = 0; + for (i, child) in children_slot.iter_mut().enumerate() { + child.simplify_and_sort_children(); + if let Self::Sum(ref mut children) = *child { + extra_kids += children.len(); + sums_to_merge.push(i); + } + } + + // If we only have one kid, we've already simplified it, and it + // doesn't really matter whether it's a sum already or not, so + // lift it up and continue. + if children_slot.len() == 1 { + return replace_self_with!(&mut children_slot[0]); + } + + let mut children = mem::replace(children_slot, Box::new([])).into_vec(); + + if !sums_to_merge.is_empty() { + children.reserve(extra_kids - sums_to_merge.len()); + // Merge all our nested sums, in reverse order so that the + // list indices are not invalidated. + for i in sums_to_merge.drain(..).rev() { + let kid_children = match children.swap_remove(i) { + Self::Sum(c) => c, + _ => unreachable!(), + }; + + // This would be nicer with + // https://github.com/rust-lang/rust/issues/59878 fixed. + children.extend(kid_children.into_vec()); + } + } + + debug_assert!(children.len() >= 2, "Should still have multiple kids!"); + + // Sort by spec order. + children.sort_unstable_by_key(|c| c.calc_node_sort_key()); + + // NOTE: if the function returns true, by the docs of dedup_by, + // a is removed. + children.dedup_by(|a, b| b.try_sum_in_place(a).is_ok()); + + if children.len() == 1 { + // If only one children remains, lift it up, and carry on. + replace_self_with!(&mut children[0]); + } else { + // Else put our simplified children back. + mem::replace(children_slot, children.into_boxed_slice()); + } + }, + Self::Length(ref mut len) => { + if let NoCalcLength::Absolute(ref mut absolute_length) = *len { + *absolute_length = AbsoluteLength::Px(absolute_length.to_px()); + } + }, + Self::Percentage(..) | Self::Angle(..) | Self::Time(..) | Self::Number(..) => { + // These are leaves already, nothing to do. + }, + } + } + /// Tries to parse a single element in the expression, that is, a /// `<length>`, `<angle>`, `<time>`, `<percentage>`, according to /// `expected_unit`. @@ -203,11 +610,12 @@ impl CalcNode { (&Token::Percentage { unit_value, .. }, CalcUnit::Percentage) => { Ok(CalcNode::Percentage(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)) + (&Token::ParenthesisBlock, _) => input.parse_nested_block(|input| { + CalcNode::parse_argument(context, input, expected_unit) + }), + (&Token::Function(ref name), _) => { + let function = CalcNode::math_function(name, location)?; + CalcNode::parse(context, input, function, expected_unit) }, (t, _) => Err(location.new_unexpected_token_error(t.clone())), } @@ -219,9 +627,58 @@ impl CalcNode { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + function: MathFunction, + expected_unit: CalcUnit, + ) -> Result<Self, ParseError<'i>> { + // TODO: Do something different based on the function name. In + // particular, for non-calc function we need to take a list of + // comma-separated arguments and such. + input.parse_nested_block(|input| { + match function { + MathFunction::Calc => Self::parse_argument(context, input, expected_unit), + MathFunction::Clamp => { + let min = Self::parse_argument(context, input, expected_unit)?; + input.expect_comma()?; + let center = Self::parse_argument(context, input, expected_unit)?; + input.expect_comma()?; + let max = Self::parse_argument(context, input, expected_unit)?; + Ok(Self::Clamp { + min: Box::new(min), + center: Box::new(center), + max: Box::new(max), + }) + }, + MathFunction::Min | MathFunction::Max => { + // TODO(emilio): The common case for parse_comma_separated + // is just one element, but for min / max is two, really... + // + // Consider adding an API to cssparser to specify the + // initial vector capacity? + let arguments = input + .parse_comma_separated(|input| { + Self::parse_argument(context, input, expected_unit) + })? + .into_boxed_slice(); + + let op = match function { + MathFunction::Min => MinMaxOp::Min, + MathFunction::Max => MinMaxOp::Max, + _ => unreachable!(), + }; + + Ok(Self::MinMax(arguments, op)) + }, + } + }) + } + + fn parse_argument<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, expected_unit: CalcUnit, ) -> Result<Self, ParseError<'i>> { - let mut root = Self::parse_product(context, input, expected_unit)?; + let mut sum = SmallVec::<[CalcNode; 1]>::new(); + sum.push(Self::parse_product(context, input, expected_unit)?); loop { let start = input.state(); @@ -232,14 +689,12 @@ impl CalcNode { } 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; + sum.push(Self::parse_product(context, input, expected_unit)?); }, 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; + let mut rhs = Self::parse_product(context, input, expected_unit)?; + rhs.negate(); + sum.push(rhs); }, ref t => { let t = t.clone(); @@ -254,7 +709,11 @@ impl CalcNode { } } - Ok(root) + Ok(if sum.len() == 1 { + sum.drain(..).next().unwrap() + } else { + Self::Sum(sum.into_boxed_slice()) + }) } /// Parse a top-level `calc` expression, and all the products that may @@ -271,20 +730,38 @@ impl CalcNode { input: &mut Parser<'i, 't>, expected_unit: CalcUnit, ) -> Result<Self, ParseError<'i>> { - let mut root = Self::parse_one(context, input, expected_unit)?; + let mut node = Self::parse_one(context, input, expected_unit)?; loop { let start = input.state(); 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; + if let Ok(rhs) = rhs.to_number() { + node.mul_by(rhs); + } else if let Ok(number) = node.to_number() { + node = rhs; + node.mul_by(number); + } else { + // One of the two parts of the multiplication has to be + // a number, at least until we implement unit math. + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } }, Ok(&Token::Delim('/')) => { let rhs = Self::parse_one(context, input, expected_unit)?; - let new_root = CalcNode::Div(Box::new(root), Box::new(rhs)); - root = new_root; + // Dividing by units is not ok. + // + // TODO(emilio): Eventually it should be. + let number = match rhs.to_number() { + Ok(n) if n != 0. => n, + _ => { + return Err( + input.new_custom_error(StyleParseErrorKind::UnspecifiedError) + ); + }, + }; + node.mul_by(1. / number); }, _ => { input.reset(&start); @@ -293,55 +770,24 @@ impl CalcNode { } } - Ok(root) + Ok(node) } /// Tries to simplify this expression into a `<length>` or `<percentage`> /// value. fn to_length_or_percentage( - &self, + &mut self, clamping_mode: AllowedNumericType, ) -> Result<CalcLengthPercentage, ()> { let mut ret = CalcLengthPercentage { - clamping_mode: clamping_mode, + clamping_mode, ..Default::default() }; + self.simplify_and_sort_children(); self.add_length_or_percentage_to(&mut ret, 1.0)?; Ok(ret) } - /// Tries to simplify this expression into a `<percentage>` value. - fn to_percentage(&self) -> Result<CSSFloat, ()> { - Ok(match *self { - CalcNode::Percentage(percentage) => percentage, - CalcNode::Sub(ref a, ref b) => a.to_percentage()? - b.to_percentage()?, - CalcNode::Sum(ref a, ref b) => a.to_percentage()? + b.to_percentage()?, - CalcNode::Mul(ref a, ref b) => match a.to_percentage() { - Ok(lhs) => { - let rhs = b.to_number()?; - lhs * rhs - }, - Err(..) => { - let lhs = a.to_number()?; - let rhs = b.to_percentage()?; - lhs * rhs - }, - }, - CalcNode::Div(ref a, ref b) => { - let lhs = a.to_percentage()?; - let rhs = b.to_number()?; - if rhs == 0. { - return Err(()); - } - lhs / rhs - }, - CalcNode::Number(..) | - CalcNode::Length(..) | - CalcNode::Angle(..) | - CalcNode::Time(..) => return Err(()), - }) - } - /// Puts this `<length>` or `<percentage>` into `ret`, or error. /// /// `factor` is the sign or multiplicative factor to account for the sign @@ -394,29 +840,14 @@ impl CalcNode { }, NoCalcLength::ServoCharacterWidth(..) => unreachable!(), }, - 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(()); + CalcNode::Sum(ref children) => { + for child in &**children { + child.add_length_or_percentage_to(ret, factor)?; } - a.add_length_or_percentage_to(ret, factor / new_factor)?; + }, + CalcNode::MinMax(..) | CalcNode::Clamp { .. } => { + // FIXME(emilio): Implement min/max/clamp for length-percentage. + return Err(()); }, CalcNode::Angle(..) | CalcNode::Time(..) | CalcNode::Number(..) => return Err(()), } @@ -426,103 +857,55 @@ impl CalcNode { /// 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(()), - }) + impl_generic_to_type!(self, Time, to_time, seconds, Time::from_calc) } /// 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.degrees() - rhs.degrees()) - }, - CalcNode::Sum(ref a, ref b) => { - let lhs = a.to_angle()?; - let rhs = b.to_angle()?; - Angle::from_calc(lhs.degrees() + rhs.degrees()) - }, - CalcNode::Mul(ref a, ref b) => match a.to_angle() { - Ok(lhs) => { - let rhs = b.to_number()?; - Angle::from_calc(lhs.degrees() * rhs) - }, - Err(..) => { - let lhs = a.to_number()?; - let rhs = b.to_angle()?; - Angle::from_calc(lhs * rhs.degrees()) - }, - }, - 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.degrees() / rhs) - }, - CalcNode::Number(..) | - CalcNode::Length(..) | - CalcNode::Percentage(..) | - CalcNode::Time(..) => return Err(()), - }) + impl_generic_to_type!(self, Angle, to_angle, degrees, Angle::from_calc) } /// 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(()), + impl_generic_to_type!(self, Number, to_number, clone, From::from) + } + + /// Tries to simplify this expression into a `<percentage>` value. + fn to_percentage(&self) -> Result<CSSFloat, ()> { + impl_generic_to_type!(self, Percentage, to_percentage, clone, From::from) + } + + /// Given a function name, and the location from where the token came from, + /// return a mathematical function corresponding to that name or an error. + #[inline] + pub fn math_function<'i>( + name: &CowRcStr<'i>, + location: cssparser::SourceLocation, + ) -> Result<MathFunction, ParseError<'i>> { + // TODO(emilio): Unify below when the pref for math functions is gone. + if name.eq_ignore_ascii_case("calc") { + return Ok(MathFunction::Calc); + } + + #[cfg(feature = "gecko")] + fn comparison_functions_enabled() -> bool { + static_prefs::pref!("layout.css.comparison-functions.enabled") + } + + #[cfg(feature = "servo")] + fn comparison_functions_enabled() -> bool { + false + } + + if !comparison_functions_enabled() { + return Err(location.new_unexpected_token_error(Token::Function(name.clone()))); + } + + Ok(match_ignore_ascii_case! { &*name, + "min" => MathFunction::Min, + "max" => MathFunction::Max, + "clamp" => MathFunction::Clamp, + _ => return Err(location.new_unexpected_token_error(Token::Function(name.clone()))), }) } @@ -530,8 +913,9 @@ impl CalcNode { pub fn parse_integer<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + function: MathFunction, ) -> Result<CSSInteger, ParseError<'i>> { - Self::parse_number(context, input).map(|n| n.round() as CSSInteger) + Self::parse_number(context, input, function).map(|n| n.round() as CSSInteger) } /// Convenience parsing function for `<length> | <percentage>`. @@ -539,8 +923,9 @@ impl CalcNode { context: &ParserContext, input: &mut Parser<'i, 't>, clamping_mode: AllowedNumericType, + function: MathFunction, ) -> Result<CalcLengthPercentage, ParseError<'i>> { - Self::parse(context, input, CalcUnit::LengthPercentage)? + Self::parse(context, input, function, CalcUnit::LengthPercentage)? .to_length_or_percentage(clamping_mode) .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } @@ -549,8 +934,9 @@ impl CalcNode { pub fn parse_percentage<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + function: MathFunction, ) -> Result<CSSFloat, ParseError<'i>> { - Self::parse(context, input, CalcUnit::Percentage)? + Self::parse(context, input, function, CalcUnit::Percentage)? .to_percentage() .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } @@ -560,8 +946,9 @@ impl CalcNode { context: &ParserContext, input: &mut Parser<'i, 't>, clamping_mode: AllowedNumericType, + function: MathFunction, ) -> Result<CalcLengthPercentage, ParseError<'i>> { - Self::parse(context, input, CalcUnit::Length)? + Self::parse(context, input, function, CalcUnit::Length)? .to_length_or_percentage(clamping_mode) .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } @@ -570,8 +957,9 @@ impl CalcNode { pub fn parse_number<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + function: MathFunction, ) -> Result<CSSFloat, ParseError<'i>> { - Self::parse(context, input, CalcUnit::Number)? + Self::parse(context, input, function, CalcUnit::Number)? .to_number() .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } @@ -580,8 +968,9 @@ impl CalcNode { pub fn parse_angle<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + function: MathFunction, ) -> Result<Angle, ParseError<'i>> { - Self::parse(context, input, CalcUnit::Angle)? + Self::parse(context, input, function, CalcUnit::Angle)? .to_angle() .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } @@ -590,8 +979,9 @@ impl CalcNode { pub fn parse_time<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + function: MathFunction, ) -> Result<Time, ParseError<'i>> { - Self::parse(context, input, CalcUnit::Time)? + Self::parse(context, input, function, CalcUnit::Time)? .to_time() .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } @@ -600,8 +990,9 @@ impl CalcNode { pub fn parse_number_or_percentage<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + function: MathFunction, ) -> Result<NumberOrPercentage, ParseError<'i>> { - let node = Self::parse(context, input, CalcUnit::Percentage)?; + let node = Self::parse(context, input, function, CalcUnit::Percentage)?; if let Ok(value) = node.to_number() { return Ok(NumberOrPercentage::Number { value }); @@ -617,8 +1008,9 @@ impl CalcNode { pub fn parse_angle_or_number<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + function: MathFunction, ) -> Result<AngleOrNumber, ParseError<'i>> { - let node = Self::parse(context, input, CalcUnit::Angle)?; + let node = Self::parse(context, input, function, CalcUnit::Angle)?; if let Ok(angle) = node.to_angle() { let degrees = angle.degrees(); diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 4e761f95338..be969f27cd4 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -298,8 +298,9 @@ impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorComponentParser<'i> for ColorComponen Ok(AngleOrNumber::Angle { degrees }) }, Token::Number { value, .. } => Ok(AngleOrNumber::Number { value }), - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { - input.parse_nested_block(|i| CalcNode::parse_angle_or_number(self.0, i)) + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + CalcNode::parse_angle_or_number(self.0, input, function) }, t => return Err(location.new_unexpected_token_error(t)), } @@ -323,15 +324,16 @@ impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorComponentParser<'i> for ColorComponen ) -> Result<NumberOrPercentage, ParseError<'i>> { let location = input.current_source_location(); - match input.next()?.clone() { + match *input.next()? { Token::Number { value, .. } => Ok(NumberOrPercentage::Number { value }), Token::Percentage { unit_value, .. } => { Ok(NumberOrPercentage::Percentage { unit_value }) }, - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { - input.parse_nested_block(|i| CalcNode::parse_number_or_percentage(self.0, i)) + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + CalcNode::parse_number_or_percentage(self.0, input, function) }, - t => return Err(location.new_unexpected_token_error(t)), + ref t => return Err(location.new_unexpected_token_error(t.clone())), } } } diff --git a/components/style/values/specified/counters.rs b/components/style/values/specified/counters.rs index 09020e73448..0849cc19116 100644 --- a/components/style/values/specified/counters.rs +++ b/components/style/values/specified/counters.rs @@ -9,8 +9,6 @@ use crate::computed_values::list_style_type::T as ListStyleType; use crate::parser::{Parse, ParserContext}; use crate::values::generics::counters as generics; use crate::values::generics::counters::CounterPair; -use crate::values::generics::counters::GenericCounterIncrement; -use crate::values::generics::counters::GenericCounterSetOrReset; #[cfg(feature = "gecko")] use crate::values::generics::CounterStyle; use crate::values::specified::url::SpecifiedImageUrl; @@ -23,7 +21,7 @@ use selectors::parser::SelectorParseErrorKind; use style_traits::{ParseError, StyleParseErrorKind}; /// A specified value for the `counter-increment` property. -pub type CounterIncrement = GenericCounterIncrement<Integer>; +pub type CounterIncrement = generics::GenericCounterIncrement<Integer>; impl Parse for CounterIncrement { fn parse<'i, 't>( @@ -35,7 +33,7 @@ impl Parse for CounterIncrement { } /// A specified value for the `counter-set` and `counter-reset` properties. -pub type CounterSetOrReset = GenericCounterSetOrReset<Integer>; +pub type CounterSetOrReset = generics::GenericCounterSetOrReset<Integer>; impl Parse for CounterSetOrReset { fn parse<'i, 't>( @@ -84,10 +82,10 @@ fn parse_counters<'i, 't>( } /// The specified value for the `content` property. -pub type Content = generics::Content<SpecifiedImageUrl>; +pub type Content = generics::GenericContent<SpecifiedImageUrl>; /// The specified value for a content item in the `content` property. -pub type ContentItem = generics::ContentItem<SpecifiedImageUrl>; +pub type ContentItem = generics::GenericContentItem<SpecifiedImageUrl>; impl Content { #[cfg(feature = "servo")] @@ -115,6 +113,7 @@ impl Parse for Content { // normal | none | [ <string> | <counter> | open-quote | close-quote | no-open-quote | // no-close-quote ]+ // TODO: <uri>, attr(<identifier>) + #[cfg_attr(feature = "servo", allow(unused_mut))] fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, @@ -131,17 +130,9 @@ impl Parse for Content { { return Ok(generics::Content::None); } - #[cfg(feature = "gecko")] - { - if input - .try(|input| input.expect_ident_matching("-moz-alt-content")) - .is_ok() - { - return Ok(generics::Content::MozAltContent); - } - } let mut content = vec![]; + let mut has_alt_content = false; loop { #[cfg(feature = "gecko")] { @@ -153,7 +144,7 @@ impl Parse for Content { match input.next() { Ok(&Token::QuotedString(ref value)) => { content.push(generics::ContentItem::String( - value.as_ref().to_owned().into_boxed_str(), + value.as_ref().to_owned().into(), )); }, Ok(&Token::Function(ref name)) => { @@ -168,7 +159,7 @@ impl Parse for Content { let location = input.current_source_location(); let name = CustomIdent::from_ident(location, input.expect_ident()?, &[])?; input.expect_comma()?; - let separator = input.expect_string()?.as_ref().to_owned().into_boxed_str(); + let separator = input.expect_string()?.as_ref().to_owned().into(); let style = Content::parse_counter_style(context, input); Ok(generics::ContentItem::Counters(name, separator, style)) }), @@ -191,6 +182,11 @@ impl Parse for Content { "close-quote" => generics::ContentItem::CloseQuote, "no-open-quote" => generics::ContentItem::NoOpenQuote, "no-close-quote" => generics::ContentItem::NoCloseQuote, + #[cfg(feature = "gecko")] + "-moz-alt-content" => { + has_alt_content = true; + generics::ContentItem::MozAltContent + }, _ =>{ let ident = ident.clone(); return Err(input.new_custom_error( @@ -206,9 +202,10 @@ impl Parse for Content { }, } } - if content.is_empty() { + // We don't allow to parse `-moz-alt-content in multiple positions. + if content.is_empty() || (has_alt_content && content.len() != 1) { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } - Ok(generics::Content::Items(content.into_boxed_slice())) + Ok(generics::Content::Items(content.into())) } } diff --git a/components/style/values/specified/font.rs b/components/style/values/specified/font.rs index 05a29c94087..84c5141ed92 100644 --- a/components/style/values/specified/font.rs +++ b/components/style/values/specified/font.rs @@ -574,11 +574,11 @@ impl KeywordInfo { /// Given a parent keyword info (self), apply an additional factor/offset to /// it. - pub fn compose(self, factor: f32, offset: CSSPixelLength) -> Self { + fn compose(self, factor: f32) -> Self { KeywordInfo { kw: self.kw, factor: self.factor * factor, - offset: self.offset * factor + offset, + offset: self.offset * factor, } } } @@ -614,12 +614,6 @@ pub enum FontSize { System(SystemFont), } -impl From<LengthPercentage> for FontSize { - fn from(other: LengthPercentage) -> Self { - FontSize::Length(other) - } -} - /// Specifies a prioritized list of font family names or generic family names. #[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)] #[cfg_attr(feature = "servo", derive(Hash))] @@ -901,7 +895,7 @@ impl FontSize { .get_parent_font() .clone_font_size() .keyword_info - .map(|i| i.compose(factor, CSSPixelLength::new(0.))) + .map(|i| i.compose(factor)) }; let mut info = None; let size = match *self { @@ -927,42 +921,8 @@ impl FontSize { base_size.resolve(context) * pc.0 }, FontSize::Length(LengthPercentage::Calc(ref calc)) => { - let parent = context.style().get_parent_font().clone_font_size(); - // if we contain em/% units and the parent was keyword derived, this is too - // Extract the ratio/offset and compose it - if (calc.em.is_some() || calc.percentage.is_some()) && parent.keyword_info.is_some() - { - let ratio = calc.em.unwrap_or(0.) + calc.percentage.map_or(0., |pc| pc.0); - // Compute it, but shave off the font-relative part (em, %). - // - // This will mean that other font-relative units like ex and - // ch will be computed against the old parent font even when - // the font changes. - // - // There's no particular "right answer" for what to do here, - // Gecko recascades as if the font had changed, we instead - // track the changes and reapply, which means that we carry - // over old computed ex/ch values whilst Gecko recomputes - // new ones. - // - // This is enough of an edge case to not really matter. - let abs = calc - .to_computed_value_zoomed( - context, - FontBaseSize::InheritedStyleButStripEmUnits, - ) - .unclamped_length(); - - info = parent.keyword_info.map(|i| i.compose(ratio, abs)); - } let calc = calc.to_computed_value_zoomed(context, base_size); - // FIXME(emilio): we _could_ use clamp_to_non_negative() - // everywhere, without affecting behavior in theory, since the - // others should reject negatives during parsing. But SMIL - // allows parsing negatives, and relies on us _not_ doing that - // clamping. That's so bonkers :( - calc.percentage_relative_to(base_size.resolve(context)) - .clamp_to_non_negative() + calc.resolve(base_size.resolve(context)) }, FontSize::Keyword(i) => { // As a specified keyword, this is keyword derived diff --git a/components/style/values/specified/gecko.rs b/components/style/values/specified/gecko.rs index 4c85d1df668..3e3085c8849 100644 --- a/components/style/values/specified/gecko.rs +++ b/components/style/values/specified/gecko.rs @@ -23,7 +23,7 @@ fn parse_pixel_or_percent<'i, 't>( value, ref unit, .. } => { match_ignore_ascii_case! { unit, - "px" => Ok(LengthPercentage::new(Length::new(value), None)), + "px" => Ok(LengthPercentage::new_length(Length::new(value))), _ => Err(()), } }, diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index d9a51665ead..ff24404c09a 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -7,9 +7,9 @@ //! [length]: https://drafts.csswg.org/css-values/#lengths use super::{AllowQuirks, Number, Percentage, ToComputedValue}; +use crate::computed_value_flags::ComputedValueFlags; use crate::font_metrics::{FontMetrics, FontMetricsOrientation}; use crate::parser::{Parse, ParserContext}; -use crate::properties::computed_value_flags::ComputedValueFlags; use crate::values::computed::{self, CSSPixelLength, Context}; use crate::values::generics::length as generics; use crate::values::generics::length::{ @@ -71,10 +71,6 @@ pub enum FontBaseSize { CurrentStyle, /// Use the inherited font-size. InheritedStyle, - /// Use the inherited font-size, but strip em units. - /// - /// FIXME(emilio): This is very complex, and should go away. - InheritedStyleButStripEmUnits, } impl FontBaseSize { @@ -82,7 +78,7 @@ impl FontBaseSize { pub fn resolve(&self, context: &Context) -> computed::Length { match *self { FontBaseSize::CurrentStyle => context.style().get_font().clone_font_size().size(), - FontBaseSize::InheritedStyleButStripEmUnits | FontBaseSize::InheritedStyle => { + FontBaseSize::InheritedStyle => { context.style().get_parent_font().clone_font_size().size() }, } @@ -100,6 +96,29 @@ impl FontRelativeLength { } } + fn try_sum(&self, other: &Self) -> Result<Self, ()> { + use self::FontRelativeLength::*; + + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return Err(()); + } + + Ok(match (self, other) { + (&Em(one), &Em(other)) => Em(one + other), + (&Ex(one), &Ex(other)) => Ex(one + other), + (&Ch(one), &Ch(other)) => Ch(one + other), + (&Rem(one), &Rem(other)) => Rem(one + other), + // See https://github.com/rust-lang/rust/issues/68867. rustc isn't + // able to figure it own on its own so we help. + _ => unsafe { + match *self { + Em(..) | Ex(..) | Ch(..) | Rem(..) => {}, + } + debug_unreachable!("Forgot to handle unit in try_sum()") + }, + }) + } + /// Computes the font-relative length. pub fn to_computed_value( &self, @@ -144,11 +163,7 @@ impl FontRelativeLength { } } - if base_size == FontBaseSize::InheritedStyleButStripEmUnits { - (Zero::zero(), length) - } else { - (reference_font_size, length) - } + (reference_font_size, length) }, FontRelativeLength::Ex(length) => { if context.for_non_inherited_property.is_some() { @@ -214,7 +229,7 @@ impl FontRelativeLength { // element, the rem units refer to the property's initial // value. // - let reference_size = if context.is_root_element || context.in_media_query { + let reference_size = if context.builder.is_root_element || context.in_media_query { reference_font_size } else { computed::Length::new(context.device().root_font_size().to_f32_px()) @@ -255,6 +270,29 @@ impl ViewportPercentageLength { } } + fn try_sum(&self, other: &Self) -> Result<Self, ()> { + use self::ViewportPercentageLength::*; + + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return Err(()); + } + + Ok(match (self, other) { + (&Vw(one), &Vw(other)) => Vw(one + other), + (&Vh(one), &Vh(other)) => Vh(one + other), + (&Vmin(one), &Vmin(other)) => Vmin(one + other), + (&Vmax(one), &Vmax(other)) => Vmax(one + other), + // See https://github.com/rust-lang/rust/issues/68867. rustc isn't + // able to figure it own on its own so we help. + _ => unsafe { + match *self { + Vw(..) | Vh(..) | Vmin(..) | Vmax(..) => {}, + } + debug_unreachable!("Forgot to handle unit in try_sum()") + }, + }) + } + /// Computes the given viewport-relative length for the given viewport size. pub fn to_computed_value(&self, viewport_size: Size2D<Au>) -> CSSPixelLength { let (factor, length) = match *self { @@ -362,6 +400,12 @@ impl ToComputedValue for AbsoluteLength { } } +impl PartialOrd for AbsoluteLength { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { + self.to_px().partial_cmp(&other.to_px()) + } +} + impl Mul<CSSFloat> for AbsoluteLength { type Output = AbsoluteLength; @@ -476,6 +520,37 @@ impl NoCalcLength { }) } + /// Try to sume two lengths if compatible into the left hand side. + pub(crate) fn try_sum(&self, other: &Self) -> Result<Self, ()> { + use self::NoCalcLength::*; + + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return Err(()); + } + + Ok(match (self, other) { + (&Absolute(ref one), &Absolute(ref other)) => Absolute(*one + *other), + (&FontRelative(ref one), &FontRelative(ref other)) => FontRelative(one.try_sum(other)?), + (&ViewportPercentage(ref one), &ViewportPercentage(ref other)) => { + ViewportPercentage(one.try_sum(other)?) + }, + (&ServoCharacterWidth(ref one), &ServoCharacterWidth(ref other)) => { + ServoCharacterWidth(CharacterWidth(one.0 + other.0)) + }, + // See https://github.com/rust-lang/rust/issues/68867. rustc isn't + // able to figure it own on its own so we help. + _ => unsafe { + match *self { + Absolute(..) | + FontRelative(..) | + ViewportPercentage(..) | + ServoCharacterWidth(..) => {}, + } + debug_unreachable!("Forgot to handle unit in try_sum()") + }, + }) + } + /// Get a px value without context. #[inline] pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> { @@ -494,6 +569,38 @@ impl NoCalcLength { impl SpecifiedValueInfo for NoCalcLength {} +impl PartialOrd for NoCalcLength { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { + use self::NoCalcLength::*; + + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return None; + } + + match (self, other) { + (&Absolute(ref one), &Absolute(ref other)) => one.to_px().partial_cmp(&other.to_px()), + (&FontRelative(ref one), &FontRelative(ref other)) => one.partial_cmp(other), + (&ViewportPercentage(ref one), &ViewportPercentage(ref other)) => { + one.partial_cmp(other) + }, + (&ServoCharacterWidth(ref one), &ServoCharacterWidth(ref other)) => { + one.0.partial_cmp(&other.0) + }, + // See https://github.com/rust-lang/rust/issues/68867. rustc isn't + // able to figure it own on its own so we help. + _ => unsafe { + match *self { + Absolute(..) | + FontRelative(..) | + ViewportPercentage(..) | + ServoCharacterWidth(..) => {}, + } + debug_unreachable!("Forgot an arm in partial_cmp?") + }, + } + } +} + impl Zero for NoCalcLength { fn zero() -> Self { NoCalcLength::Absolute(AbsoluteLength::Px(0.)) @@ -542,6 +649,31 @@ impl Mul<CSSFloat> for Length { } } +impl PartialOrd for FontRelativeLength { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { + use self::FontRelativeLength::*; + + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return None; + } + + match (self, other) { + (&Em(ref one), &Em(ref other)) => one.partial_cmp(other), + (&Ex(ref one), &Ex(ref other)) => one.partial_cmp(other), + (&Ch(ref one), &Ch(ref other)) => one.partial_cmp(other), + (&Rem(ref one), &Rem(ref other)) => one.partial_cmp(other), + // See https://github.com/rust-lang/rust/issues/68867. rustc isn't + // able to figure it own on its own so we help. + _ => unsafe { + match *self { + Em(..) | Ex(..) | Ch(..) | Rem(..) => {}, + } + debug_unreachable!("Forgot an arm in partial_cmp?") + }, + } + } +} + impl Mul<CSSFloat> for FontRelativeLength { type Output = FontRelativeLength; @@ -570,6 +702,31 @@ impl Mul<CSSFloat> for ViewportPercentageLength { } } +impl PartialOrd for ViewportPercentageLength { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { + use self::ViewportPercentageLength::*; + + if std::mem::discriminant(self) != std::mem::discriminant(other) { + return None; + } + + match (self, other) { + (&Vw(ref one), &Vw(ref other)) => one.partial_cmp(other), + (&Vh(ref one), &Vh(ref other)) => one.partial_cmp(other), + (&Vmin(ref one), &Vmin(ref other)) => one.partial_cmp(other), + (&Vmax(ref one), &Vmax(ref other)) => one.partial_cmp(other), + // See https://github.com/rust-lang/rust/issues/68867. rustc isn't + // able to figure it own on its own so we help. + _ => unsafe { + match *self { + Vw(..) | Vh(..) | Vmin(..) | Vmax(..) => {}, + } + debug_unreachable!("Forgot an arm in partial_cmp?") + }, + } + } +} + impl Length { #[inline] fn parse_internal<'i, 't>( @@ -599,11 +756,11 @@ impl Length { value, )))) }, - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => input - .parse_nested_block(|input| { - CalcNode::parse_length(context, input, num_context) - .map(|calc| Length::Calc(Box::new(calc))) - }), + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + let calc = CalcNode::parse_length(context, input, num_context, function)?; + Ok(Length::Calc(Box::new(calc))) + }, ref token => return Err(location.new_unexpected_token_error(token.clone())), } } @@ -822,10 +979,10 @@ impl LengthPercentage { return Ok(LengthPercentage::Length(NoCalcLength::from_px(value))); } }, - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { - let calc = input.parse_nested_block(|i| { - CalcNode::parse_length_or_percentage(context, i, num_context) - })?; + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + let calc = + CalcNode::parse_length_or_percentage(context, input, num_context, function)?; Ok(LengthPercentage::Calc(Box::new(calc))) }, _ => return Err(location.new_unexpected_token_error(token.clone())), diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 7f3ecfb96b3..54401045809 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -31,7 +31,7 @@ use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKin pub use self::align::{AlignContent, AlignItems, AlignSelf, ContentDistribution}; #[cfg(feature = "gecko")] pub use self::align::{JustifyContent, JustifyItems, JustifySelf, SelfAlignment}; -pub use self::angle::Angle; +pub use self::angle::{AllowUnitlessZeroAngle, Angle}; pub use self::background::{BackgroundRepeat, BackgroundSize}; pub use self::basic_shape::FillRule; pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth}; @@ -130,6 +130,47 @@ pub mod transform; pub mod ui; pub mod url; +/// <angle> | <percentage> +/// https://drafts.csswg.org/css-values/#typedef-angle-percentage +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] +pub enum AngleOrPercentage { + Percentage(Percentage), + Angle(Angle), +} + +impl AngleOrPercentage { + fn parse_internal<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + allow_unitless_zero: AllowUnitlessZeroAngle, + ) -> Result<Self, ParseError<'i>> { + if let Ok(per) = input.try(|i| Percentage::parse(context, i)) { + return Ok(AngleOrPercentage::Percentage(per)); + } + + Angle::parse_internal(context, input, allow_unitless_zero).map(AngleOrPercentage::Angle) + } + + /// Allow unitless angles, used for conic-gradients as specified by the spec. + /// https://drafts.csswg.org/css-images-4/#valdef-conic-gradient-angle + pub fn parse_with_unitless<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::Yes) + } +} + +impl Parse for AngleOrPercentage { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::No) + } +} + /// Parse a `<number>` value, with a given clamping mode. fn parse_number_with_clamping_mode<'i, 't>( context: &ParserContext, @@ -144,8 +185,9 @@ fn parse_number_with_clamping_mode<'i, 't>( calc_clamping_mode: None, }) }, - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { - let result = input.parse_nested_block(|i| CalcNode::parse_number(context, i))?; + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + let result = CalcNode::parse_number(context, input, function)?; Ok(Number { value: result.min(f32::MAX).max(f32::MIN), calc_clamping_mode: Some(clamping_mode), @@ -543,8 +585,9 @@ impl Parse for Integer { Token::Number { int_value: Some(v), .. } => Ok(Integer::new(v)), - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { - let result = input.parse_nested_block(|i| CalcNode::parse_integer(context, i))?; + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + let result = CalcNode::parse_integer(context, input, function)?; Ok(Integer::from_calc(result)) }, ref t => Err(location.new_unexpected_token_error(t.clone())), @@ -559,16 +602,16 @@ impl Integer { input: &mut Parser<'i, 't>, min: i32, ) -> Result<Integer, ParseError<'i>> { - match Integer::parse(context, input) { - // FIXME(emilio): The spec asks us to avoid rejecting it at parse - // time except until computed value time. - // - // It's not totally clear it's worth it though, and no other browser - // does this. - Ok(value) if value.value() >= min => Ok(value), - Ok(_value) => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), - Err(e) => Err(e), + let value = Integer::parse(context, input)?; + // FIXME(emilio): The spec asks us to avoid rejecting it at parse + // time except until computed value time. + // + // It's not totally clear it's worth it though, and no other browser + // does this. + if value.value() < min { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } + Ok(value) } /// Parse a non-negative integer. @@ -767,9 +810,12 @@ impl AllowQuirks { ToShmem, )] #[css(function)] +#[repr(C)] pub struct Attr { - /// Optional namespace prefix and URL. - pub namespace: Option<(Prefix, Namespace)>, + /// Optional namespace prefix. + pub namespace_prefix: Prefix, + /// Optional namespace URL. + pub namespace_url: Namespace, /// Attribute name pub attribute: Atom, } @@ -814,7 +860,7 @@ impl Attr { ref t => return Err(location.new_unexpected_token_error(t.clone())), }; - let prefix_and_ns = if let Some(ns) = first { + let (namespace_prefix, namespace_url) = if let Some(ns) = first { let prefix = Prefix::from(ns.as_ref()); let ns = match get_namespace_for_prefix(&prefix, context) { Some(ns) => ns, @@ -823,17 +869,18 @@ impl Attr { .new_custom_error(StyleParseErrorKind::UnspecifiedError)); }, }; - Some((prefix, ns)) + (prefix, ns) } else { - None + (Prefix::default(), Namespace::default()) }; return Ok(Attr { - namespace: prefix_and_ns, + namespace_prefix, + namespace_url, attribute: Atom::from(second_token.as_ref()), }); }, // In the case of attr(foobar ) we don't want to error out - // because of the trailing whitespace + // because of the trailing whitespace. Token::WhiteSpace(..) => {}, ref t => return Err(input.new_unexpected_token_error(t.clone())), } @@ -841,7 +888,8 @@ impl Attr { if let Some(first) = first { Ok(Attr { - namespace: None, + namespace_prefix: Prefix::default(), + namespace_url: Namespace::default(), attribute: Atom::from(first.as_ref()), }) } else { @@ -856,8 +904,8 @@ impl ToCss for Attr { W: Write, { dest.write_str("attr(")?; - if let Some((ref prefix, ref _url)) = self.namespace { - serialize_atom_identifier(prefix, dest)?; + if !self.namespace_prefix.is_empty() { + serialize_atom_identifier(&self.namespace_prefix, dest)?; dest.write_str("|")?; } serialize_atom_identifier(&self.attribute, dest)?; diff --git a/components/style/values/specified/percentage.rs b/components/style/values/specified/percentage.rs index 50ebbb3bcf6..75549dca3be 100644 --- a/components/style/values/specified/percentage.rs +++ b/components/style/values/specified/percentage.rs @@ -117,14 +117,14 @@ impl Percentage { { Ok(Percentage::new(unit_value)) }, - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { - let result = - input.parse_nested_block(|i| CalcNode::parse_percentage(context, i))?; + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + let value = CalcNode::parse_percentage(context, input, function)?; // TODO(emilio): -moz-image-rect is the only thing that uses // the clamping mode... I guess we could disallow it... Ok(Percentage { - value: result, + value, calc_clamping_mode: Some(num_context), }) }, diff --git a/components/style/values/specified/position.rs b/components/style/values/specified/position.rs index 8d35671991d..3f01988e49d 100644 --- a/components/style/values/specified/position.rs +++ b/components/style/values/specified/position.rs @@ -22,6 +22,7 @@ use cssparser::Parser; use selectors::parser::SelectorParseErrorKind; use servo_arc::Arc; use std::fmt::{self, Write}; +use style_traits::values::specified::AllowedNumericType; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; /// The specified value of a CSS `<position>` @@ -297,7 +298,7 @@ impl<S: Side> ToComputedValue for PositionComponent<S> { let p = Percentage(1. - length.percentage()); let l = -length.unclamped_length(); // We represent `<end-side> <length>` as `calc(100% - <length>)`. - ComputedLengthPercentage::with_clamping_mode(l, Some(p), length.clamping_mode) + ComputedLengthPercentage::new_calc(l, Some(p), AllowedNumericType::All) }, PositionComponent::Side(_, Some(ref length)) | PositionComponent::Length(ref length) => length.to_computed_value(context), diff --git a/components/style/values/specified/svg_path.rs b/components/style/values/specified/svg_path.rs index 9a94af6a82f..288f396b73c 100644 --- a/components/style/values/specified/svg_path.rs +++ b/components/style/values/specified/svg_path.rs @@ -21,8 +21,10 @@ use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; #[derive( Clone, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, @@ -40,7 +42,6 @@ impl SVGPathData { /// Get the array of PathCommand. #[inline] pub fn commands(&self) -> &[PathCommand] { - debug_assert!(!self.0.is_empty()); &self.0 } @@ -90,10 +91,6 @@ impl Parse for SVGPathData { ) -> Result<Self, ParseError<'i>> { let location = input.current_source_location(); let path_string = input.expect_string()?.as_ref(); - if path_string.is_empty() { - // Treat an empty string as invalid, so we will not set it. - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } // Parse the svg path string as multiple sub-paths. let mut path_parser = PathParser::new(path_string); @@ -156,8 +153,10 @@ impl ComputeSquaredDistance for SVGPathData { ComputeSquaredDistance, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToShmem, @@ -483,8 +482,10 @@ impl ToCss for PathCommand { ComputeSquaredDistance, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToShmem, @@ -511,8 +512,10 @@ impl IsAbsolute { ComputeSquaredDistance, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToCss, @@ -530,7 +533,9 @@ impl CoordPair { } /// The EllipticalArc flag type. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem)] +#[derive( + Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, SpecifiedValueInfo, ToShmem, +)] #[repr(C)] pub struct ArcFlag(bool); diff --git a/components/style/values/specified/text.rs b/components/style/values/specified/text.rs index 8c50a61ef94..438b926802f 100644 --- a/components/style/values/specified/text.rs +++ b/components/style/values/specified/text.rs @@ -97,7 +97,7 @@ impl ToComputedValue for LineHeight { let computed_calc = calc.to_computed_value_zoomed(context, FontBaseSize::CurrentStyle); let base = context.style().get_font().clone_font_size().size(); - computed_calc.percentage_relative_to(base) + computed_calc.resolve(base) }, }; GenericLineHeight::Length(result.into()) @@ -596,7 +596,7 @@ impl ToComputedValue for TextAlign { // In that case, the default behavior here will set it to left, // but we want to set it to right -- instead set it to the default (`start`), // which will do the right thing in this case (but not the general case) - if _context.is_root_element { + if _context.builder.is_root_element { return TextAlignKeyword::Start; } let parent = _context @@ -1029,8 +1029,8 @@ pub enum TextDecorationSkipInk { None, } -/// Implements type for `text-underline-offset` and `text-decoration-thickness` properties -pub type TextDecorationLength = GenericTextDecorationLength<Length>; +/// Implements type for `text-decoration-thickness` property +pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>; impl TextDecorationLength { /// `Auto` value. @@ -1048,21 +1048,23 @@ impl TextDecorationLength { bitflags! { #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] - #[value_info(other_values = "auto,under,left,right")] + #[value_info(other_values = "auto,from-font,under,left,right")] #[repr(C)] /// Specified keyword values for the text-underline-position property. - /// (Non-exclusive, but not all combinations are allowed: only `under` may occur - /// together with either `left` or `right`.) - /// https://drafts.csswg.org/css-text-decor-3/#text-underline-position-property + /// (Non-exclusive, but not all combinations are allowed: the spec grammar gives + /// `auto | [ from-font | under ] || [ left | right ]`.) + /// https://drafts.csswg.org/css-text-decor-4/#text-underline-position-property pub struct TextUnderlinePosition: u8 { /// Use automatic positioning below the alphabetic baseline. const AUTO = 0; + /// Use underline position from the first available font. + const FROM_FONT = 1 << 0; /// Below the glyph box. - const UNDER = 1 << 0; + const UNDER = 1 << 1; /// In vertical mode, place to the left of the text. - const LEFT = 1 << 1; + const LEFT = 1 << 2; /// In vertical mode, place to the right of the text. - const RIGHT = 1 << 2; + const RIGHT = 1 << 3; } } @@ -1085,7 +1087,12 @@ impl Parse for TextUnderlinePosition { "auto" if result.is_empty() => { return Ok(result); }, - "under" if !result.intersects(TextUnderlinePosition::UNDER) => { + "from-font" if !result.intersects(TextUnderlinePosition::FROM_FONT | + TextUnderlinePosition::UNDER) => { + result.insert(TextUnderlinePosition::FROM_FONT); + }, + "under" if !result.intersects(TextUnderlinePosition::FROM_FONT | + TextUnderlinePosition::UNDER) => { result.insert(TextUnderlinePosition::UNDER); }, "left" if !result.intersects(TextUnderlinePosition::LEFT | @@ -1131,6 +1138,7 @@ impl ToCss for TextUnderlinePosition { }; } + maybe_write!(FROM_FONT => "from-font"); maybe_write!(UNDER => "under"); maybe_write!(LEFT => "left"); maybe_write!(RIGHT => "right"); diff --git a/components/style/values/specified/time.rs b/components/style/values/specified/time.rs index 0006ec45923..aba3f0a828f 100644 --- a/components/style/values/specified/time.rs +++ b/components/style/values/specified/time.rs @@ -69,7 +69,7 @@ impl Time { /// Returns a `Time` value from a CSS `calc()` expression. pub fn from_calc(seconds: CSSFloat) -> Self { Time { - seconds: seconds, + seconds, unit: TimeUnit::Second, was_calc: true, } @@ -95,11 +95,20 @@ impl Time { Time::parse_dimension(value, unit, /* from_calc = */ false) .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) }, - Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => { - match input.parse_nested_block(|i| CalcNode::parse_time(context, i)) { - Ok(time) if clamping_mode.is_ok(ParsingMode::DEFAULT, time.seconds) => Ok(time), - _ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), + Token::Function(ref name) => { + let function = CalcNode::math_function(name, location)?; + let time = CalcNode::parse_time(context, input, function)?; + + // FIXME(emilio): Rejecting calc() at parse time is wrong, + // was_calc should probably be replaced by calc_clamping_mode or + // something like we do for numbers, or we should do the + // clamping here instead (simpler, but technically incorrect, + // though still more correct than this!). + if !clamping_mode.is_ok(ParsingMode::DEFAULT, time.seconds) { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } + + Ok(time) }, ref t => return Err(location.new_unexpected_token_error(t.clone())), } |