diff options
Diffstat (limited to 'components/style/values')
34 files changed, 1950 insertions, 982 deletions
diff --git a/components/style/values/animated/length.rs b/components/style/values/animated/length.rs index 04a0844dbe0..04690446e64 100644 --- a/components/style/values/animated/length.rs +++ b/components/style/values/animated/length.rs @@ -7,6 +7,7 @@ use super::{Animate, Procedure}; use crate::values::computed::length::LengthPercentage; use crate::values::computed::Percentage; +use style_traits::values::specified::AllowedNumericType; /// <https://drafts.csswg.org/css-transitions/#animtype-lpcalc> impl Animate for LengthPercentage { @@ -26,10 +27,13 @@ impl Animate for LengthPercentage { .animate(&other.unclamped_length(), procedure)?; let percentage = animate_percentage_half(self.specified_percentage(), other.specified_percentage())?; - Ok(Self::with_clamping_mode( + + // Gets clamped as needed after the animation if needed, so no need to + // specify any particular AllowedNumericType. + Ok(LengthPercentage::new_calc( length, percentage, - self.clamping_mode, + AllowedNumericType::All, )) } } diff --git a/components/style/values/animated/mod.rs b/components/style/values/animated/mod.rs index ea43d01112e..7e699542fd4 100644 --- a/components/style/values/animated/mod.rs +++ b/components/style/values/animated/mod.rs @@ -107,7 +107,7 @@ pub fn animate_multiplicative_factor( /// be equal or an error is returned. /// /// If a variant is annotated with `#[animation(error)]`, the corresponding -/// `match` arm is not generated. +/// `match` arm returns an error. /// /// If the two values are not similar, an error is returned unless a fallback /// function has been specified through `#[animate(fallback)]`. @@ -424,6 +424,16 @@ impl ToAnimatedZero for i32 { } } +impl<T> ToAnimatedZero for Box<T> +where + T: ToAnimatedZero, +{ + #[inline] + fn to_animated_zero(&self) -> Result<Self, ()> { + Ok(Box::new((**self).to_animated_zero()?)) + } +} + impl<T> ToAnimatedZero for Option<T> where T: ToAnimatedZero, diff --git a/components/style/values/animated/transform.rs b/components/style/values/animated/transform.rs index 3d93ae93f6d..621ae60084d 100644 --- a/components/style/values/animated/transform.rs +++ b/components/style/values/animated/transform.rs @@ -1168,10 +1168,11 @@ impl ComputeSquaredDistance for ComputedTransformOperation { // However, dropping percentage makes us impossible to compute // the distance for the percentage-percentage case, but Gecko // uses the same formula, so it's fine for now. - let fx = fx.length_component().px(); - let fy = fy.length_component().px(); - let tx = tx.length_component().px(); - let ty = ty.length_component().px(); + let basis = Length::new(0.); + let fx = fx.resolve(basis).px(); + let fy = fy.resolve(basis).px(); + let tx = tx.resolve(basis).px(); + let ty = ty.resolve(basis).px(); Ok(fx.compute_squared_distance(&tx)? + fy.compute_squared_distance(&ty)? + diff --git a/components/style/values/computed/align.rs b/components/style/values/computed/align.rs index ea6088db5d5..45d6cfac323 100644 --- a/components/style/values/computed/align.rs +++ b/components/style/values/computed/align.rs @@ -9,7 +9,9 @@ use crate::values::computed::{Context, ToComputedValue}; use crate::values::specified; -pub use super::specified::{AlignContent, AlignItems, JustifyContent, SelfAlignment}; +pub use super::specified::{ + AlignContent, AlignItems, ContentDistribution, JustifyContent, SelfAlignment, +}; pub use super::specified::{AlignSelf, JustifySelf}; /// The computed value for the `justify-items` property. @@ -34,7 +36,8 @@ pub use super::specified::{AlignSelf, JustifySelf}; /// /// See the discussion in https://bugzil.la/1384542. #[derive(Clone, Copy, Debug, Eq, PartialEq, ToCss, ToResolvedValue)] -pub struct JustifyItems { +#[repr(C)] +pub struct ComputedJustifyItems { /// The specified value for the property. Can contain the bare `legacy` /// keyword. #[css(skip)] @@ -45,6 +48,8 @@ pub struct JustifyItems { pub computed: specified::JustifyItems, } +pub use self::ComputedJustifyItems as JustifyItems; + impl JustifyItems { /// Returns the `legacy` value. pub fn legacy() -> Self { diff --git a/components/style/values/computed/counters.rs b/components/style/values/computed/counters.rs index 3a083632eb9..40cfe46efa6 100644 --- a/components/style/values/computed/counters.rs +++ b/components/style/values/computed/counters.rs @@ -16,7 +16,7 @@ pub type CounterIncrement = GenericCounterIncrement<i32>; pub type CounterSetOrReset = GenericCounterSetOrReset<i32>; /// A computed value for the `content` property. -pub type Content = generics::Content<ComputedImageUrl>; +pub type Content = generics::GenericContent<ComputedImageUrl>; /// A computed content item. -pub type ContentItem = generics::ContentItem<ComputedImageUrl>; +pub type ContentItem = generics::GenericContentItem<ComputedImageUrl>; diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index 485ef954d8e..eedd05d1f15 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -4,27 +4,24 @@ //! `<length>` computed values, and related ones. -use super::{Context, Number, Percentage, ToComputedValue}; +use super::{Context, Number, ToComputedValue}; use crate::values::animated::ToAnimatedValue; use crate::values::computed::NonNegativeNumber; -use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; use crate::values::generics::length as generics; use crate::values::generics::length::{ GenericLengthOrNumber, GenericLengthPercentageOrNormal, GenericMaxSize, GenericSize, }; use crate::values::generics::NonNegative; -use crate::values::specified::length::ViewportPercentageLength; -use crate::values::specified::length::{AbsoluteLength, FontBaseSize, FontRelativeLength}; +use crate::values::specified::length::{AbsoluteLength, FontBaseSize}; use crate::values::{specified, CSSFloat}; use crate::Zero; use app_units::Au; -use ordered_float::NotNan; use std::fmt::{self, Write}; use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub}; -use style_traits::values::specified::AllowedNumericType; use style_traits::{CSSPixel, CssWriter, ToCss}; pub use super::image::Image; +pub use super::length_percentage::{LengthPercentage, NonNegativeLengthPercentage}; pub use crate::values::specified::url::UrlOrNone; pub use crate::values::specified::{Angle, BorderStyle, Time}; @@ -54,13 +51,15 @@ impl ToComputedValue for specified::NoCalcLength { } impl ToComputedValue for specified::Length { - type ComputedValue = CSSPixelLength; + type ComputedValue = Length; #[inline] fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { match *self { specified::Length::NoCalc(l) => l.to_computed_value(context), - specified::Length::Calc(ref calc) => calc.to_computed_value(context).length(), + specified::Length::Calc(ref calc) => { + calc.to_computed_value(context).to_length().unwrap() + }, } } @@ -70,382 +69,6 @@ impl ToComputedValue for specified::Length { } } -/// A `<length-percentage>` value. This can be either a `<length>`, a -/// `<percentage>`, or a combination of both via `calc()`. -/// -/// https://drafts.csswg.org/css-values-4/#typedef-length-percentage -#[allow(missing_docs)] -#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize, ToAnimatedZero, ToResolvedValue)] -#[repr(C)] -pub struct LengthPercentage { - length: Length, - percentage: Percentage, - #[animation(constant)] - pub clamping_mode: AllowedNumericType, - /// Whether we specified a percentage or not. - #[animation(constant)] - pub has_percentage: bool, -} - -// NOTE(emilio): We don't compare `clamping_mode` since we want to preserve the -// invariant that `from_computed_value(length).to_computed_value(..) == length`. -// -// Right now for e.g. a non-negative length, we set clamping_mode to `All` -// unconditionally for non-calc values, and to `NonNegative` for calc. -// -// If we determine that it's sound, from_computed_value() can generate an -// absolute length, which then would get `All` as the clamping mode. -// -// We may want to just eagerly-detect whether we can clamp in -// `LengthPercentage::new` and switch to `AllowedNumericType::NonNegative` then, -// maybe. -impl PartialEq for LengthPercentage { - fn eq(&self, other: &Self) -> bool { - self.length == other.length && - self.percentage == other.percentage && - self.has_percentage == other.has_percentage - } -} - -impl ComputeSquaredDistance for LengthPercentage { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - // FIXME(nox): This looks incorrect to me, to add a distance between lengths - // with a distance between percentages. - Ok(self - .unclamped_length() - .compute_squared_distance(&other.unclamped_length())? + - self.percentage - .compute_squared_distance(&other.percentage)?) - } -} - -impl LengthPercentage { - /// Returns a new `LengthPercentage`. - #[inline] - pub fn new(length: Length, percentage: Option<Percentage>) -> Self { - Self::with_clamping_mode(length, percentage, AllowedNumericType::All) - } - - /// Returns a new `LengthPercentage` with zero length and some percentage. - pub fn new_percent(percentage: Percentage) -> Self { - Self::new(Length::zero(), Some(percentage)) - } - - /// Returns a new `LengthPercentage` with a specific clamping mode. - #[inline] - pub fn with_clamping_mode( - length: Length, - percentage: Option<Percentage>, - clamping_mode: AllowedNumericType, - ) -> Self { - Self { - clamping_mode, - length, - percentage: percentage.unwrap_or_default(), - has_percentage: percentage.is_some(), - } - } - - /// Returns this `calc()` as a `<length>`. - /// - /// Panics in debug mode if a percentage is present in the expression. - #[inline] - pub fn length(&self) -> CSSPixelLength { - debug_assert!(!self.has_percentage); - self.length_component() - } - - /// Returns the length component of this `calc()` - #[inline] - pub fn length_component(&self) -> CSSPixelLength { - CSSPixelLength::new(self.clamping_mode.clamp(self.length.px())) - } - - /// Returns the `<length>` component of this `calc()`, unclamped. - #[inline] - pub fn unclamped_length(&self) -> CSSPixelLength { - self.length - } - - /// Returns the percentage component of this `calc()` - #[inline] - pub fn percentage_component(&self) -> Percentage { - Percentage(self.clamping_mode.clamp(self.percentage.0)) - } - - /// Return the percentage value as CSSFloat. - #[inline] - pub fn percentage(&self) -> CSSFloat { - self.percentage.0 - } - - /// Return the specified percentage if any. - #[inline] - pub fn specified_percentage(&self) -> Option<Percentage> { - if self.has_percentage { - Some(self.percentage) - } else { - None - } - } - - /// Returns the length component if this could be represented as a - /// non-calc length. - pub fn as_length(&self) -> Option<Length> { - if !self.has_percentage { - Some(self.length_component()) - } else { - None - } - } - - /// Returns the percentage component if this could be represented as a - /// non-calc percentage. - pub fn as_percentage(&self) -> Option<Percentage> { - if !self.has_percentage || self.length.px() != 0. { - return None; - } - - Some(Percentage(self.clamping_mode.clamp(self.percentage.0))) - } - - /// Resolves the percentage. - #[inline] - pub fn percentage_relative_to(&self, basis: Length) -> Length { - let length = self.unclamped_length().0 + basis.0 * self.percentage.0; - Length::new(self.clamping_mode.clamp(length)) - } - - /// Convert the computed value into used value. - #[inline] - pub fn maybe_to_used_value(&self, container_len: Option<Length>) -> Option<Au> { - self.maybe_percentage_relative_to(container_len) - .map(Au::from) - } - - /// If there are special rules for computing percentages in a value (e.g. - /// the height property), they apply whenever a calc() expression contains - /// percentages. - pub fn maybe_percentage_relative_to(&self, container_len: Option<Length>) -> Option<Length> { - if self.has_percentage { - return Some(self.percentage_relative_to(container_len?)); - } - Some(self.length()) - } -} - -impl ToCss for LengthPercentage { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - specified::LengthPercentage::from_computed_value(self).to_css(dest) - } -} - -impl specified::CalcLengthPercentage { - /// Compute the value, zooming any absolute units by the zoom function. - fn to_computed_value_with_zoom<F>( - &self, - context: &Context, - zoom_fn: F, - base_size: FontBaseSize, - ) -> LengthPercentage - where - F: Fn(Length) -> Length, - { - use std::f32; - let mut length = 0.; - - if let Some(absolute) = self.absolute { - length += zoom_fn(absolute.to_computed_value(context)).px(); - } - - for val in &[ - self.vw.map(ViewportPercentageLength::Vw), - self.vh.map(ViewportPercentageLength::Vh), - self.vmin.map(ViewportPercentageLength::Vmin), - self.vmax.map(ViewportPercentageLength::Vmax), - ] { - if let Some(val) = *val { - let viewport_size = context.viewport_size_for_viewport_unit_resolution(); - length += val.to_computed_value(viewport_size).px(); - } - } - - for val in &[ - self.ch.map(FontRelativeLength::Ch), - self.em.map(FontRelativeLength::Em), - self.ex.map(FontRelativeLength::Ex), - self.rem.map(FontRelativeLength::Rem), - ] { - if let Some(val) = *val { - length += val.to_computed_value(context, base_size).px(); - } - } - - LengthPercentage::with_clamping_mode( - Length::new(length.min(f32::MAX).max(f32::MIN)), - self.percentage, - self.clamping_mode, - ) - } - - /// Compute font-size or line-height taking into account text-zoom if necessary. - pub fn to_computed_value_zoomed( - &self, - context: &Context, - base_size: FontBaseSize, - ) -> LengthPercentage { - self.to_computed_value_with_zoom( - context, - |abs| context.maybe_zoom_text(abs.into()), - base_size, - ) - } - - /// Compute the value into pixel length as CSSFloat without context, - /// so it returns Err(()) if there is any non-absolute unit. - pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> { - if self.vw.is_some() || - self.vh.is_some() || - self.vmin.is_some() || - self.vmax.is_some() || - self.em.is_some() || - self.ex.is_some() || - self.ch.is_some() || - self.rem.is_some() || - self.percentage.is_some() - { - return Err(()); - } - - match self.absolute { - Some(abs) => Ok(abs.to_px()), - None => { - debug_assert!(false, "Someone forgot to handle an unit here: {:?}", self); - Err(()) - }, - } - } -} - -impl ToComputedValue for specified::CalcLengthPercentage { - type ComputedValue = LengthPercentage; - - fn to_computed_value(&self, context: &Context) -> LengthPercentage { - // normal properties don't zoom, and compute em units against the current style's font-size - self.to_computed_value_with_zoom(context, |abs| abs, FontBaseSize::CurrentStyle) - } - - #[inline] - fn from_computed_value(computed: &LengthPercentage) -> Self { - specified::CalcLengthPercentage { - clamping_mode: computed.clamping_mode, - absolute: Some(AbsoluteLength::from_computed_value(&computed.length)), - percentage: computed.specified_percentage(), - ..Default::default() - } - } -} - -impl LengthPercentage { - /// 1px length value for SVG defaults - #[inline] - pub fn one() -> LengthPercentage { - LengthPercentage::new(Length::new(1.), None) - } - - /// Returns true if the computed value is absolute 0 or 0%. - #[inline] - pub fn is_definitely_zero(&self) -> bool { - self.unclamped_length().px() == 0.0 && self.percentage.0 == 0.0 - } - - // CSSFloat doesn't implement Hash, so does CSSPixelLength. Therefore, we - // still use Au as the hash key. - #[allow(missing_docs)] - pub fn to_hash_key(&self) -> (Au, NotNan<f32>) { - ( - Au::from(self.unclamped_length()), - NotNan::new(self.percentage.0).unwrap(), - ) - } - - /// Returns the used value. - pub fn to_used_value(&self, containing_length: Au) -> Au { - Au::from(self.to_pixel_length(containing_length)) - } - - /// Returns the used value as CSSPixelLength. - pub fn to_pixel_length(&self, containing_length: Au) -> Length { - self.percentage_relative_to(containing_length.into()) - } - - /// Returns the clamped non-negative values. - #[inline] - pub fn clamp_to_non_negative(self) -> Self { - if let Some(p) = self.specified_percentage() { - // If we can eagerly clamp the percentage then just do that. - if self.length.is_zero() { - return Self::with_clamping_mode( - Length::zero(), - Some(p.clamp_to_non_negative()), - AllowedNumericType::NonNegative, - ); - } - - return Self::with_clamping_mode(self.length, Some(p), AllowedNumericType::NonNegative); - } - - Self::with_clamping_mode( - self.length.clamp_to_non_negative(), - None, - AllowedNumericType::NonNegative, - ) - } -} - -impl ToComputedValue for specified::LengthPercentage { - type ComputedValue = LengthPercentage; - - fn to_computed_value(&self, context: &Context) -> LengthPercentage { - match *self { - specified::LengthPercentage::Length(ref value) => { - LengthPercentage::new(value.to_computed_value(context), None) - }, - specified::LengthPercentage::Percentage(value) => LengthPercentage::new_percent(value), - specified::LengthPercentage::Calc(ref calc) => (**calc).to_computed_value(context), - } - } - - fn from_computed_value(computed: &LengthPercentage) -> Self { - if let Some(p) = computed.as_percentage() { - return specified::LengthPercentage::Percentage(p); - } - - if !computed.has_percentage { - return specified::LengthPercentage::Length(ToComputedValue::from_computed_value( - &computed.length(), - )); - } - - specified::LengthPercentage::Calc(Box::new(ToComputedValue::from_computed_value(computed))) - } -} - -impl Zero for LengthPercentage { - fn zero() -> Self { - LengthPercentage::new(Length::zero(), None) - } - - #[inline] - fn is_zero(&self) -> bool { - self.is_definitely_zero() - } -} - /// Some boilerplate to share between negative and non-negative /// length-percentage or auto. macro_rules! computed_length_percentage_or_auto { @@ -521,70 +144,6 @@ impl NonNegativeLengthPercentageOrAuto { computed_length_percentage_or_auto!(NonNegativeLengthPercentage); } -/// A wrapper of LengthPercentage, whose value must be >= 0. -pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>; - -impl ToAnimatedValue for NonNegativeLengthPercentage { - type AnimatedValue = LengthPercentage; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.0 - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - NonNegative(animated.clamp_to_non_negative()) - } -} - -impl From<NonNegativeLength> for NonNegativeLengthPercentage { - #[inline] - fn from(length: NonNegativeLength) -> Self { - NonNegative(LengthPercentage::new(length.0, None)) - } -} - -impl From<LengthPercentage> for NonNegativeLengthPercentage { - #[inline] - fn from(lp: LengthPercentage) -> Self { - NonNegative(lp) - } -} - -// TODO(emilio): This is a really generic impl which is only needed to implement -// Animated and co for Spacing<>. Get rid of this, probably? -impl From<Au> for LengthPercentage { - #[inline] - fn from(length: Au) -> Self { - LengthPercentage::new(length.into(), None) - } -} - -impl NonNegativeLengthPercentage { - /// Returns true if the computed value is absolute 0 or 0%. - #[inline] - pub fn is_definitely_zero(&self) -> bool { - self.0.is_definitely_zero() - } - - /// Returns the used value. - #[inline] - pub fn to_used_value(&self, containing_length: Au) -> Au { - let resolved = self.0.to_used_value(containing_length); - ::std::cmp::max(resolved, Au(0)) - } - - /// Convert the computed value into used value. - #[inline] - pub fn maybe_to_used_value(&self, containing_length: Option<Au>) -> Option<Au> { - let resolved = self - .0 - .maybe_to_used_value(containing_length.map(|v| v.into()))?; - Some(::std::cmp::max(resolved, Au(0))) - } -} - #[cfg(feature = "servo")] impl MaxSize { /// Convert the computed value into used value. diff --git a/components/style/values/computed/length_percentage.rs b/components/style/values/computed/length_percentage.rs new file mode 100644 index 00000000000..96bf76c98d4 --- /dev/null +++ b/components/style/values/computed/length_percentage.rs @@ -0,0 +1,793 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! `<length-percentage>` computed values, and related ones. +//! +//! The over-all design is a tagged pointer, with the lower bits of the pointer +//! being non-zero if it is a non-calc value. +//! +//! It is expected to take 64 bits both in x86 and x86-64. This is implemented +//! as a `union`, with 4 different variants: +//! +//! * The length and percentage variants have a { tag, f32 } (effectively) +//! layout. The tag has to overlap with the lower 2 bits of the calc variant. +//! +//! * The `calc()` variant is a { tag, pointer } in x86 (so same as the +//! others), or just a { pointer } in x86-64 (so that the two bits of the tag +//! can be obtained from the lower bits of the pointer). +//! +//! * There's a `tag` variant just to make clear when only the tag is intended +//! to be read. Note that the tag needs to be masked always by `TAG_MASK`, to +//! deal with the pointer variant in x86-64. +//! +//! The assertions in the constructor methods ensure that the tag getter matches +//! our expectations. + +use super::{Context, Length, Percentage, ToComputedValue}; +use crate::values::animated::{ToAnimatedValue, ToAnimatedZero}; +use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; +use crate::values::generics::NonNegative; +use crate::values::specified::length::FontBaseSize; +use crate::values::{specified, CSSFloat}; +use crate::Zero; +use app_units::Au; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Write}; +use style_traits::values::specified::AllowedNumericType; +use style_traits::{CssWriter, ToCss}; + +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +pub struct LengthVariant { + tag: u8, + length: Length, +} + +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +pub struct PercentageVariant { + tag: u8, + percentage: Percentage, +} + +// NOTE(emilio): cbindgen only understands the #[cfg] on the top level +// definition. +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +#[cfg(target_pointer_width = "32")] +pub struct CalcVariant { + tag: u8, + ptr: *mut CalcLengthPercentage, +} + +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +#[cfg(target_pointer_width = "64")] +pub struct CalcVariant { + ptr: usize, // In little-endian byte order +} + +// `CalcLengthPercentage` is `Send + Sync` as asserted below. +unsafe impl Send for CalcVariant {} +unsafe impl Sync for CalcVariant {} + +#[doc(hidden)] +#[derive(Clone, Copy)] +#[repr(C)] +pub struct TagVariant { + tag: u8, +} + +/// A `<length-percentage>` value. This can be either a `<length>`, a +/// `<percentage>`, or a combination of both via `calc()`. +/// +/// cbindgen:private-default-tagged-enum-constructor=false +/// cbindgen:derive-mut-casts=true +/// +/// https://drafts.csswg.org/css-values-4/#typedef-length-percentage +/// +/// The tag is stored in the lower two bits. +/// +/// We need to use a struct instead of the union directly because unions with +/// Drop implementations are unstable, looks like. +/// +/// Also we need the union and the variants to be `pub` (even though the member +/// is private) so that cbindgen generates it. They're not part of the public +/// API otherwise. +#[repr(transparent)] +pub struct LengthPercentage(LengthPercentageUnion); + +#[doc(hidden)] +#[repr(C)] +pub union LengthPercentageUnion { + length: LengthVariant, + percentage: PercentageVariant, + calc: CalcVariant, + tag: TagVariant, +} + +impl LengthPercentageUnion { + #[doc(hidden)] // Need to be public so that cbindgen generates it. + pub const TAG_CALC: u8 = 0; + #[doc(hidden)] + pub const TAG_LENGTH: u8 = 1; + #[doc(hidden)] + pub const TAG_PERCENTAGE: u8 = 2; + #[doc(hidden)] + pub const TAG_MASK: u8 = 0b11; +} + +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u8)] +enum Tag { + Calc = LengthPercentageUnion::TAG_CALC, + Length = LengthPercentageUnion::TAG_LENGTH, + Percentage = LengthPercentageUnion::TAG_PERCENTAGE, +} + +// All the members should be 64 bits, even in 32-bit builds. +#[allow(unused)] +unsafe fn static_assert() { + fn assert_send_and_sync<T: Send + Sync>() {} + std::mem::transmute::<u64, LengthVariant>(0u64); + std::mem::transmute::<u64, PercentageVariant>(0u64); + std::mem::transmute::<u64, CalcVariant>(0u64); + std::mem::transmute::<u64, LengthPercentage>(0u64); + assert_send_and_sync::<LengthVariant>(); + assert_send_and_sync::<PercentageVariant>(); + assert_send_and_sync::<CalcLengthPercentage>(); +} + +impl Drop for LengthPercentage { + fn drop(&mut self) { + if self.tag() == Tag::Calc { + let _ = unsafe { Box::from_raw(self.calc_ptr()) }; + } + } +} + +impl MallocSizeOf for LengthPercentage { + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + match self.unpack() { + Unpacked::Length(..) | Unpacked::Percentage(..) => 0, + Unpacked::Calc(c) => unsafe { ops.malloc_size_of(c) }, + } + } +} + +/// An unpacked `<length-percentage>` that borrows the `calc()` variant. +#[derive(Clone, Debug, PartialEq)] +enum Unpacked<'a> { + Calc(&'a CalcLengthPercentage), + Length(Length), + Percentage(Percentage), +} + +/// An unpacked `<length-percentage>` that owns the `calc()` variant, for +/// serialization purposes. +#[derive(Deserialize, PartialEq, Serialize)] +enum Serializable { + Calc(CalcLengthPercentage), + Length(Length), + Percentage(Percentage), +} + +impl LengthPercentage { + /// 1px length value for SVG defaults + #[inline] + pub fn one() -> Self { + Self::new_length(Length::new(1.)) + } + + /// Constructs a length value. + #[inline] + pub fn new_length(length: Length) -> Self { + let length = Self(LengthPercentageUnion { + length: LengthVariant { + tag: LengthPercentageUnion::TAG_LENGTH, + length, + }, + }); + debug_assert_eq!(length.tag(), Tag::Length); + length + } + + /// Constructs a percentage value. + #[inline] + pub fn new_percent(percentage: Percentage) -> Self { + let percent = Self(LengthPercentageUnion { + percentage: PercentageVariant { + tag: LengthPercentageUnion::TAG_PERCENTAGE, + percentage, + }, + }); + debug_assert_eq!(percent.tag(), Tag::Percentage); + percent + } + + /// Constructs a `calc()` value. + #[inline] + pub fn new_calc( + length: Length, + percentage: Option<Percentage>, + clamping_mode: AllowedNumericType, + ) -> Self { + let percentage = match percentage { + Some(p) => p, + None => return Self::new_length(Length::new(clamping_mode.clamp(length.px()))), + }; + if length.is_zero() { + return Self::new_percent(Percentage(clamping_mode.clamp(percentage.0))); + } + Self::new_calc_unchecked(Box::new(CalcLengthPercentage { + length, + percentage, + clamping_mode, + })) + } + + /// Private version of new_calc() that constructs a calc() variant without + /// checking. + fn new_calc_unchecked(calc: Box<CalcLengthPercentage>) -> Self { + let ptr = Box::into_raw(calc); + + #[cfg(target_pointer_width = "32")] + let calc = CalcVariant { + tag: LengthPercentageUnion::TAG_CALC, + ptr, + }; + + #[cfg(target_pointer_width = "64")] + let calc = CalcVariant { + #[cfg(target_endian = "little")] + ptr: ptr as usize, + #[cfg(target_endian = "big")] + ptr: (ptr as usize).swap_bytes(), + }; + + let calc = Self(LengthPercentageUnion { calc }); + debug_assert_eq!(calc.tag(), Tag::Calc); + calc + } + + #[inline] + fn tag(&self) -> Tag { + match unsafe { self.0.tag.tag & LengthPercentageUnion::TAG_MASK } { + LengthPercentageUnion::TAG_CALC => Tag::Calc, + LengthPercentageUnion::TAG_LENGTH => Tag::Length, + LengthPercentageUnion::TAG_PERCENTAGE => Tag::Percentage, + _ => unreachable!("Bogus tag?"), + } + } + + #[inline] + fn unpack<'a>(&'a self) -> Unpacked<'a> { + unsafe { + match self.tag() { + Tag::Calc => Unpacked::Calc(&*self.calc_ptr()), + Tag::Length => Unpacked::Length(self.0.length.length), + Tag::Percentage => Unpacked::Percentage(self.0.percentage.percentage), + } + } + } + + #[inline] + unsafe fn calc_ptr(&self) -> *mut CalcLengthPercentage { + #[cfg(not(all(target_endian = "big", target_pointer_width = "64")))] + { + self.0.calc.ptr as *mut _ + } + #[cfg(all(target_endian = "big", target_pointer_width = "64"))] + { + self.0.calc.ptr.swap_bytes() as *mut _ + } + } + + #[inline] + fn to_serializable(&self) -> Serializable { + match self.unpack() { + Unpacked::Calc(c) => Serializable::Calc(c.clone()), + Unpacked::Length(l) => Serializable::Length(l), + Unpacked::Percentage(p) => Serializable::Percentage(p), + } + } + + #[inline] + fn from_serializable(s: Serializable) -> Self { + match s { + Serializable::Calc(c) => Self::new_calc_unchecked(Box::new(c)), + Serializable::Length(l) => Self::new_length(l), + Serializable::Percentage(p) => Self::new_percent(p), + } + } + + /// Returns true if the computed value is absolute 0 or 0%. + #[inline] + pub fn is_definitely_zero(&self) -> bool { + match self.unpack() { + Unpacked::Length(l) => l.px() == 0.0, + Unpacked::Percentage(p) => p.0 == 0.0, + Unpacked::Calc(ref c) => { + debug_assert_ne!( + c.length.px(), + 0.0, + "Should've been simplified to a percentage" + ); + false + }, + } + } + + /// Returns the `<length>` component of this `calc()`, unclamped. + #[inline] + pub fn unclamped_length(&self) -> Length { + match self.unpack() { + Unpacked::Length(l) => l, + Unpacked::Percentage(..) => Zero::zero(), + Unpacked::Calc(c) => c.unclamped_length(), + } + } + + /// Returns this `calc()` as a `<length>`. + /// + /// Panics in debug mode if a percentage is present in the expression. + #[inline] + fn length(&self) -> Length { + debug_assert!(!self.has_percentage()); + self.length_component() + } + + /// Returns the `<length>` component of this `calc()`, clamped. + #[inline] + pub fn length_component(&self) -> Length { + match self.unpack() { + Unpacked::Length(l) => l, + Unpacked::Percentage(..) => Zero::zero(), + Unpacked::Calc(c) => c.length_component(), + } + } + + /// Returns the `<percentage>` component of this `calc()`, unclamped, as a + /// float. + /// + /// FIXME: This are very different semantics from length(), we should + /// probably rename this. + #[inline] + pub fn percentage(&self) -> CSSFloat { + match self.unpack() { + Unpacked::Length(..) => 0., + Unpacked::Percentage(p) => p.0, + Unpacked::Calc(c) => c.percentage.0, + } + } + + /// Resolves the percentage. + #[inline] + pub fn resolve(&self, basis: Length) -> Length { + match self.unpack() { + Unpacked::Length(l) => l, + Unpacked::Percentage(p) => Length::new(basis.px() * p.0), + Unpacked::Calc(ref c) => c.resolve(basis), + } + } + + /// Resolves the percentage. Just an alias of resolve(). + #[inline] + pub fn percentage_relative_to(&self, basis: Length) -> Length { + self.resolve(basis) + } + + /// Return whether there's any percentage in this value. + #[inline] + pub fn has_percentage(&self) -> bool { + match self.unpack() { + Unpacked::Length(..) => false, + Unpacked::Percentage(..) | Unpacked::Calc(..) => true, + } + } + + /// Converts to a `<length>` if possible. + pub fn to_length(&self) -> Option<Length> { + match self.unpack() { + Unpacked::Length(l) => Some(l), + Unpacked::Percentage(..) | Unpacked::Calc(..) => { + debug_assert!(self.has_percentage()); + return None; + }, + } + } + + /// Converts to a `<percentage>` if possible. + #[inline] + pub fn to_percentage(&self) -> Option<Percentage> { + match self.unpack() { + Unpacked::Length(..) => None, + Unpacked::Percentage(p) => Some(p), + Unpacked::Calc(ref c) => { + debug_assert!(!c.length.is_zero()); + None + }, + } + } + + /// Return the specified percentage if any. + #[inline] + pub fn specified_percentage(&self) -> Option<Percentage> { + match self.unpack() { + Unpacked::Length(..) => None, + Unpacked::Percentage(p) => Some(p), + Unpacked::Calc(ref c) => { + debug_assert!(self.has_percentage()); + Some(c.percentage) + }, + } + } + + /// Returns the used value. + #[inline] + pub fn to_used_value(&self, containing_length: Au) -> Au { + Au::from(self.to_pixel_length(containing_length)) + } + + /// Returns the used value as CSSPixelLength. + #[inline] + pub fn to_pixel_length(&self, containing_length: Au) -> Length { + self.resolve(containing_length.into()) + } + + /// Convert the computed value into used value. + #[inline] + pub fn maybe_to_used_value(&self, container_len: Option<Length>) -> Option<Au> { + self.maybe_percentage_relative_to(container_len) + .map(Au::from) + } + + /// If there are special rules for computing percentages in a value (e.g. + /// the height property), they apply whenever a calc() expression contains + /// percentages. + pub fn maybe_percentage_relative_to(&self, container_len: Option<Length>) -> Option<Length> { + if self.has_percentage() { + return Some(self.resolve(container_len?)); + } + Some(self.length()) + } + + /// Returns the clamped non-negative values. + #[inline] + pub fn clamp_to_non_negative(&self) -> Self { + match self.unpack() { + Unpacked::Length(l) => Self::new_length(l.clamp_to_non_negative()), + Unpacked::Percentage(p) => Self::new_percent(p.clamp_to_non_negative()), + Unpacked::Calc(c) => c.clamp_to_non_negative(), + } + } +} + +impl PartialEq for LengthPercentage { + fn eq(&self, other: &Self) -> bool { + self.unpack() == other.unpack() + } +} + +impl fmt::Debug for LengthPercentage { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + self.unpack().fmt(formatter) + } +} + +impl ToAnimatedZero for LengthPercentage { + fn to_animated_zero(&self) -> Result<Self, ()> { + Ok(match self.unpack() { + Unpacked::Length(l) => Self::new_length(l.to_animated_zero()?), + Unpacked::Percentage(p) => Self::new_percent(p.to_animated_zero()?), + Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.to_animated_zero()?)), + }) + } +} + +impl Clone for LengthPercentage { + fn clone(&self) -> Self { + match self.unpack() { + Unpacked::Length(l) => Self::new_length(l), + Unpacked::Percentage(p) => Self::new_percent(p), + Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.clone())), + } + } +} + +impl ToComputedValue for specified::LengthPercentage { + type ComputedValue = LengthPercentage; + + fn to_computed_value(&self, context: &Context) -> LengthPercentage { + match *self { + specified::LengthPercentage::Length(ref value) => { + LengthPercentage::new_length(value.to_computed_value(context)) + }, + specified::LengthPercentage::Percentage(value) => LengthPercentage::new_percent(value), + specified::LengthPercentage::Calc(ref calc) => (**calc).to_computed_value(context), + } + } + + fn from_computed_value(computed: &LengthPercentage) -> Self { + match computed.unpack() { + Unpacked::Length(ref l) => { + specified::LengthPercentage::Length(ToComputedValue::from_computed_value(l)) + }, + Unpacked::Percentage(p) => specified::LengthPercentage::Percentage(p), + Unpacked::Calc(c) => { + // We simplify before constructing the LengthPercentage if + // needed, so this is always fine. + specified::LengthPercentage::Calc(Box::new( + specified::CalcLengthPercentage::from_computed_value(c), + )) + }, + } + } +} + +impl ComputeSquaredDistance for LengthPercentage { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + // A somewhat arbitrary base, it doesn't really make sense to mix + // lengths with percentages, but we can't do much better here, and this + // ensures that the distance between length-only and percentage-only + // lengths makes sense. + let basis = Length::new(100.); + self.resolve(basis) + .compute_squared_distance(&other.resolve(basis)) + } +} + +impl ToCss for LengthPercentage { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + specified::LengthPercentage::from_computed_value(self).to_css(dest) + } +} + +impl Zero for LengthPercentage { + fn zero() -> Self { + LengthPercentage::new_length(Length::zero()) + } + + #[inline] + fn is_zero(&self) -> bool { + self.is_definitely_zero() + } +} + +impl Serialize for LengthPercentage { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + self.to_serializable().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for LengthPercentage { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + Ok(Self::from_serializable(Serializable::deserialize( + deserializer, + )?)) + } +} + +/// The representation of a calc() function with mixed lengths and percentages. +#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize, ToAnimatedZero, ToResolvedValue)] +#[repr(C)] +pub struct CalcLengthPercentage { + length: Length, + + percentage: Percentage, + + #[animation(constant)] + clamping_mode: AllowedNumericType, +} + +impl CalcLengthPercentage { + /// Returns the length component of this `calc()`, clamped. + #[inline] + fn length_component(&self) -> Length { + Length::new(self.clamping_mode.clamp(self.length.px())) + } + + /// Resolves the percentage. + #[inline] + pub fn resolve(&self, basis: Length) -> Length { + let length = self.length.px() + basis.px() * self.percentage.0; + Length::new(self.clamping_mode.clamp(length)) + } + + /// Returns the length, without clamping. + #[inline] + fn unclamped_length(&self) -> Length { + self.length + } + + /// Returns the clamped non-negative values. + #[inline] + fn clamp_to_non_negative(&self) -> LengthPercentage { + LengthPercentage::new_calc( + self.length, + Some(self.percentage), + AllowedNumericType::NonNegative, + ) + } +} + +// NOTE(emilio): We don't compare `clamping_mode` since we want to preserve the +// invariant that `from_computed_value(length).to_computed_value(..) == length`. +// +// Right now for e.g. a non-negative length, we set clamping_mode to `All` +// unconditionally for non-calc values, and to `NonNegative` for calc. +// +// If we determine that it's sound, from_computed_value() can generate an +// absolute length, which then would get `All` as the clamping mode. +// +// We may want to just eagerly-detect whether we can clamp in +// `LengthPercentage::new` and switch to `AllowedNumericType::NonNegative` then, +// maybe. +impl PartialEq for CalcLengthPercentage { + fn eq(&self, other: &Self) -> bool { + self.length == other.length && self.percentage == other.percentage + } +} + +impl specified::CalcLengthPercentage { + /// Compute the value, zooming any absolute units by the zoom function. + fn to_computed_value_with_zoom<F>( + &self, + context: &Context, + zoom_fn: F, + base_size: FontBaseSize, + ) -> LengthPercentage + where + F: Fn(Length) -> Length, + { + use crate::values::specified::length::{FontRelativeLength, ViewportPercentageLength}; + use std::f32; + + let mut length = 0.; + + if let Some(absolute) = self.absolute { + length += zoom_fn(absolute.to_computed_value(context)).px(); + } + + for val in &[ + self.vw.map(ViewportPercentageLength::Vw), + self.vh.map(ViewportPercentageLength::Vh), + self.vmin.map(ViewportPercentageLength::Vmin), + self.vmax.map(ViewportPercentageLength::Vmax), + ] { + if let Some(val) = *val { + let viewport_size = context.viewport_size_for_viewport_unit_resolution(); + length += val.to_computed_value(viewport_size).px(); + } + } + + for val in &[ + self.ch.map(FontRelativeLength::Ch), + self.em.map(FontRelativeLength::Em), + self.ex.map(FontRelativeLength::Ex), + self.rem.map(FontRelativeLength::Rem), + ] { + if let Some(val) = *val { + length += val.to_computed_value(context, base_size).px(); + } + } + + LengthPercentage::new_calc( + Length::new(length.min(f32::MAX).max(f32::MIN)), + self.percentage, + self.clamping_mode, + ) + } + + /// Compute font-size or line-height taking into account text-zoom if necessary. + pub fn to_computed_value_zoomed( + &self, + context: &Context, + base_size: FontBaseSize, + ) -> LengthPercentage { + self.to_computed_value_with_zoom( + context, + |abs| context.maybe_zoom_text(abs.into()), + base_size, + ) + } + + /// Compute the value into pixel length as CSSFloat without context, + /// so it returns Err(()) if there is any non-absolute unit. + pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> { + if self.vw.is_some() || + self.vh.is_some() || + self.vmin.is_some() || + self.vmax.is_some() || + self.em.is_some() || + self.ex.is_some() || + self.ch.is_some() || + self.rem.is_some() || + self.percentage.is_some() + { + return Err(()); + } + + match self.absolute { + Some(abs) => Ok(abs.to_px()), + None => { + debug_assert!(false, "Someone forgot to handle an unit here: {:?}", self); + Err(()) + }, + } + } + + /// Compute the calc using the current font-size (and without text-zoom). + pub fn to_computed_value(&self, context: &Context) -> LengthPercentage { + self.to_computed_value_with_zoom(context, |abs| abs, FontBaseSize::CurrentStyle) + } + + #[inline] + fn from_computed_value(computed: &CalcLengthPercentage) -> Self { + use crate::values::specified::length::AbsoluteLength; + + specified::CalcLengthPercentage { + clamping_mode: computed.clamping_mode, + absolute: Some(AbsoluteLength::from_computed_value(&computed.length)), + percentage: Some(computed.percentage), + ..Default::default() + } + } +} + +/// A wrapper of LengthPercentage, whose value must be >= 0. +pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>; + +impl ToAnimatedValue for NonNegativeLengthPercentage { + type AnimatedValue = LengthPercentage; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + self.0 + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + NonNegative(animated.clamp_to_non_negative()) + } +} + +impl NonNegativeLengthPercentage { + /// Returns true if the computed value is absolute 0 or 0%. + #[inline] + pub fn is_definitely_zero(&self) -> bool { + self.0.is_definitely_zero() + } + + /// Returns the used value. + #[inline] + pub fn to_used_value(&self, containing_length: Au) -> Au { + let resolved = self.0.to_used_value(containing_length); + std::cmp::max(resolved, Au(0)) + } + + /// Convert the computed value into used value. + #[inline] + pub fn maybe_to_used_value(&self, containing_length: Option<Au>) -> Option<Au> { + let resolved = self + .0 + .maybe_to_used_value(containing_length.map(|v| v.into()))?; + Some(std::cmp::max(resolved, Au(0))) + } +} diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index f5b3440f426..7cfd1e20abe 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -108,6 +108,7 @@ pub mod flex; pub mod font; pub mod image; pub mod length; +pub mod length_percentage; pub mod list; pub mod motion; pub mod outline; @@ -125,9 +126,6 @@ pub mod url; /// A `Context` is all the data a specified value could ever need to compute /// itself and be transformed to a computed value. pub struct Context<'a> { - /// Whether the current element is the root element. - pub is_root_element: bool, - /// Values accessed through this need to be in the properties "computed /// early": color, text-decoration, font-size, display, position, float, /// border-*-style, outline-style, font-family, writing-mode... @@ -186,7 +184,6 @@ impl<'a> Context<'a> { let provider = get_metrics_provider_for_product(); let context = Context { - is_root_element: false, builder: StyleBuilder::for_inheritance(device, None, None), font_metrics_provider: &provider, cached_system_font: None, @@ -200,11 +197,6 @@ impl<'a> Context<'a> { f(&context) } - /// Whether the current element is the root element. - pub fn is_root_element(&self) -> bool { - self.is_root_element - } - /// The current device. pub fn device(&self) -> &Device { self.builder.device @@ -471,6 +463,53 @@ trivial_to_computed_value!(Atom); trivial_to_computed_value!(Prefix); trivial_to_computed_value!(String); trivial_to_computed_value!(Box<str>); +trivial_to_computed_value!(crate::OwnedStr); + +#[allow(missing_docs)] +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + ToAnimatedZero, + ToCss, + ToResolvedValue, +)] +#[repr(C, u8)] +pub enum AngleOrPercentage { + Percentage(Percentage), + Angle(Angle), +} + +impl ToComputedValue for specified::AngleOrPercentage { + type ComputedValue = AngleOrPercentage; + + #[inline] + fn to_computed_value(&self, context: &Context) -> AngleOrPercentage { + match *self { + specified::AngleOrPercentage::Percentage(percentage) => { + AngleOrPercentage::Percentage(percentage.to_computed_value(context)) + }, + specified::AngleOrPercentage::Angle(angle) => { + AngleOrPercentage::Angle(angle.to_computed_value(context)) + }, + } + } + #[inline] + fn from_computed_value(computed: &AngleOrPercentage) -> Self { + match *computed { + AngleOrPercentage::Percentage(percentage) => specified::AngleOrPercentage::Percentage( + ToComputedValue::from_computed_value(&percentage), + ), + AngleOrPercentage::Angle(angle) => { + specified::AngleOrPercentage::Angle(ToComputedValue::from_computed_value(&angle)) + }, + } + } +} /// A `<number>` value. pub type Number = CSSFloat; diff --git a/components/style/values/computed/motion.rs b/components/style/values/computed/motion.rs index 88a5b7c7280..e2565ff3468 100644 --- a/components/style/values/computed/motion.rs +++ b/components/style/values/computed/motion.rs @@ -23,8 +23,10 @@ fn is_auto_zero_angle(auto: &bool, angle: &Angle) -> bool { ComputeSquaredDistance, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, ToAnimatedZero, ToCss, ToResolvedValue, diff --git a/components/style/values/computed/text.rs b/components/style/values/computed/text.rs index 3f9ffb2ad0f..0ca2e6044ed 100644 --- a/components/style/values/computed/text.rs +++ b/components/style/values/computed/text.rs @@ -27,8 +27,8 @@ pub use crate::values::specified::{TextDecorationSkipInk, TextTransform}; /// A computed value for the `initial-letter` property. pub type InitialLetter = GenericInitialLetter<CSSFloat, CSSInteger>; -/// 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>; /// A computed value for the `letter-spacing` property. #[repr(transparent)] diff --git a/components/style/values/distance.rs b/components/style/values/distance.rs index a1872366c2a..67c735676b5 100644 --- a/components/style/values/distance.rs +++ b/components/style/values/distance.rs @@ -17,7 +17,7 @@ use std::ops::Add; /// on each fields of the values. /// /// If a variant is annotated with `#[animation(error)]`, the corresponding -/// `match` arm is not generated. +/// `match` arm returns an error. /// /// If the two values are not similar, an error is returned unless a fallback /// function has been specified through `#[distance(fallback)]`. diff --git a/components/style/values/generics/counters.rs b/components/style/values/generics/counters.rs index 69893c75561..05e34703911 100644 --- a/components/style/values/generics/counters.rs +++ b/components/style/values/generics/counters.rs @@ -145,30 +145,27 @@ fn is_decimal(counter_type: &CounterStyleType) -> bool { /// /// https://drafts.csswg.org/css-content/#propdef-content #[derive( - Clone, - Debug, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, + Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToShmem, )] -pub enum Content<ImageUrl> { +#[repr(u8)] +pub enum GenericContent<ImageUrl> { /// `normal` reserved keyword. Normal, /// `none` reserved keyword. None, - /// `-moz-alt-content`. - #[cfg(feature = "gecko")] - MozAltContent, /// Content items. - Items(#[css(iterable)] Box<[ContentItem<ImageUrl>]>), + Items(#[css(iterable)] crate::OwnedSlice<GenericContentItem<ImageUrl>>), } +pub use self::GenericContent as Content; + impl<ImageUrl> Content<ImageUrl> { + /// Whether `self` represents list of items. + #[inline] + pub fn is_items(&self) -> bool { + matches!(*self, Self::Items(..)) + } + /// Set `content` property to `normal`. #[inline] pub fn normal() -> Self { @@ -189,9 +186,10 @@ impl<ImageUrl> Content<ImageUrl> { ToResolvedValue, ToShmem, )] -pub enum ContentItem<ImageUrl> { +#[repr(u8)] +pub enum GenericContentItem<ImageUrl> { /// Literal string content. - String(Box<str>), + String(crate::OwnedStr), /// `counter(name, style)`. #[css(comma, function)] Counter(CustomIdent, #[css(skip_if = "is_decimal")] CounterStyleType), @@ -199,7 +197,7 @@ pub enum ContentItem<ImageUrl> { #[css(comma, function)] Counters( CustomIdent, - Box<str>, + crate::OwnedStr, #[css(skip_if = "is_decimal")] CounterStyleType, ), /// `open-quote`. @@ -210,9 +208,14 @@ pub enum ContentItem<ImageUrl> { NoOpenQuote, /// `no-close-quote`. NoCloseQuote, + /// `-moz-alt-content`. + #[cfg(feature = "gecko")] + MozAltContent, /// `attr([namespace? `|`]? ident)` #[cfg(feature = "gecko")] Attr(Attr), /// `url(url)` Url(ImageUrl), } + +pub use self::GenericContentItem as ContentItem; diff --git a/components/style/values/generics/mod.rs b/components/style/values/generics/mod.rs index 4db80abc8a8..a97d9e1018a 100644 --- a/components/style/values/generics/mod.rs +++ b/components/style/values/generics/mod.rs @@ -39,7 +39,7 @@ pub mod transform; pub mod ui; pub mod url; -// https://drafts.csswg.org/css-counter-styles/#typedef-symbols-type +/// https://drafts.csswg.org/css-counter-styles/#typedef-symbols-type #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( @@ -55,6 +55,7 @@ pub mod url; ToResolvedValue, ToShmem, )] +#[repr(u8)] pub enum SymbolsType { Cyclic, Numeric, @@ -63,39 +64,12 @@ pub enum SymbolsType { Fixed, } -#[cfg(feature = "gecko")] -impl SymbolsType { - /// Convert symbols type to their corresponding Gecko values. - pub fn to_gecko_keyword(self) -> u8 { - use crate::gecko_bindings::structs; - match self { - SymbolsType::Cyclic => structs::NS_STYLE_COUNTER_SYSTEM_CYCLIC as u8, - SymbolsType::Numeric => structs::NS_STYLE_COUNTER_SYSTEM_NUMERIC as u8, - SymbolsType::Alphabetic => structs::NS_STYLE_COUNTER_SYSTEM_ALPHABETIC as u8, - SymbolsType::Symbolic => structs::NS_STYLE_COUNTER_SYSTEM_SYMBOLIC as u8, - SymbolsType::Fixed => structs::NS_STYLE_COUNTER_SYSTEM_FIXED as u8, - } - } - - /// Convert Gecko value to symbol type. - pub fn from_gecko_keyword(gecko_value: u32) -> SymbolsType { - use crate::gecko_bindings::structs; - match gecko_value { - structs::NS_STYLE_COUNTER_SYSTEM_CYCLIC => SymbolsType::Cyclic, - structs::NS_STYLE_COUNTER_SYSTEM_NUMERIC => SymbolsType::Numeric, - structs::NS_STYLE_COUNTER_SYSTEM_ALPHABETIC => SymbolsType::Alphabetic, - structs::NS_STYLE_COUNTER_SYSTEM_SYMBOLIC => SymbolsType::Symbolic, - structs::NS_STYLE_COUNTER_SYSTEM_FIXED => SymbolsType::Fixed, - x => panic!("Unexpected value for symbol type {}", x), - } - } -} - /// <https://drafts.csswg.org/css-counter-styles/#typedef-counter-style> /// /// Note that 'none' is not a valid name. #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, Debug, Eq, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] +#[repr(u8)] pub enum CounterStyle { /// `<counter-style-name>` Name(CustomIdent), diff --git a/components/style/values/generics/motion.rs b/components/style/values/generics/motion.rs index 685eaad43e2..25ba5873adc 100644 --- a/components/style/values/generics/motion.rs +++ b/components/style/values/generics/motion.rs @@ -73,13 +73,16 @@ pub struct RayFunction<Angle> { /// The offset-path value. /// /// https://drafts.fxtf.org/motion-1/#offset-path-property +/// cbindgen:private-default-tagged-enum-constructor=false #[derive( Animate, Clone, ComputeSquaredDistance, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, diff --git a/components/style/values/generics/position.rs b/components/style/values/generics/position.rs index 2742dd87913..a3552ea3eab 100644 --- a/components/style/values/generics/position.rs +++ b/components/style/values/generics/position.rs @@ -12,8 +12,10 @@ ComputeSquaredDistance, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedValue, ToAnimatedZero, @@ -44,15 +46,18 @@ impl<H, V> Position<H, V> { /// A generic type for representing an `Auto | <position>`. /// This is used by <offset-anchor> for now. /// https://drafts.fxtf.org/motion-1/#offset-anchor-property +/// cbindgen:private-default-tagged-enum-constructor=false #[derive( Animate, Clone, ComputeSquaredDistance, Copy, Debug, + Deserialize, MallocSizeOf, Parse, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, diff --git a/components/style/values/generics/text.rs b/components/style/values/generics/text.rs index fd434cc1273..ceeb59a3d0c 100644 --- a/components/style/values/generics/text.rs +++ b/components/style/values/generics/text.rs @@ -123,8 +123,8 @@ impl<N, L> LineHeight<N, L> { } } -/// Implements type for text-underline-offset and text-decoration-thickness -/// which take the grammar of auto | from-font | <length> +/// Implements type for text-decoration-thickness +/// which takes the grammar of auto | from-font | <length> | <percentage> /// /// https://drafts.csswg.org/css-text-decor-4/ #[repr(C, u8)] @@ -148,7 +148,7 @@ impl<N, L> LineHeight<N, L> { )] #[allow(missing_docs)] pub enum GenericTextDecorationLength<L> { - Length(L), + LengthPercentage(L), Auto, FromFont, } diff --git a/components/style/values/generics/transform.rs b/components/style/values/generics/transform.rs index 3322323e76e..26e54ab8df4 100644 --- a/components/style/values/generics/transform.rs +++ b/components/style/values/generics/transform.rs @@ -23,8 +23,10 @@ use style_traits::{CssWriter, ToCss}; Clone, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToComputedValue, ToCss, @@ -51,8 +53,10 @@ pub use self::GenericMatrix as Matrix; Clone, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToComputedValue, ToCss, @@ -141,8 +145,10 @@ fn is_same<N: PartialEq>(x: &N, y: &N) -> bool { #[derive( Clone, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToComputedValue, ToCss, @@ -267,8 +273,10 @@ pub use self::GenericTransformOperation as TransformOperation; #[derive( Clone, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToComputedValue, ToCss, @@ -369,7 +377,7 @@ impl ToAbsoluteLength for ComputedLengthPercentage { // distance without any layout info. // // FIXME(emilio): This looks wrong. - None => Ok(self.length_component().px()), + None => Ok(self.resolve(Zero::zero()).px()), } } } @@ -611,8 +619,10 @@ pub fn get_normalized_vector_and_angle<T: Zero>( Clone, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, @@ -623,6 +633,7 @@ pub fn get_normalized_vector_and_angle<T: Zero>( /// A value of the `Rotate` property /// /// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> +/// cbindgen:private-default-tagged-enum-constructor=false pub enum GenericRotate<Number, Angle> { /// 'none' None, @@ -685,8 +696,10 @@ where Clone, Copy, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, @@ -697,6 +710,7 @@ where /// A value of the `Scale` property /// /// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> +/// cbindgen:private-default-tagged-enum-constructor=false pub enum GenericScale<Number> { /// 'none' None, @@ -749,8 +763,10 @@ fn y_axis_and_z_axis_are_zero<LengthPercentage: Zero, Length: Zero>( #[derive( Clone, Debug, + Deserialize, MallocSizeOf, PartialEq, + Serialize, SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, @@ -772,6 +788,7 @@ fn y_axis_and_z_axis_are_zero<LengthPercentage: Zero, Length: Zero>( /// https://github.com/w3c/csswg-drafts/issues/3305 /// /// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> +/// cbindgen:private-default-tagged-enum-constructor=false pub enum GenericTranslate<LengthPercentage, Length> where LengthPercentage: Zero, @@ -803,9 +820,8 @@ pub use self::GenericTranslate as Translate; ToResolvedValue, ToShmem, )] +#[repr(u8)] pub enum TransformStyle { - #[cfg(feature = "servo")] - Auto, Flat, #[css(keyword = "preserve-3d")] Preserve3d, diff --git a/components/style/values/resolved/counters.rs b/components/style/values/resolved/counters.rs new file mode 100644 index 00000000000..bf6490f7401 --- /dev/null +++ b/components/style/values/resolved/counters.rs @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! Resolved values for counter properties + +use super::{Context, ToResolvedValue}; +use crate::values::computed; + +/// https://drafts.csswg.org/css-content/#content-property +/// +/// We implement this at resolved value time because otherwise it causes us to +/// allocate a bunch of useless initial structs for ::before / ::after, which is +/// a bit unfortunate. +/// +/// Though these should be temporary, mostly, so if this causes complexity in +/// other places, it should be fine to move to `StyleAdjuster`. +/// +/// See https://github.com/w3c/csswg-drafts/issues/4632 for where some related +/// issues are being discussed. +impl ToResolvedValue for computed::Content { + type ResolvedValue = Self; + + #[inline] + fn to_resolved_value(self, context: &Context) -> Self { + let is_before_or_after = context + .style + .pseudo() + .map_or(false, |p| p.is_before_or_after()); + + match self { + Self::Normal if is_before_or_after => Self::None, + // For now, make `content: none` compute to `normal` on other + // elements, as we don't respect it. + // + // FIXME(emilio, bug 1605473): for marker this should be preserved + // and respected, probably. + Self::None if !is_before_or_after => Self::Normal, + other => other, + } + } + + #[inline] + fn from_resolved_value(resolved: Self) -> Self { + resolved + } +} diff --git a/components/style/values/resolved/mod.rs b/components/style/values/resolved/mod.rs index e64178a1c95..022cb7893c6 100644 --- a/components/style/values/resolved/mod.rs +++ b/components/style/values/resolved/mod.rs @@ -10,6 +10,7 @@ use cssparser; use smallvec::SmallVec; mod color; +mod counters; use crate::values::computed; @@ -68,6 +69,7 @@ trivial_to_resolved_value!(u32); trivial_to_resolved_value!(usize); trivial_to_resolved_value!(String); trivial_to_resolved_value!(Box<str>); +trivial_to_resolved_value!(crate::OwnedStr); trivial_to_resolved_value!(cssparser::RGBA); trivial_to_resolved_value!(crate::Atom); trivial_to_resolved_value!(app_units::Au); @@ -76,6 +78,7 @@ trivial_to_resolved_value!(computed::url::ComputedUrl); trivial_to_resolved_value!(computed::url::ComputedImageUrl); #[cfg(feature = "servo")] trivial_to_resolved_value!(html5ever::Prefix); +trivial_to_resolved_value!(computed::LengthPercentage); impl<A, B> ToResolvedValue for (A, B) where 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())), } |