diff options
Diffstat (limited to 'servo/components/style/values')
-rw-r--r-- | servo/components/style/values/animated/mod.rs | 27 | ||||
-rw-r--r-- | servo/components/style/values/computed/length_percentage.rs | 41 | ||||
-rw-r--r-- | servo/components/style/values/computed/mod.rs | 19 | ||||
-rw-r--r-- | servo/components/style/values/generics/calc.rs | 243 | ||||
-rw-r--r-- | servo/components/style/values/generics/counters.rs | 37 | ||||
-rw-r--r-- | servo/components/style/values/resolved/mod.rs | 19 | ||||
-rw-r--r-- | servo/components/style/values/specified/box.rs | 24 | ||||
-rw-r--r-- | servo/components/style/values/specified/calc.rs | 82 | ||||
-rw-r--r-- | servo/components/style/values/specified/color.rs | 216 | ||||
-rw-r--r-- | servo/components/style/values/specified/counters.rs | 52 |
10 files changed, 367 insertions, 393 deletions
diff --git a/servo/components/style/values/animated/mod.rs b/servo/components/style/values/animated/mod.rs index 31ea206fc0..235ddcba5a 100644 --- a/servo/components/style/values/animated/mod.rs +++ b/servo/components/style/values/animated/mod.rs @@ -273,6 +273,23 @@ where } } +impl<T> ToAnimatedValue for thin_vec::ThinVec<T> +where + T: ToAnimatedValue, +{ + type AnimatedValue = thin_vec::ThinVec<<T as ToAnimatedValue>::AnimatedValue>; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + self.into_iter().map(T::to_animated_value).collect() + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + animated.into_iter().map(T::from_animated_value).collect() + } +} + impl<T> ToAnimatedValue for Box<T> where T: ToAnimatedValue, @@ -452,6 +469,16 @@ where } } +impl<T> ToAnimatedZero for thin_vec::ThinVec<T> +where + T: ToAnimatedZero, +{ + #[inline] + fn to_animated_zero(&self) -> Result<Self, ()> { + self.iter().map(|v| v.to_animated_zero()).collect() + } +} + impl<T> ToAnimatedZero for Box<[T]> where T: ToAnimatedZero, diff --git a/servo/components/style/values/computed/length_percentage.rs b/servo/components/style/values/computed/length_percentage.rs index c448025dd1..f5f3303141 100644 --- a/servo/components/style/values/computed/length_percentage.rs +++ b/servo/components/style/values/computed/length_percentage.rs @@ -680,12 +680,12 @@ impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf { } } - fn unitless_value(&self) -> f32 { - match *self { + fn unitless_value(&self) -> Option<f32> { + Some(match *self { Self::Length(ref l) => l.px(), Self::Percentage(ref p) => p.0, Self::Number(n) => n, - } + }) } fn new_number(value: f32) -> Self { @@ -709,9 +709,18 @@ impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf { return None; } - let self_negative = self.is_negative(); - if self_negative != other.is_negative() { - return Some(if self_negative { std::cmp::Ordering::Less } else { std::cmp::Ordering::Greater }); + let Ok(self_negative) = self.is_negative() else { + return None; + }; + let Ok(other_negative) = other.is_negative() else { + return None; + }; + if self_negative != other_negative { + return Some(if self_negative { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Greater + }); } match (self, other) { @@ -774,15 +783,17 @@ impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf { } else { // The right side is not a number, so the result should be in the units of the right // side. - other.map(|v| v * *left); - std::mem::swap(self, other); - true + if other.map(|v| v * *left).is_ok() { + std::mem::swap(self, other); + true + } else { + false + } } } else if let Self::Number(ref right) = *other { // The left side is not a number, but the right side is, so the result is the left // side unit. - self.map(|v| v * *right); - true + self.map(|v| v * *right).is_ok() } else { // Neither side is a number, so a product is not possible. false @@ -814,8 +825,8 @@ impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf { }) } - fn map(&mut self, mut op: impl FnMut(f32) -> f32) { - match self { + fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> { + Ok(match self { Self::Length(value) => { *value = Length::new(op(value.px())); }, @@ -825,7 +836,7 @@ impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf { Self::Number(value) => { *value = op(*value); }, - } + }) } fn simplify(&mut self) {} @@ -921,7 +932,7 @@ impl specified::CalcLengthPercentage { } }), Leaf::Number(n) => CalcLengthPercentageLeaf::Number(n), - Leaf::Angle(..) | Leaf::Time(..) | Leaf::Resolution(..) => { + Leaf::Angle(..) | Leaf::Time(..) | Leaf::Resolution(..) | Leaf::ColorComponent(..) => { unreachable!("Shouldn't have parsed") }, }); diff --git a/servo/components/style/values/computed/mod.rs b/servo/components/style/values/computed/mod.rs index 85aadb401f..ca32408a79 100644 --- a/servo/components/style/values/computed/mod.rs +++ b/servo/components/style/values/computed/mod.rs @@ -649,6 +649,25 @@ where } } +impl<T> ToComputedValue for thin_vec::ThinVec<T> +where + T: ToComputedValue, +{ + type ComputedValue = thin_vec::ThinVec<<T as ToComputedValue>::ComputedValue>; + + #[inline] + fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { + self.iter() + .map(|item| item.to_computed_value(context)) + .collect() + } + + #[inline] + fn from_computed_value(computed: &Self::ComputedValue) -> Self { + computed.iter().map(T::from_computed_value).collect() + } +} + // NOTE(emilio): This is implementable more generically, but it's unlikely // what you want there, as it forces you to have an extra allocation. // diff --git a/servo/components/style/values/generics/calc.rs b/servo/components/style/values/generics/calc.rs index abcb5fe6eb..215db1932b 100644 --- a/servo/components/style/values/generics/calc.rs +++ b/servo/components/style/values/generics/calc.rs @@ -255,12 +255,15 @@ bitflags! { const TIME = 1 << 3; /// <resolution> const RESOLUTION = 1 << 4; + /// A component of a color (r, g, b, h, s, l, alpha, etc.) + const COLOR_COMPONENT = 1 << 5; /// <length-percentage> const LENGTH_PERCENTAGE = Self::LENGTH.bits() | Self::PERCENTAGE.bits(); // NOTE: When you add to this, make sure to make Atan2 deal with these. /// Allow all units. - const ALL = Self::LENGTH.bits() | Self::PERCENTAGE.bits() | Self::ANGLE.bits() | Self::TIME.bits() | Self::RESOLUTION.bits(); + const ALL = Self::LENGTH.bits() | Self::PERCENTAGE.bits() | Self::ANGLE.bits() | + Self::TIME.bits() | Self::RESOLUTION.bits() | Self::COLOR_COMPONENT.bits(); } } @@ -323,8 +326,8 @@ pub trait CalcNodeLeaf: Clone + Sized + PartialEq + ToCss { /// Returns the unit of the leaf. fn unit(&self) -> CalcUnits; - /// Returns the unitless value of this leaf. - fn unitless_value(&self) -> f32; + /// Returns the unitless value of this leaf if one is available. + fn unitless_value(&self) -> Option<f32>; /// Return true if the units of both leaves are equal. (NOTE: Does not take /// the values into account) @@ -347,23 +350,31 @@ pub trait CalcNodeLeaf: Clone + Sized + PartialEq + ToCss { fn as_number(&self) -> Option<f32>; /// Whether this value is known-negative. - fn is_negative(&self) -> bool { - self.unitless_value().is_sign_negative() + fn is_negative(&self) -> Result<bool, ()> { + self.unitless_value() + .map(|v| Ok(v.is_sign_negative())) + .unwrap_or_else(|| Err(())) } /// Whether this value is infinite. - fn is_infinite(&self) -> bool { - self.unitless_value().is_infinite() + fn is_infinite(&self) -> Result<bool, ()> { + self.unitless_value() + .map(|v| Ok(v.is_infinite())) + .unwrap_or_else(|| Err(())) } /// Whether this value is zero. - fn is_zero(&self) -> bool { - self.unitless_value().is_zero() + fn is_zero(&self) -> Result<bool, ()> { + self.unitless_value() + .map(|v| Ok(v.is_zero())) + .unwrap_or_else(|| Err(())) } /// Whether this value is NaN. - fn is_nan(&self) -> bool { - self.unitless_value().is_nan() + fn is_nan(&self) -> Result<bool, ()> { + self.unitless_value() + .map(|v| Ok(v.is_nan())) + .unwrap_or_else(|| Err(())) } /// Tries to merge one leaf into another using the sum, that is, perform `x` + `y`. @@ -379,12 +390,7 @@ pub trait CalcNodeLeaf: Clone + Sized + PartialEq + ToCss { O: Fn(f32, f32) -> f32; /// Map the value of this node with the given operation. - fn map(&mut self, op: impl FnMut(f32) -> f32); - - /// Negates the leaf. - fn negate(&mut self) { - self.map(std::ops::Neg::neg); - } + fn map(&mut self, op: impl FnMut(f32) -> f32) -> Result<(), ()>; /// Canonicalizes the expression if necessary. fn simplify(&mut self); @@ -393,16 +399,20 @@ pub trait CalcNodeLeaf: Clone + Sized + PartialEq + ToCss { fn sort_key(&self) -> SortKey; /// Create a new leaf containing the sign() result of the given leaf. - fn sign_from(leaf: &impl CalcNodeLeaf) -> Self { - Self::new_number(if leaf.is_nan() { + fn sign_from(leaf: &impl CalcNodeLeaf) -> Result<Self, ()> { + let Some(value) = leaf.unitless_value() else { + return Err(()); + }; + + Ok(Self::new_number(if value.is_nan() { f32::NAN - } else if leaf.is_zero() { - leaf.unitless_value() - } else if leaf.is_negative() { + } else if value.is_zero() { + value + } else if value.is_sign_negative() { -1.0 } else { 1.0 - }) + })) } } @@ -426,8 +436,8 @@ impl<L: CalcNodeLeaf> CalcNode<L> { /// Change all the leaf nodes to have the given value. This is useful when /// you have `calc(1px * nan)` and you want to replace the product node with /// `calc(nan)`, in which case the unit will be retained. - fn coerce_to_value(&mut self, value: f32) { - self.map(|_| value); + fn coerce_to_value(&mut self, value: f32) -> Result<(), ()> { + self.map(|_| value) } /// Return true if a product is distributive over this node. @@ -436,7 +446,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> { #[inline] pub fn is_product_distributive(&self) -> bool { match self { - Self::Leaf(_) => true, + Self::Leaf(l) => l.unit() != CalcUnits::COLOR_COMPONENT, Self::Sum(children) => children.iter().all(|c| c.is_product_distributive()), _ => false, } @@ -546,7 +556,11 @@ impl<L: CalcNodeLeaf> CalcNode<L> { } match *self { - CalcNode::Leaf(ref mut leaf) => leaf.negate(), + CalcNode::Leaf(ref mut leaf) => { + if leaf.map(std::ops::Neg::neg).is_err() { + wrap_self_in_negate(self) + } + }, CalcNode::Negate(ref mut value) => { // Don't negate the value here. Replace `self` with it's child. let result = mem::replace(value.as_mut(), Self::dummy()); @@ -668,7 +682,9 @@ impl<L: CalcNodeLeaf> CalcNode<L> { } if self.is_product_distributive() { - self.map(|v| v * number); + if self.map(|v| v * number).is_err() { + return false; + } return true; } } @@ -682,7 +698,9 @@ impl<L: CalcNodeLeaf> CalcNode<L> { } if other.is_product_distributive() { - other.map(|v| v * number); + if other.map(|v| v * number).is_err() { + return false; + } std::mem::swap(self, other); return true; } @@ -706,48 +724,52 @@ impl<L: CalcNodeLeaf> CalcNode<L> { } /// Map the value of this node with the given operation. - pub fn map(&mut self, mut op: impl FnMut(f32) -> f32) { - fn map_internal<L: CalcNodeLeaf>(node: &mut CalcNode<L>, op: &mut impl FnMut(f32) -> f32) { + pub fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> { + fn map_internal<L: CalcNodeLeaf>( + node: &mut CalcNode<L>, + op: &mut impl FnMut(f32) -> f32, + ) -> Result<(), ()> { match node { CalcNode::Leaf(l) => l.map(op), CalcNode::Negate(v) | CalcNode::Invert(v) => map_internal(v, op), CalcNode::Sum(children) | CalcNode::Product(children) => { for node in &mut **children { - map_internal(node, op); + map_internal(node, op)?; } + Ok(()) }, CalcNode::MinMax(children, _) => { for node in &mut **children { - map_internal(node, op); + map_internal(node, op)?; } + Ok(()) }, CalcNode::Clamp { min, center, max } => { - map_internal(min, op); - map_internal(center, op); - map_internal(max, op); + map_internal(min, op)?; + map_internal(center, op)?; + map_internal(max, op) }, CalcNode::Round { value, step, .. } => { - map_internal(value, op); - map_internal(step, op); + map_internal(value, op)?; + map_internal(step, op) }, CalcNode::ModRem { dividend, divisor, .. } => { - map_internal(dividend, op); - map_internal(divisor, op); + map_internal(dividend, op)?; + map_internal(divisor, op) }, CalcNode::Hypot(children) => { for node in &mut **children { - map_internal(node, op); + map_internal(node, op)?; } + Ok(()) }, - CalcNode::Abs(child) | CalcNode::Sign(child) => { - map_internal(child, op); - }, + CalcNode::Abs(child) | CalcNode::Sign(child) => map_internal(child, op), } } - map_internal(self, &mut op); + map_internal(self, &mut op) } /// Convert this `CalcNode` into a `CalcNode` with a different leaf kind. @@ -849,12 +871,12 @@ impl<L: CalcNodeLeaf> CalcNode<L> { Self::Leaf(l) => leaf_to_output_fn(l), Self::Negate(child) => { let mut result = child.resolve_internal(leaf_to_output_fn)?; - result.map(|v| v.neg()); + result.map(|v| v.neg())?; Ok(result) }, Self::Invert(child) => { let mut result = child.resolve_internal(leaf_to_output_fn)?; - result.map(|v| 1.0 / v); + result.map(|v| 1.0 / v)?; Ok(result) }, Self::Sum(children) => { @@ -878,13 +900,13 @@ impl<L: CalcNodeLeaf> CalcNode<L> { Some(left) => { // Left side is a number, so we use the right node as the result. result = right; - result.map(|v| v * left); + result.map(|v| v * left)?; }, None => { // Left side is not a number, so check if the right side is. match right.as_number() { Some(right) => { - result.map(|v| v * right); + result.map(|v| v * right)?; }, None => { // Multiplying with both sides having units. @@ -900,7 +922,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> { Self::MinMax(children, op) => { let mut result = children[0].resolve_internal(leaf_to_output_fn)?; - if result.is_nan() { + if result.is_nan()? { return Ok(result); } @@ -912,7 +934,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> { return Err(()); } - if candidate.is_nan() { + if candidate.is_nan()? { result = candidate; break; } @@ -938,15 +960,15 @@ impl<L: CalcNodeLeaf> CalcNode<L> { return Err(()); } - if min.is_nan() { + if min.is_nan()? { return Ok(min); } - if center.is_nan() { + if center.is_nan()? { return Ok(center); } - if max.is_nan() { + if max.is_nan()? { return Ok(max); } @@ -972,7 +994,10 @@ impl<L: CalcNodeLeaf> CalcNode<L> { return Err(()); } - let step = step.unitless_value().abs(); + let Some(step) = step.unitless_value() else { + return Err(()); + }; + let step = step.abs(); value.map(|value| { // TODO(emilio): Seems like at least a few of these @@ -1039,7 +1064,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> { } }, } - }); + })?; Ok(value) }, @@ -1055,13 +1080,15 @@ impl<L: CalcNodeLeaf> CalcNode<L> { return Err(()); } - let divisor = divisor.unitless_value(); - dividend.map(|dividend| op.apply(dividend, divisor)); + let Some(divisor) = divisor.unitless_value() else { + return Err(()); + }; + dividend.map(|dividend| op.apply(dividend, divisor))?; Ok(dividend) }, Self::Hypot(children) => { let mut result = children[0].resolve_internal(leaf_to_output_fn)?; - result.map(|v| v.powi(2)); + result.map(|v| v.powi(2))?; for child in children.iter().skip(1) { let child_value = child.resolve_internal(leaf_to_output_fn)?; @@ -1070,45 +1097,48 @@ impl<L: CalcNodeLeaf> CalcNode<L> { return Err(()); } - result.map(|v| v + child_value.unitless_value().powi(2)); + let Some(child_value) = child_value.unitless_value() else { + return Err(()); + }; + result.map(|v| v + child_value.powi(2))?; } - result.map(|v| v.sqrt()); + result.map(|v| v.sqrt())?; Ok(result) }, Self::Abs(ref c) => { let mut result = c.resolve_internal(leaf_to_output_fn)?; - result.map(|v| v.abs()); + result.map(|v| v.abs())?; Ok(result) }, Self::Sign(ref c) => { let result = c.resolve_internal(leaf_to_output_fn)?; - Ok(L::sign_from(&result)) + Ok(L::sign_from(&result)?) }, } } - fn is_negative_leaf(&self) -> bool { - match *self { - Self::Leaf(ref l) => l.is_negative(), + fn is_negative_leaf(&self) -> Result<bool, ()> { + Ok(match *self { + Self::Leaf(ref l) => l.is_negative()?, _ => false, - } + }) } - fn is_zero_leaf(&self) -> bool { - match *self { - Self::Leaf(ref l) => l.is_zero(), + fn is_zero_leaf(&self) -> Result<bool, ()> { + Ok(match *self { + Self::Leaf(ref l) => l.is_zero()?, _ => false, - } + }) } - fn is_infinite_leaf(&self) -> bool { - match *self { - Self::Leaf(ref l) => l.is_infinite(), + fn is_infinite_leaf(&self) -> Result<bool, ()> { + Ok(match *self { + Self::Leaf(ref l) => l.is_infinite()?, _ => false, - } + }) } /// Visits all the nodes in this calculation tree recursively, starting by @@ -1250,54 +1280,64 @@ impl<L: CalcNodeLeaf> CalcNode<L> { ref mut value, ref mut step, } => { - if step.is_zero_leaf() { - value.coerce_to_value(f32::NAN); + if value_or_stop!(step.is_zero_leaf()) { + value_or_stop!(value.coerce_to_value(f32::NAN)); replace_self_with!(&mut **value); return; } - if value.is_infinite_leaf() && step.is_infinite_leaf() { - value.coerce_to_value(f32::NAN); + if value_or_stop!(value.is_infinite_leaf()) && + value_or_stop!(step.is_infinite_leaf()) + { + value_or_stop!(value.coerce_to_value(f32::NAN)); replace_self_with!(&mut **value); return; } - if value.is_infinite_leaf() { + if value_or_stop!(value.is_infinite_leaf()) { replace_self_with!(&mut **value); return; } - if step.is_infinite_leaf() { + if value_or_stop!(step.is_infinite_leaf()) { match strategy { RoundingStrategy::Nearest | RoundingStrategy::ToZero => { - value.coerce_to_value(0.0); + value_or_stop!(value.coerce_to_value(0.0)); replace_self_with!(&mut **value); return; }, RoundingStrategy::Up => { - if !value.is_negative_leaf() && !value.is_zero_leaf() { - value.coerce_to_value(f32::INFINITY); + if !value_or_stop!(value.is_negative_leaf()) && + !value_or_stop!(value.is_zero_leaf()) + { + value_or_stop!(value.coerce_to_value(f32::INFINITY)); replace_self_with!(&mut **value); return; - } else if !value.is_negative_leaf() && value.is_zero_leaf() { + } else if !value_or_stop!(value.is_negative_leaf()) && + value_or_stop!(value.is_zero_leaf()) + { replace_self_with!(&mut **value); return; } else { - value.coerce_to_value(0.0); + value_or_stop!(value.coerce_to_value(0.0)); replace_self_with!(&mut **value); return; } }, RoundingStrategy::Down => { - if value.is_negative_leaf() && !value.is_zero_leaf() { - value.coerce_to_value(f32::INFINITY); + if value_or_stop!(value.is_negative_leaf()) && + !value_or_stop!(value.is_zero_leaf()) + { + value_or_stop!(value.coerce_to_value(f32::INFINITY)); replace_self_with!(&mut **value); return; - } else if value.is_negative_leaf() && value.is_zero_leaf() { + } else if value_or_stop!(value.is_negative_leaf()) && + value_or_stop!(value.is_zero_leaf()) + { replace_self_with!(&mut **value); return; } else { - value.coerce_to_value(0.0); + value_or_stop!(value.coerce_to_value(0.0)); replace_self_with!(&mut **value); return; } @@ -1305,17 +1345,18 @@ impl<L: CalcNodeLeaf> CalcNode<L> { } } - if step.is_negative_leaf() { + if value_or_stop!(step.is_negative_leaf()) { step.negate(); } let remainder = value_or_stop!(value.try_op(step, Rem::rem)); - if remainder.is_zero_leaf() { + if value_or_stop!(remainder.is_zero_leaf()) { replace_self_with!(&mut **value); return; } - let (mut lower_bound, mut upper_bound) = if value.is_negative_leaf() { + let (mut lower_bound, mut upper_bound) = if value_or_stop!(value.is_negative_leaf()) + { let upper_bound = value_or_stop!(value.try_op(&remainder, Sub::sub)); let lower_bound = value_or_stop!(upper_bound.try_op(&step, Sub::sub)); @@ -1348,11 +1389,11 @@ impl<L: CalcNodeLeaf> CalcNode<L> { let mut lower_diff = lower_bound.clone(); let mut upper_diff = upper_bound.clone(); - if lower_diff.is_negative_leaf() { + if value_or_stop!(lower_diff.is_negative_leaf()) { lower_diff.negate(); } - if upper_diff.is_negative_leaf() { + if value_or_stop!(upper_diff.is_negative_leaf()) { upper_diff.negate(); } @@ -1518,13 +1559,13 @@ impl<L: CalcNodeLeaf> CalcNode<L> { }, Self::Abs(ref mut child) => { if let CalcNode::Leaf(leaf) = child.as_mut() { - leaf.map(|v| v.abs()); + value_or_stop!(leaf.map(|v| v.abs())); replace_self_with!(&mut **child); } }, Self::Sign(ref mut child) => { if let CalcNode::Leaf(leaf) = child.as_mut() { - let mut result = Self::Leaf(L::sign_from(leaf)); + let mut result = Self::Leaf(value_or_stop!(L::sign_from(leaf))); replace_self_with!(&mut result); } }, @@ -1553,7 +1594,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> { // 1. If root’s child is a number (not a percentage or dimension) return the // reciprocal of the child’s value. if leaf.unit().is_empty() { - child.map(|v| 1.0 / v); + value_or_stop!(child.map(|v| 1.0 / v)); replace_self_with!(&mut **child); } }, @@ -1678,10 +1719,12 @@ impl<L: CalcNodeLeaf> CalcNode<L> { if !first { match child { Self::Leaf(l) => { - if l.is_negative() { + if let Ok(true) = l.is_negative() { dest.write_str(" - ")?; let mut negated = l.clone(); - negated.negate(); + // We can unwrap here, because we already + // checked if the value inside is negative. + negated.map(std::ops::Neg::neg).unwrap(); negated.to_css(dest)?; } else { dest.write_str(" + ")?; diff --git a/servo/components/style/values/generics/counters.rs b/servo/components/style/values/generics/counters.rs index 3f23c74b33..d6db48f9c4 100644 --- a/servo/components/style/values/generics/counters.rs +++ b/servo/components/style/values/generics/counters.rs @@ -203,6 +203,41 @@ fn is_decimal(counter_type: &CounterStyleType) -> bool { *counter_type == CounterStyle::decimal() } +/// The non-normal, non-none values of the content property. +#[derive( + Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToShmem, +)] +#[repr(C)] +pub struct GenericContentItems<Image> { + /// The actual content items. Note that, past the alt marker, only some subset (strings, + /// attr(), counter()) + pub items: thin_vec::ThinVec<GenericContentItem<Image>>, + /// The index at which alt text starts, always non-zero. If equal to items.len(), no alt text + /// exists. + pub alt_start: usize, +} + +impl<Image> ToCss for GenericContentItems<Image> +where + Image: ToCss, +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + for (i, item) in self.items.iter().enumerate() { + if i == self.alt_start { + dest.write_str(" /")?; + } + if i != 0 { + dest.write_str(" ")?; + } + item.to_css(dest)?; + } + Ok(()) + } +} + /// The specified value for the `content` property. /// /// https://drafts.csswg.org/css-content/#propdef-content @@ -216,7 +251,7 @@ pub enum GenericContent<Image> { /// `none` reserved keyword. None, /// Content items. - Items(#[css(iterable)] crate::OwnedSlice<GenericContentItem<Image>>), + Items(GenericContentItems<Image>), } pub use self::GenericContent as Content; diff --git a/servo/components/style/values/resolved/mod.rs b/servo/components/style/values/resolved/mod.rs index d830474fe6..fc59f8f36d 100644 --- a/servo/components/style/values/resolved/mod.rs +++ b/servo/components/style/values/resolved/mod.rs @@ -180,6 +180,25 @@ where } } +impl<T> ToResolvedValue for thin_vec::ThinVec<T> +where + T: ToResolvedValue, +{ + type ResolvedValue = thin_vec::ThinVec<<T as ToResolvedValue>::ResolvedValue>; + + #[inline] + fn to_resolved_value(self, context: &Context) -> Self::ResolvedValue { + self.into_iter() + .map(|item| item.to_resolved_value(context)) + .collect() + } + + #[inline] + fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { + resolved.into_iter().map(T::from_resolved_value).collect() + } +} + impl<T> ToResolvedValue for Box<T> where T: ToResolvedValue, diff --git a/servo/components/style/values/specified/box.rs b/servo/components/style/values/specified/box.rs index ee50227504..9b87103048 100644 --- a/servo/components/style/values/specified/box.rs +++ b/servo/components/style/values/specified/box.rs @@ -1495,6 +1495,9 @@ pub enum Appearance { /// For HTML's <input type=number> #[parse(condition = "ParserContext::chrome_rules_enabled")] NumberInput, + /// For HTML's <input type=password> + #[parse(condition = "ParserContext::chrome_rules_enabled")] + PasswordInput, /// The progress bar's progress indicator #[parse(condition = "ParserContext::chrome_rules_enabled")] Progresschunk, @@ -1576,27 +1579,6 @@ pub enum Appearance { /// A tooltip. #[parse(condition = "ParserContext::chrome_rules_enabled")] Tooltip, - /// A listbox or tree widget header - #[parse(condition = "ParserContext::chrome_rules_enabled")] - Treeheader, - /// An individual header cell - #[parse(condition = "ParserContext::chrome_rules_enabled")] - Treeheadercell, - /// A tree item. - #[parse(condition = "ParserContext::chrome_rules_enabled")] - Treeitem, - /// A tree widget branch line - #[parse(condition = "ParserContext::chrome_rules_enabled")] - Treeline, - /// A tree widget twisty. - #[parse(condition = "ParserContext::chrome_rules_enabled")] - Treetwisty, - /// Open tree widget twisty. - #[parse(condition = "ParserContext::chrome_rules_enabled")] - Treetwistyopen, - /// A tree widget. - #[parse(condition = "ParserContext::chrome_rules_enabled")] - Treeview, /// Mac help button. #[parse(condition = "ParserContext::chrome_rules_enabled")] diff --git a/servo/components/style/values/specified/calc.rs b/servo/components/style/values/specified/calc.rs index 17f043ac58..a354963ce1 100644 --- a/servo/components/style/values/specified/calc.rs +++ b/servo/components/style/values/specified/calc.rs @@ -6,6 +6,7 @@ //! //! [calc]: https://drafts.csswg.org/css-values/#calc-notation +use crate::color::parsing::ChannelKeyword; use crate::parser::ParserContext; use crate::values::generics::calc::{ self as generic, CalcNodeLeaf, CalcUnits, MinMaxOp, ModRemOp, PositivePercentageBasis, @@ -80,6 +81,8 @@ pub enum Leaf { Time(Time), /// `<resolution>` Resolution(Resolution), + /// A component of a color. + ColorComponent(ChannelKeyword), /// `<percentage>` Percentage(CSSFloat), /// `<number>` @@ -107,6 +110,7 @@ impl ToCss for Leaf { Self::Percentage(p) => serialize_percentage(p, dest), Self::Angle(ref a) => a.to_css(dest), Self::Time(ref t) => t.to_css(dest), + Self::ColorComponent(ref s) => s.to_css(dest), } } } @@ -152,19 +156,21 @@ impl generic::CalcNodeLeaf for Leaf { Leaf::Angle(_) => CalcUnits::ANGLE, Leaf::Time(_) => CalcUnits::TIME, Leaf::Resolution(_) => CalcUnits::RESOLUTION, + Leaf::ColorComponent(_) => CalcUnits::COLOR_COMPONENT, Leaf::Percentage(_) => CalcUnits::PERCENTAGE, Leaf::Number(_) => CalcUnits::empty(), } } - fn unitless_value(&self) -> f32 { - match *self { + fn unitless_value(&self) -> Option<f32> { + Some(match *self { Self::Length(ref l) => l.unitless_value(), Self::Percentage(n) | Self::Number(n) => n, Self::Resolution(ref r) => r.dppx(), Self::Angle(ref a) => a.degrees(), Self::Time(ref t) => t.seconds(), - } + Self::ColorComponent(_) => return None, + }) } fn new_number(value: f32) -> Self { @@ -182,8 +188,8 @@ impl generic::CalcNodeLeaf for Leaf { return None; } - let self_negative = self.is_negative(); - if self_negative != other.is_negative() { + let self_negative = self.is_negative().unwrap_or(false); + if self_negative != other.is_negative().unwrap_or(false) { return Some(if self_negative { cmp::Ordering::Less } else { @@ -198,10 +204,11 @@ impl generic::CalcNodeLeaf for Leaf { (&Time(ref one), &Time(ref other)) => one.seconds().partial_cmp(&other.seconds()), (&Resolution(ref one), &Resolution(ref other)) => one.dppx().partial_cmp(&other.dppx()), (&Number(ref one), &Number(ref other)) => one.partial_cmp(other), + (&ColorComponent(ref one), &ColorComponent(ref other)) => one.partial_cmp(other), _ => { match *self { Length(..) | Percentage(..) | Angle(..) | Time(..) | Number(..) | - Resolution(..) => {}, + Resolution(..) | ColorComponent(..) => {}, } unsafe { debug_unreachable!("Forgot a branch?"); @@ -216,14 +223,15 @@ impl generic::CalcNodeLeaf for Leaf { Leaf::Angle(_) | Leaf::Time(_) | Leaf::Resolution(_) | - Leaf::Percentage(_) => None, + Leaf::Percentage(_) | + Leaf::ColorComponent(_) => None, Leaf::Number(value) => Some(value), } } fn sort_key(&self) -> SortKey { match *self { - Self::Number(..) => SortKey::Number, + Self::Number(..) | Self::ColorComponent(..) => SortKey::Number, Self::Percentage(..) => SortKey::Percentage, Self::Time(..) => SortKey::Sec, Self::Resolution(..) => SortKey::Dppx, @@ -316,7 +324,7 @@ impl generic::CalcNodeLeaf for Leaf { _ => { match *other { Number(..) | Percentage(..) | Angle(..) | Time(..) | Resolution(..) | - Length(..) => {}, + Length(..) | ColorComponent(..) => {}, } unsafe { debug_unreachable!(); @@ -336,15 +344,17 @@ impl generic::CalcNodeLeaf for Leaf { } else { // The right side is not a number, so the result should be in the units of the right // side. - other.map(|v| v * *left); - std::mem::swap(self, other); - true + if other.map(|v| v * *left).is_ok() { + std::mem::swap(self, other); + true + } else { + false + } } } else if let Self::Number(ref right) = *other { // The left side is not a number, but the right side is, so the result is the left // side unit. - self.map(|v| v * *right); - true + self.map(|v| v * *right).is_ok() } else { // Neither side is a number, so a product is not possible. false @@ -392,7 +402,7 @@ impl generic::CalcNodeLeaf for Leaf { _ => { match *other { Number(..) | Percentage(..) | Angle(..) | Time(..) | Length(..) | - Resolution(..) => {}, + Resolution(..) | ColorComponent(..) => {}, } unsafe { debug_unreachable!(); @@ -401,15 +411,16 @@ impl generic::CalcNodeLeaf for Leaf { } } - fn map(&mut self, mut op: impl FnMut(f32) -> f32) { - match self { + fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> { + Ok(match self { Leaf::Length(one) => *one = one.map(op), Leaf::Angle(one) => *one = specified::Angle::from_calc(op(one.degrees())), Leaf::Time(one) => *one = specified::Time::from_seconds(op(one.seconds())), Leaf::Resolution(one) => *one = specified::Resolution::from_dppx(op(one.dppx())), Leaf::Percentage(one) => *one = op(*one), Leaf::Number(one) => *one = op(*one), - } + Leaf::ColorComponent(..) => return Err(()), + }) } } @@ -468,15 +479,30 @@ impl CalcNode { CalcNode::parse(context, input, function, allowed_units) }, &Token::Ident(ref ident) => { - let number = match_ignore_ascii_case! { &**ident, - "e" => std::f32::consts::E, - "pi" => std::f32::consts::PI, - "infinity" => f32::INFINITY, - "-infinity" => f32::NEG_INFINITY, - "nan" => f32::NAN, - _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))), + let leaf = match_ignore_ascii_case! { &**ident, + "e" => Leaf::Number(std::f32::consts::E), + "pi" => Leaf::Number(std::f32::consts::PI), + "infinity" => Leaf::Number(f32::INFINITY), + "-infinity" => Leaf::Number(f32::NEG_INFINITY), + "nan" => Leaf::Number(f32::NAN), + _ => { + if crate::color::parsing::rcs_enabled() && + allowed_units.intersects(CalcUnits::COLOR_COMPONENT) + { + if let Ok(channel_keyword) = ChannelKeyword::from_ident(&ident) { + Leaf::ColorComponent(channel_keyword) + } else { + return Err(location + .new_unexpected_token_error(Token::Ident(ident.clone()))); + } + } else { + return Err( + location.new_unexpected_token_error(Token::Ident(ident.clone())) + ); + } + }, }; - Ok(CalcNode::Leaf(Leaf::Number(number))) + Ok(CalcNode::Leaf(leaf)) }, t => Err(location.new_unexpected_token_error(t.clone())), } @@ -817,7 +843,9 @@ impl CalcNode { if let Ok(resolved) = right.resolve() { if let Some(number) = resolved.as_number() { if number != 1.0 && left.is_product_distributive() { - left.map(|l| l / number); + if left.map(|l| l / number).is_err() { + return InPlaceDivisionResult::Invalid; + } return InPlaceDivisionResult::Merged; } } else { diff --git a/servo/components/style/values/specified/color.rs b/servo/components/style/values/specified/color.rs index f823ba7d30..289d89e44e 100644 --- a/servo/components/style/values/specified/color.rs +++ b/servo/components/style/values/specified/color.rs @@ -5,12 +5,8 @@ //! Specified color values. use super::AllowQuirks; -use crate::color::component::ColorComponent; -use crate::color::convert::normalize_hue; -use crate::color::parsing::{ - self, ColorParser, FromParsedColor, NumberOrAngle, NumberOrPercentage, -}; -use crate::color::{mix::ColorInterpolationMethod, AbsoluteColor, ColorSpace}; +use crate::color::mix::ColorInterpolationMethod; +use crate::color::{parsing, AbsoluteColor, ColorSpace}; use crate::media_queries::Device; use crate::parser::{Parse, ParserContext}; use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue}; @@ -19,8 +15,7 @@ use crate::values::generics::color::{ }; use crate::values::specified::Percentage; use crate::values::{normalize, CustomIdent}; -use cssparser::color::OPAQUE; -use cssparser::{color::PredefinedColorSpace, BasicParseErrorKind, ParseErrorKind, Parser, Token}; +use cssparser::{BasicParseErrorKind, ParseErrorKind, Parser, Token}; use std::fmt::{self, Write}; use std::io::Write as IoWrite; use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind}; @@ -429,199 +424,6 @@ impl SystemColor { } } -impl<T> From<ColorComponent<T>> for Option<T> { - fn from(value: ColorComponent<T>) -> Self { - match value { - ColorComponent::None => None, - ColorComponent::Value(value) => Some(value), - } - } -} - -impl ColorComponent<NumberOrPercentage> { - #[inline] - fn into_alpha(self) -> Option<f32> { - match self { - ColorComponent::None => None, - ColorComponent::Value(number_or_percentage) => { - Some(normalize(number_or_percentage.to_number(1.0)).clamp(0.0, OPAQUE)) - }, - } - } -} - -impl FromParsedColor for Color { - fn from_current_color() -> Self { - Color::CurrentColor - } - - fn from_rgba( - red: ColorComponent<u8>, - green: ColorComponent<u8>, - blue: ColorComponent<u8>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self { - macro_rules! c { - ($c:expr) => {{ - match $c { - ColorComponent::None => 0u8, - ColorComponent::Value(value) => value, - } - }}; - } - - // Legacy rgb() doesn't support "none" alpha values and falls back to 0. - let alpha = alpha.into_alpha().unwrap_or(0.0); - - AbsoluteColor::srgb_legacy(c!(red), c!(green), c!(blue), alpha).into() - } - - fn from_hsl( - hue: ColorComponent<NumberOrAngle>, - saturation: ColorComponent<NumberOrPercentage>, - lightness: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self { - // Percent reference range for S and L: 0% = 0.0, 100% = 100.0 - const LIGHTNESS_RANGE: f32 = 100.0; - const SATURATION_RANGE: f32 = 100.0; - - let hue = hue.map_value(|angle| normalize_hue(angle.degrees())); - let saturation = - saturation.map_value(|s| s.to_number(SATURATION_RANGE).clamp(0.0, SATURATION_RANGE)); - let lightness = - lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE).clamp(0.0, LIGHTNESS_RANGE)); - - AbsoluteColor::new( - ColorSpace::Hsl, - hue, - saturation, - lightness, - alpha.into_alpha(), - ) - .into() - } - - fn from_hwb( - hue: ColorComponent<NumberOrAngle>, - whiteness: ColorComponent<NumberOrPercentage>, - blackness: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self { - // Percent reference range for W and B: 0% = 0.0, 100% = 100.0 - const WHITENESS_RANGE: f32 = 100.0; - const BLACKNESS_RANGE: f32 = 100.0; - - let hue = hue.map_value(|angle| normalize_hue(angle.degrees())); - let whiteness = - whiteness.map_value(|w| w.to_number(WHITENESS_RANGE).clamp(0.0, WHITENESS_RANGE)); - let blackness = - blackness.map_value(|b| b.to_number(BLACKNESS_RANGE).clamp(0.0, BLACKNESS_RANGE)); - - AbsoluteColor::new( - ColorSpace::Hwb, - hue, - whiteness, - blackness, - alpha.into_alpha(), - ) - .into() - } - - fn from_lab( - lightness: ColorComponent<NumberOrPercentage>, - a: ColorComponent<NumberOrPercentage>, - b: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self { - // for L: 0% = 0.0, 100% = 100.0 - // for a and b: -100% = -125, 100% = 125 - const LIGHTNESS_RANGE: f32 = 100.0; - const A_B_RANGE: f32 = 125.0; - - let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE)); - let a = a.map_value(|a| a.to_number(A_B_RANGE)); - let b = b.map_value(|b| b.to_number(A_B_RANGE)); - - AbsoluteColor::new(ColorSpace::Lab, lightness, a, b, alpha.into_alpha()).into() - } - - fn from_lch( - lightness: ColorComponent<NumberOrPercentage>, - chroma: ColorComponent<NumberOrPercentage>, - hue: ColorComponent<NumberOrAngle>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self { - // for L: 0% = 0.0, 100% = 100.0 - // for C: 0% = 0, 100% = 150 - const LIGHTNESS_RANGE: f32 = 100.0; - const CHROMA_RANGE: f32 = 150.0; - - let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE)); - let chroma = chroma.map_value(|c| c.to_number(CHROMA_RANGE)); - let hue = hue.map_value(|angle| normalize_hue(angle.degrees())); - - AbsoluteColor::new(ColorSpace::Lch, lightness, chroma, hue, alpha.into_alpha()).into() - } - - fn from_oklab( - lightness: ColorComponent<NumberOrPercentage>, - a: ColorComponent<NumberOrPercentage>, - b: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self { - // for L: 0% = 0.0, 100% = 1.0 - // for a and b: -100% = -0.4, 100% = 0.4 - const LIGHTNESS_RANGE: f32 = 1.0; - const A_B_RANGE: f32 = 0.4; - - let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE)); - let a = a.map_value(|a| a.to_number(A_B_RANGE)); - let b = b.map_value(|b| b.to_number(A_B_RANGE)); - - AbsoluteColor::new(ColorSpace::Oklab, lightness, a, b, alpha.into_alpha()).into() - } - - fn from_oklch( - lightness: ColorComponent<NumberOrPercentage>, - chroma: ColorComponent<NumberOrPercentage>, - hue: ColorComponent<NumberOrAngle>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self { - // for L: 0% = 0.0, 100% = 1.0 - // for C: 0% = 0.0 100% = 0.4 - const LIGHTNESS_RANGE: f32 = 1.0; - const CHROMA_RANGE: f32 = 0.4; - - let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE)); - let chroma = chroma.map_value(|c| c.to_number(CHROMA_RANGE)); - let hue = hue.map_value(|angle| normalize_hue(angle.degrees())); - - AbsoluteColor::new( - ColorSpace::Oklch, - lightness, - chroma, - hue, - alpha.into_alpha(), - ) - .into() - } - - fn from_color_function( - color_space: PredefinedColorSpace, - c1: ColorComponent<NumberOrPercentage>, - c2: ColorComponent<NumberOrPercentage>, - c3: ColorComponent<NumberOrPercentage>, - alpha: ColorComponent<NumberOrPercentage>, - ) -> Self { - let c1 = c1.map_value(|c| c.to_number(1.0)); - let c2 = c2.map_value(|c| c.to_number(1.0)); - let c3 = c3.map_value(|c| c.to_number(1.0)); - - AbsoluteColor::new(color_space.into(), c1, c2, c3, alpha.into_alpha()).into() - } -} - /// Whether to preserve authored colors during parsing. That's useful only if we /// plan to serialize the color back. #[derive(Copy, Clone)] @@ -658,8 +460,7 @@ impl Color { }, }; - let color_parser = ColorParser { context: &context }; - match input.try_parse(|i| parsing::parse_color_with(&color_parser, i)) { + match input.try_parse(|i| parsing::parse_color_with(context, i)) { Ok(mut color) => { if let Color::Absolute(ref mut absolute) = color { // Because we can't set the `authored` value at construction time, we have to set it @@ -827,12 +628,9 @@ impl Color { loc: &cssparser::SourceLocation, ) -> Result<Self, ParseError<'i>> { match cssparser::color::parse_hash_color(bytes) { - Ok((r, g, b, a)) => Ok(Self::from_rgba( - r.into(), - g.into(), - b.into(), - ColorComponent::Value(NumberOrPercentage::Number { value: a }), - )), + Ok((r, g, b, a)) => Ok(Self::from_absolute_color(AbsoluteColor::srgb_legacy( + r, g, b, a, + ))), Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)), } } diff --git a/servo/components/style/values/specified/counters.rs b/servo/components/style/values/specified/counters.rs index 7760be91d7..6e41497caf 100644 --- a/servo/components/style/values/specified/counters.rs +++ b/servo/components/style/values/specified/counters.rs @@ -192,29 +192,33 @@ impl Parse for Content { return Ok(generics::Content::None); } - let mut content = vec![]; - let mut has_alt_content = false; + let mut items = thin_vec::ThinVec::new(); + let mut alt_start = None; loop { - { + if alt_start.is_none() { if let Ok(image) = input.try_parse(|i| Image::parse_forbid_none(context, i)) { - content.push(generics::ContentItem::Image(image)); + items.push(generics::ContentItem::Image(image)); continue; } } - match input.next() { - Ok(&Token::QuotedString(ref value)) => { - content.push(generics::ContentItem::String( + let Ok(t) = input.next() else { break }; + match *t { + Token::QuotedString(ref value) => { + items.push(generics::ContentItem::String( value.as_ref().to_owned().into(), )); }, - Ok(&Token::Function(ref name)) => { + Token::Function(ref name) => { + // FIXME(emilio): counter() / counters() should be valid per spec past + // the alt marker, but it's likely non-trivial to support and other + // browsers don't support it either, so restricting it for now. let result = match_ignore_ascii_case! { &name, - "counter" => input.parse_nested_block(|input| { + "counter" if alt_start.is_none() => input.parse_nested_block(|input| { let name = CustomIdent::parse(input, &[])?; let style = Content::parse_counter_style(context, input); Ok(generics::ContentItem::Counter(name, style)) }), - "counters" => input.parse_nested_block(|input| { + "counters" if alt_start.is_none() => input.parse_nested_block(|input| { let name = CustomIdent::parse(input, &[])?; input.expect_comma()?; let separator = input.expect_string()?.as_ref().to_owned().into(); @@ -232,17 +236,16 @@ impl Parse for Content { )) } }?; - content.push(result); + items.push(result); }, - Ok(&Token::Ident(ref ident)) => { - content.push(match_ignore_ascii_case! { &ident, + Token::Ident(ref ident) if alt_start.is_none() => { + items.push(match_ignore_ascii_case! { &ident, "open-quote" => generics::ContentItem::OpenQuote, "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; + "-moz-alt-content" if context.in_ua_sheet() => { generics::ContentItem::MozAltContent }, "-moz-label-content" if context.chrome_rules_enabled() => { @@ -256,17 +259,26 @@ impl Parse for Content { } }); }, - Err(_) => break, - Ok(t) => { + Token::Delim('/') + if alt_start.is_none() && + !items.is_empty() && + static_prefs::pref!("layout.css.content.alt-text.enabled") => + { + alt_start = Some(items.len()); + }, + ref t => { let t = t.clone(); return Err(input.new_unexpected_token_error(t)); }, } } - // We don't allow to parse `-moz-alt-content` in multiple positions. - if content.is_empty() || (has_alt_content && content.len() != 1) { + if items.is_empty() { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } - Ok(generics::Content::Items(content.into())) + let alt_start = alt_start.unwrap_or(items.len()); + Ok(generics::Content::Items(generics::GenericContentItems { + items, + alt_start, + })) } } |