From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- servo/components/style/values/generics/grid.rs | 829 +++++++++++++++++++++++++ 1 file changed, 829 insertions(+) create mode 100644 servo/components/style/values/generics/grid.rs (limited to 'servo/components/style/values/generics/grid.rs') diff --git a/servo/components/style/values/generics/grid.rs b/servo/components/style/values/generics/grid.rs new file mode 100644 index 0000000000..e35c96a28c --- /dev/null +++ b/servo/components/style/values/generics/grid.rs @@ -0,0 +1,829 @@ +/* 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/. */ + +//! Generic types for the handling of +//! [grids](https://drafts.csswg.org/css-grid/). + +use crate::parser::{Parse, ParserContext}; +use crate::values::specified; +use crate::values::specified::grid::parse_line_names; +use crate::values::{CSSFloat, CustomIdent}; +use crate::{Atom, Zero}; +use cssparser::Parser; +use std::fmt::{self, Write}; +use std::{cmp, usize}; +use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; + +/// These are the limits that we choose to clamp grid line numbers to. +/// http://drafts.csswg.org/css-grid/#overlarge-grids +/// line_num is clamped to this range at parse time. +pub const MIN_GRID_LINE: i32 = -10000; +/// See above. +pub const MAX_GRID_LINE: i32 = 10000; + +/// A `` type. +/// +/// +#[derive( + Clone, + Debug, + Default, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericGridLine { + /// A custom identifier for named lines, or the empty atom otherwise. + /// + /// + pub ident: Atom, + /// Denotes the nth grid line from grid item's placement. + /// + /// This is clamped by MIN_GRID_LINE and MAX_GRID_LINE. + /// + /// NOTE(emilio): If we ever allow animating these we need to either do + /// something more complicated for the clamping, or do this clamping at + /// used-value time. + pub line_num: Integer, + /// Flag to check whether it's a `span` keyword. + pub is_span: bool, +} + +pub use self::GenericGridLine as GridLine; + +impl GridLine +where + Integer: PartialEq + Zero, +{ + /// The `auto` value. + pub fn auto() -> Self { + Self { + is_span: false, + line_num: Zero::zero(), + ident: atom!(""), + } + } + + /// Check whether this `` represents an `auto` value. + pub fn is_auto(&self) -> bool { + self.ident == atom!("") && self.line_num.is_zero() && !self.is_span + } + + /// Check whether this `` represents a `` value. + pub fn is_ident_only(&self) -> bool { + self.ident != atom!("") && self.line_num.is_zero() && !self.is_span + } + + /// Check if `self` makes `other` omittable according to the rules at: + /// https://drafts.csswg.org/css-grid/#propdef-grid-column + /// https://drafts.csswg.org/css-grid/#propdef-grid-area + pub fn can_omit(&self, other: &Self) -> bool { + if self.is_ident_only() { + self == other + } else { + other.is_auto() + } + } +} + +impl ToCss for GridLine +where + Integer: ToCss + PartialEq + Zero, +{ + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + if self.is_auto() { + return dest.write_str("auto"); + } + + if self.is_span { + dest.write_str("span")?; + } + + if !self.line_num.is_zero() { + if self.is_span { + dest.write_char(' ')?; + } + self.line_num.to_css(dest)?; + } + + if self.ident != atom!("") { + if self.is_span || !self.line_num.is_zero() { + dest.write_char(' ')?; + } + CustomIdent(self.ident.clone()).to_css(dest)?; + } + + Ok(()) + } +} + +impl Parse for GridLine { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let mut grid_line = Self::auto(); + if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { + return Ok(grid_line); + } + + // | [ && ? ] | [ span && [ || ] ] + // This horror is simply, + // [ span? && [ || ] ] + // And, for some magical reason, "span" should be the first or last value and not in-between. + let mut val_before_span = false; + + for _ in 0..3 { + // Maximum possible entities for + let location = input.current_source_location(); + if input.try_parse(|i| i.expect_ident_matching("span")).is_ok() { + if grid_line.is_span { + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + if !grid_line.line_num.is_zero() || grid_line.ident != atom!("") { + val_before_span = true; + } + + grid_line.is_span = true; + } else if let Ok(i) = input.try_parse(|i| specified::Integer::parse(context, i)) { + // FIXME(emilio): Probably shouldn't reject if it's calc()... + let value = i.value(); + if value == 0 || val_before_span || !grid_line.line_num.is_zero() { + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + grid_line.line_num = specified::Integer::new(cmp::max( + MIN_GRID_LINE, + cmp::min(value, MAX_GRID_LINE), + )); + } else if let Ok(name) = input.try_parse(|i| i.expect_ident_cloned()) { + if val_before_span || grid_line.ident != atom!("") { + return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + // NOTE(emilio): `span` is consumed above, so we only need to + // reject `auto`. + grid_line.ident = CustomIdent::from_ident(location, &name, &["auto"])?.0; + } else { + break; + } + } + + if grid_line.is_auto() { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + if grid_line.is_span { + if !grid_line.line_num.is_zero() { + if grid_line.line_num.value() <= 0 { + // disallow negative integers for grid spans + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + } else if grid_line.ident == atom!("") { + // integer could be omitted + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + } + + Ok(grid_line) + } +} + +/// A track breadth for explicit grid track sizing. It's generic solely to +/// avoid re-implementing it for the computed type. +/// +/// +#[derive( + Animate, + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericTrackBreadth { + /// The generic type is almost always a non-negative `` + Breadth(L), + /// A flex fraction specified in `fr` units. + #[css(dimension)] + Fr(CSSFloat), + /// `auto` + Auto, + /// `min-content` + MinContent, + /// `max-content` + MaxContent, +} + +pub use self::GenericTrackBreadth as TrackBreadth; + +impl TrackBreadth { + /// Check whether this is a `` (i.e., it only has ``) + /// + /// + #[inline] + pub fn is_fixed(&self) -> bool { + matches!(*self, TrackBreadth::Breadth(..)) + } +} + +/// A `` type for explicit grid track sizing. Like ``, this is +/// generic only to avoid code bloat. It only takes `` +/// +/// +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericTrackSize { + /// A flexible `` + Breadth(GenericTrackBreadth), + /// A `minmax` function for a range over an inflexible `` + /// and a flexible `` + /// + /// + #[css(function)] + Minmax(GenericTrackBreadth, GenericTrackBreadth), + /// A `fit-content` function. + /// + /// This stores a TrackBreadth for convenience, but it can only be a + /// LengthPercentage. + /// + /// + #[css(function)] + FitContent(GenericTrackBreadth), +} + +pub use self::GenericTrackSize as TrackSize; + +impl TrackSize { + /// The initial value. + const INITIAL_VALUE: Self = TrackSize::Breadth(TrackBreadth::Auto); + + /// Returns the initial value. + pub const fn initial_value() -> Self { + Self::INITIAL_VALUE + } + + /// Returns true if `self` is the initial value. + pub fn is_initial(&self) -> bool { + matches!(*self, TrackSize::Breadth(TrackBreadth::Auto)) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585 + } + + /// Check whether this is a `` + /// + /// + pub fn is_fixed(&self) -> bool { + match *self { + TrackSize::Breadth(ref breadth) => breadth.is_fixed(), + // For minmax function, it could be either + // minmax(, ) or minmax(, ), + // and since both variants are a subset of minmax(, ), we only + // need to make sure that they're fixed. So, we don't have to modify the parsing function. + TrackSize::Minmax(ref breadth_1, ref breadth_2) => { + if breadth_1.is_fixed() { + return true; // the second value is always a + } + + match *breadth_1 { + TrackBreadth::Fr(_) => false, // should be at this point + _ => breadth_2.is_fixed(), + } + }, + TrackSize::FitContent(_) => false, + } + } +} + +impl Default for TrackSize { + fn default() -> Self { + Self::initial_value() + } +} + +impl ToCss for TrackSize { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + match *self { + TrackSize::Breadth(ref breadth) => breadth.to_css(dest), + TrackSize::Minmax(ref min, ref max) => { + // According to gecko minmax(auto, ) is equivalent to , + // and both are serialized as . + if let TrackBreadth::Auto = *min { + if let TrackBreadth::Fr(_) = *max { + return max.to_css(dest); + } + } + + dest.write_str("minmax(")?; + min.to_css(dest)?; + dest.write_str(", ")?; + max.to_css(dest)?; + dest.write_char(')') + }, + TrackSize::FitContent(ref lp) => { + dest.write_str("fit-content(")?; + lp.to_css(dest)?; + dest.write_char(')') + }, + } + } +} + +/// A `+`. +/// We use the empty slice as `auto`, and always parse `auto` as an empty slice. +/// This means it's impossible to have a slice containing only one auto item. +#[derive( + Clone, + Debug, + Default, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(transparent)] +pub struct GenericImplicitGridTracks( + #[css(if_empty = "auto", iterable)] pub crate::OwnedSlice, +); + +pub use self::GenericImplicitGridTracks as ImplicitGridTracks; + +impl ImplicitGridTracks { + /// Returns true if current value is same as its initial value (i.e. auto). + pub fn is_initial(&self) -> bool { + debug_assert_ne!( + *self, + ImplicitGridTracks(crate::OwnedSlice::from(vec![Default::default()])) + ); + self.0.is_empty() + } +} + +/// Helper function for serializing identifiers with a prefix and suffix, used +/// for serializing (in grid). +pub fn concat_serialize_idents( + prefix: &str, + suffix: &str, + slice: &[CustomIdent], + sep: &str, + dest: &mut CssWriter, +) -> fmt::Result +where + W: Write, +{ + if let Some((ref first, rest)) = slice.split_first() { + dest.write_str(prefix)?; + first.to_css(dest)?; + for thing in rest { + dest.write_str(sep)?; + thing.to_css(dest)?; + } + + dest.write_str(suffix)?; + } + + Ok(()) +} + +/// The initial argument of the `repeat` function. +/// +/// +#[derive( + Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, +)] +#[repr(C, u8)] +pub enum RepeatCount { + /// A positive integer. This is allowed only for `` and `` + Number(Integer), + /// An `` keyword allowed only for `` + AutoFill, + /// An `` keyword allowed only for `` + AutoFit, +} + +impl Parse for RepeatCount { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + if let Ok(mut i) = input.try_parse(|i| specified::Integer::parse_positive(context, i)) { + if i.value() > MAX_GRID_LINE { + i = specified::Integer::new(MAX_GRID_LINE); + } + return Ok(RepeatCount::Number(i)); + } + try_match_ident_ignore_ascii_case! { input, + "auto-fill" => Ok(RepeatCount::AutoFill), + "auto-fit" => Ok(RepeatCount::AutoFit), + } + } +} + +/// The structure containing `` and `` values. +/// +/// It can also hold `repeat()` function parameters, which expands into the respective +/// values in its computed form. +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[css(function = "repeat")] +#[repr(C)] +pub struct GenericTrackRepeat { + /// The number of times for the value to be repeated (could also be `auto-fit` or `auto-fill`) + pub count: RepeatCount, + /// `` accompanying `` values. + /// + /// If there's no ``, then it's represented by an empty vector. + /// For N `` values, there will be N+1 ``, and so this vector's + /// length is always one value more than that of the ``. + pub line_names: crate::OwnedSlice>, + /// `` values. + pub track_sizes: crate::OwnedSlice>, +} + +pub use self::GenericTrackRepeat as TrackRepeat; + +impl ToCss for TrackRepeat { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + dest.write_str("repeat(")?; + self.count.to_css(dest)?; + dest.write_str(", ")?; + + let mut line_names_iter = self.line_names.iter(); + for (i, (ref size, ref names)) in self + .track_sizes + .iter() + .zip(&mut line_names_iter) + .enumerate() + { + if i > 0 { + dest.write_char(' ')?; + } + + concat_serialize_idents("[", "] ", names, " ", dest)?; + size.to_css(dest)?; + } + + if let Some(line_names_last) = line_names_iter.next() { + concat_serialize_idents(" [", "]", line_names_last, " ", dest)?; + } + + dest.write_char(')')?; + + Ok(()) + } +} + +/// Track list values. Can be or +#[derive( + Animate, + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericTrackListValue { + /// A value. + TrackSize(#[animation(field_bound)] GenericTrackSize), + /// A value. + TrackRepeat(#[animation(field_bound)] GenericTrackRepeat), +} + +pub use self::GenericTrackListValue as TrackListValue; + +impl TrackListValue { + // FIXME: can't use TrackSize::initial_value() here b/c rustc error "is not yet stable as a const fn" + const INITIAL_VALUE: Self = TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto)); + + fn is_repeat(&self) -> bool { + matches!(*self, TrackListValue::TrackRepeat(..)) + } + + /// Returns true if `self` is the initial value. + pub fn is_initial(&self) -> bool { + matches!( + *self, + TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto)) + ) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585 + } +} + +impl Default for TrackListValue { + #[inline] + fn default() -> Self { + Self::INITIAL_VALUE + } +} + +/// A grid `` type. +/// +/// +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericTrackList { + /// The index in `values` where our `` value is, if in bounds. + #[css(skip)] + pub auto_repeat_index: usize, + /// A vector of ` | ` values. + pub values: crate::OwnedSlice>, + /// `` accompanying ` | ` values. + /// + /// If there's no ``, then it's represented by an empty vector. + /// For N values, there will be N+1 ``, and so this vector's + /// length is always one value more than that of the ``. + pub line_names: crate::OwnedSlice>, +} + +pub use self::GenericTrackList as TrackList; + +impl TrackList { + /// Whether this track list is an explicit track list (that is, doesn't have + /// any repeat values). + pub fn is_explicit(&self) -> bool { + !self.values.iter().any(|v| v.is_repeat()) + } + + /// Whether this track list has an `` value. + pub fn has_auto_repeat(&self) -> bool { + self.auto_repeat_index < self.values.len() + } +} + +impl ToCss for TrackList { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + let mut values_iter = self.values.iter().peekable(); + let mut line_names_iter = self.line_names.iter().peekable(); + + for idx in 0.. { + let names = line_names_iter.next().unwrap(); // This should exist! + concat_serialize_idents("[", "]", names, " ", dest)?; + + match values_iter.next() { + Some(value) => { + if !names.is_empty() { + dest.write_char(' ')?; + } + + value.to_css(dest)?; + }, + None => break, + } + + if values_iter.peek().is_some() || + line_names_iter.peek().map_or(false, |v| !v.is_empty()) || + (idx + 1 == self.auto_repeat_index) + { + dest.write_char(' ')?; + } + } + + Ok(()) + } +} + +/// The `` for subgrids. +/// +/// `subgrid [ | repeat( | auto-fill, +) ]+` +/// +/// https://drafts.csswg.org/css-grid-2/#typedef-line-name-list +#[derive( + Clone, + Debug, + Default, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct LineNameList { + /// The optional `` + pub names: crate::OwnedSlice>, + /// Indicates the starting line names that requires `auto-fill`, if in bounds. + pub fill_start: usize, + /// Indicates the number of line names in the auto-fill + pub fill_len: usize, +} + +impl Parse for LineNameList { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + input.expect_ident_matching("subgrid")?; + let mut line_names = vec![]; + let mut fill_data = None; + // Rather than truncating the result after inserting values, just + // have a maximum number of values. This gives us an early out on very + // large name lists, but more importantly prevents OOM on huge repeat + // expansions. (bug 1583429) + let mut max_remaining = MAX_GRID_LINE as usize; + + loop { + let repeat_parse_result = input.try_parse(|input| { + input.expect_function_matching("repeat")?; + input.parse_nested_block(|input| { + let count = RepeatCount::parse(context, input)?; + input.expect_comma()?; + let mut names_list = vec![]; + names_list.push(parse_line_names(input)?); // there should be at least one + while let Ok(names) = input.try_parse(parse_line_names) { + names_list.push(names); + } + Ok((names_list, count)) + }) + }); + if let Ok((names_list, count)) = repeat_parse_result { + let mut handle_size = |n| { + let n = cmp::min(n, max_remaining); + max_remaining -= n; + n + }; + match count { + // FIXME(emilio): we shouldn't expand repeat() at + // parse time for subgrid. (bug 1583429) + RepeatCount::Number(num) => { + let n = handle_size(num.value() as usize * names_list.len()); + line_names.extend(names_list.iter().cloned().cycle().take(n)); + }, + RepeatCount::AutoFill if fill_data.is_none() => { + let fill_idx = line_names.len(); + let fill_len = names_list.len(); + fill_data = Some((fill_idx, fill_len)); + let n = handle_size(fill_len); + line_names.extend(names_list.into_iter().take(n)); + }, + _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), + } + } else if let Ok(names) = input.try_parse(parse_line_names) { + if max_remaining > 0 { + line_names.push(names); + max_remaining -= 1; + } + } else { + break; + } + } + + debug_assert!(line_names.len() <= MAX_GRID_LINE as usize); + + let (fill_start, fill_len) = fill_data.unwrap_or((0, 0)); + + Ok(LineNameList { + names: line_names.into(), + fill_start: fill_start, + fill_len: fill_len, + }) + } +} + +impl ToCss for LineNameList { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + dest.write_str("subgrid")?; + let fill_start = self.fill_start; + let fill_len = self.fill_len; + for (i, names) in self.names.iter().enumerate() { + if fill_len > 0 && i == fill_start { + dest.write_str(" repeat(auto-fill,")?; + } + + dest.write_str(" [")?; + + if let Some((ref first, rest)) = names.split_first() { + first.to_css(dest)?; + for name in rest { + dest.write_char(' ')?; + name.to_css(dest)?; + } + } + + dest.write_char(']')?; + if fill_len > 0 && i == fill_start + fill_len - 1 { + dest.write_char(')')?; + } + } + + Ok(()) + } +} + +/// Variants for ` | ` +#[derive( + Animate, + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[value_info(other_values = "subgrid")] +#[repr(C, u8)] +pub enum GenericGridTemplateComponent { + /// `none` value. + None, + /// The grid `` + TrackList( + #[animation(field_bound)] + #[compute(field_bound)] + #[resolve(field_bound)] + #[shmem(field_bound)] + Box>, + ), + /// A `subgrid ?` + /// TODO: Support animations for this after subgrid is addressed in [grid-2] spec. + #[animation(error)] + Subgrid(Box), + /// `masonry` value. + /// https://github.com/w3c/csswg-drafts/issues/4650 + Masonry, +} + +pub use self::GenericGridTemplateComponent as GridTemplateComponent; + +impl GridTemplateComponent { + /// The initial value. + const INITIAL_VALUE: Self = Self::None; + + /// Returns length of the s + pub fn track_list_len(&self) -> usize { + match *self { + GridTemplateComponent::TrackList(ref tracklist) => tracklist.values.len(), + _ => 0, + } + } + + /// Returns true if `self` is the initial value. + pub fn is_initial(&self) -> bool { + matches!(*self, Self::None) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585 + } +} + +impl Default for GridTemplateComponent { + #[inline] + fn default() -> Self { + Self::INITIAL_VALUE + } +} -- cgit v1.2.3