aboutsummaryrefslogtreecommitdiffstats
path: root/components/style/values
diff options
context:
space:
mode:
Diffstat (limited to 'components/style/values')
-rw-r--r--components/style/values/animated/length.rs8
-rw-r--r--components/style/values/animated/mod.rs12
-rw-r--r--components/style/values/animated/transform.rs9
-rw-r--r--components/style/values/computed/align.rs9
-rw-r--r--components/style/values/computed/counters.rs4
-rw-r--r--components/style/values/computed/length.rs455
-rw-r--r--components/style/values/computed/length_percentage.rs793
-rw-r--r--components/style/values/computed/mod.rs57
-rw-r--r--components/style/values/computed/motion.rs2
-rw-r--r--components/style/values/computed/text.rs4
-rw-r--r--components/style/values/distance.rs2
-rw-r--r--components/style/values/generics/counters.rs39
-rw-r--r--components/style/values/generics/mod.rs32
-rw-r--r--components/style/values/generics/motion.rs3
-rw-r--r--components/style/values/generics/position.rs5
-rw-r--r--components/style/values/generics/text.rs6
-rw-r--r--components/style/values/generics/transform.rs22
-rw-r--r--components/style/values/resolved/counters.rs47
-rw-r--r--components/style/values/resolved/mod.rs3
-rw-r--r--components/style/values/specified/align.rs94
-rw-r--r--components/style/values/specified/angle.rs20
-rw-r--r--components/style/values/specified/box.rs73
-rw-r--r--components/style/values/specified/calc.rs760
-rw-r--r--components/style/values/specified/color.rs14
-rw-r--r--components/style/values/specified/counters.rs35
-rw-r--r--components/style/values/specified/font.rs48
-rw-r--r--components/style/values/specified/gecko.rs2
-rw-r--r--components/style/values/specified/length.rs199
-rw-r--r--components/style/values/specified/mod.rs96
-rw-r--r--components/style/values/specified/percentage.rs8
-rw-r--r--components/style/values/specified/position.rs3
-rw-r--r--components/style/values/specified/svg_path.rs17
-rw-r--r--components/style/values/specified/text.rs32
-rw-r--r--components/style/values/specified/time.rs19
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(&center) {
+ 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(&center) {
+ 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())),
}