1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4 
5 //! CSS handling for the computed value of
6 //! [grids](https://drafts.csswg.org/css-grid/)
7 
8 use crate::parser::{Parse, ParserContext};
9 use crate::values::generics::grid::{GridTemplateComponent, ImplicitGridTracks, RepeatCount};
10 use crate::values::generics::grid::{LineNameList, TrackBreadth, TrackRepeat, TrackSize};
11 use crate::values::generics::grid::{TrackList, TrackListValue};
12 use crate::values::specified::{Integer, LengthPercentage};
13 use crate::values::{CSSFloat, CustomIdent};
14 use cssparser::{ParseError as CssParseError, Parser, Token};
15 use std::mem;
16 use style_traits::{ParseError, StyleParseErrorKind};
17 
18 /// Parse a single flexible length.
parse_flex<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CSSFloat, ParseError<'i>>19 pub fn parse_flex<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CSSFloat, ParseError<'i>> {
20     let location = input.current_source_location();
21     match *input.next()? {
22         Token::Dimension {
23             value, ref unit, ..
24         } if unit.eq_ignore_ascii_case("fr") && value.is_sign_positive() => Ok(value),
25         ref t => Err(location.new_unexpected_token_error(t.clone())),
26     }
27 }
28 
29 impl<L> TrackBreadth<L> {
parse_keyword<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>>30     fn parse_keyword<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
31         #[derive(Parse)]
32         enum TrackKeyword {
33             Auto,
34             MaxContent,
35             MinContent,
36         }
37 
38         Ok(match TrackKeyword::parse(input)? {
39             TrackKeyword::Auto => TrackBreadth::Auto,
40             TrackKeyword::MaxContent => TrackBreadth::MaxContent,
41             TrackKeyword::MinContent => TrackBreadth::MinContent,
42         })
43     }
44 }
45 
46 impl Parse for TrackBreadth<LengthPercentage> {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>47     fn parse<'i, 't>(
48         context: &ParserContext,
49         input: &mut Parser<'i, 't>,
50     ) -> Result<Self, ParseError<'i>> {
51         // FIXME: This and other callers in this file should use
52         // NonNegativeLengthPercentage instead.
53         //
54         // Though it seems these cannot be animated so it's ~ok.
55         if let Ok(lp) = input.try(|i| LengthPercentage::parse_non_negative(context, i)) {
56             return Ok(TrackBreadth::Breadth(lp));
57         }
58 
59         if let Ok(f) = input.try(parse_flex) {
60             return Ok(TrackBreadth::Fr(f));
61         }
62 
63         Self::parse_keyword(input)
64     }
65 }
66 
67 impl Parse for TrackSize<LengthPercentage> {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>68     fn parse<'i, 't>(
69         context: &ParserContext,
70         input: &mut Parser<'i, 't>,
71     ) -> Result<Self, ParseError<'i>> {
72         if let Ok(b) = input.try(|i| TrackBreadth::parse(context, i)) {
73             return Ok(TrackSize::Breadth(b));
74         }
75 
76         if input.try(|i| i.expect_function_matching("minmax")).is_ok() {
77             return input.parse_nested_block(|input| {
78                 let inflexible_breadth =
79                     match input.try(|i| LengthPercentage::parse_non_negative(context, i)) {
80                         Ok(lp) => TrackBreadth::Breadth(lp),
81                         Err(..) => TrackBreadth::parse_keyword(input)?,
82                     };
83 
84                 input.expect_comma()?;
85                 Ok(TrackSize::Minmax(
86                     inflexible_breadth,
87                     TrackBreadth::parse(context, input)?,
88                 ))
89             });
90         }
91 
92         input.expect_function_matching("fit-content")?;
93         let lp = input.parse_nested_block(|i| LengthPercentage::parse_non_negative(context, i))?;
94         Ok(TrackSize::FitContent(TrackBreadth::Breadth(lp)))
95     }
96 }
97 
98 impl Parse for ImplicitGridTracks<TrackSize<LengthPercentage>> {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>99     fn parse<'i, 't>(
100         context: &ParserContext,
101         input: &mut Parser<'i, 't>,
102     ) -> Result<Self, ParseError<'i>> {
103         use style_traits::{Separator, Space};
104         let track_sizes = Space::parse(input, |i| TrackSize::parse(context, i))?;
105         if track_sizes.len() == 1 && track_sizes[0].is_initial() {
106             // A single track with the initial value is always represented by an empty slice.
107             return Ok(Default::default());
108         }
109         return Ok(ImplicitGridTracks(track_sizes.into()));
110     }
111 }
112 
113 /// Parse the grid line names into a vector of owned strings.
114 ///
115 /// <https://drafts.csswg.org/css-grid/#typedef-line-names>
parse_line_names<'i, 't>( input: &mut Parser<'i, 't>, ) -> Result<crate::OwnedSlice<CustomIdent>, ParseError<'i>>116 pub fn parse_line_names<'i, 't>(
117     input: &mut Parser<'i, 't>,
118 ) -> Result<crate::OwnedSlice<CustomIdent>, ParseError<'i>> {
119     input.expect_square_bracket_block()?;
120     input.parse_nested_block(|input| {
121         let mut values = vec![];
122         while let Ok((loc, ident)) = input.try(|i| -> Result<_, CssParseError<()>> {
123             Ok((i.current_source_location(), i.expect_ident_cloned()?))
124         }) {
125             let ident = CustomIdent::from_ident(loc, &ident, &["span", "auto"])?;
126             values.push(ident);
127         }
128 
129         Ok(values.into())
130     })
131 }
132 
133 /// The type of `repeat` function (only used in parsing).
134 ///
135 /// <https://drafts.csswg.org/css-grid/#typedef-track-repeat>
136 #[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo)]
137 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
138 enum RepeatType {
139     /// [`<auto-repeat>`](https://drafts.csswg.org/css-grid/#typedef-auto-repeat)
140     Auto,
141     /// [`<track-repeat>`](https://drafts.csswg.org/css-grid/#typedef-track-repeat)
142     Normal,
143     /// [`<fixed-repeat>`](https://drafts.csswg.org/css-grid/#typedef-fixed-repeat)
144     Fixed,
145 }
146 
147 impl TrackRepeat<LengthPercentage, Integer> {
parse_with_repeat_type<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<(Self, RepeatType), ParseError<'i>>148     fn parse_with_repeat_type<'i, 't>(
149         context: &ParserContext,
150         input: &mut Parser<'i, 't>,
151     ) -> Result<(Self, RepeatType), ParseError<'i>> {
152         input
153             .try(|i| i.expect_function_matching("repeat").map_err(|e| e.into()))
154             .and_then(|_| {
155                 input.parse_nested_block(|input| {
156                     let count = RepeatCount::parse(context, input)?;
157                     input.expect_comma()?;
158 
159                     let is_auto = count == RepeatCount::AutoFit || count == RepeatCount::AutoFill;
160                     let mut repeat_type = if is_auto {
161                         RepeatType::Auto
162                     } else {
163                         // <fixed-size> is a subset of <track-size>, so it should work for both
164                         RepeatType::Fixed
165                     };
166 
167                     let mut names = vec![];
168                     let mut values = vec![];
169                     let mut current_names;
170 
171                     loop {
172                         current_names = input.try(parse_line_names).unwrap_or_default();
173                         if let Ok(track_size) = input.try(|i| TrackSize::parse(context, i)) {
174                             if !track_size.is_fixed() {
175                                 if is_auto {
176                                     // should be <fixed-size> for <auto-repeat>
177                                     return Err(input
178                                         .new_custom_error(StyleParseErrorKind::UnspecifiedError));
179                                 }
180 
181                                 if repeat_type == RepeatType::Fixed {
182                                     repeat_type = RepeatType::Normal // <track-size> for sure
183                                 }
184                             }
185 
186                             values.push(track_size);
187                             names.push(current_names);
188                         } else {
189                             if values.is_empty() {
190                                 // expecting at least one <track-size>
191                                 return Err(
192                                     input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
193                                 );
194                             }
195 
196                             names.push(current_names); // final `<line-names>`
197                             break; // no more <track-size>, breaking
198                         }
199                     }
200 
201                     let repeat = TrackRepeat {
202                         count,
203                         track_sizes: values.into(),
204                         line_names: names.into(),
205                     };
206 
207                     Ok((repeat, repeat_type))
208                 })
209             })
210     }
211 }
212 
213 impl Parse for TrackList<LengthPercentage, Integer> {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>214     fn parse<'i, 't>(
215         context: &ParserContext,
216         input: &mut Parser<'i, 't>,
217     ) -> Result<Self, ParseError<'i>> {
218         let mut current_names = vec![];
219         let mut names = vec![];
220         let mut values = vec![];
221 
222         // Whether we've parsed an `<auto-repeat>` value.
223         let mut auto_repeat_index = None;
224         // assume that everything is <fixed-size>. This flag is useful when we encounter <auto-repeat>
225         let mut at_least_one_not_fixed = false;
226         loop {
227             current_names.extend_from_slice(&mut input.try(parse_line_names).unwrap_or_default());
228             if let Ok(track_size) = input.try(|i| TrackSize::parse(context, i)) {
229                 if !track_size.is_fixed() {
230                     at_least_one_not_fixed = true;
231                     if auto_repeat_index.is_some() {
232                         // <auto-track-list> only accepts <fixed-size> and <fixed-repeat>
233                         return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
234                     }
235                 }
236 
237                 let vec = mem::replace(&mut current_names, vec![]);
238                 names.push(vec.into());
239                 values.push(TrackListValue::TrackSize(track_size));
240             } else if let Ok((repeat, type_)) =
241                 input.try(|i| TrackRepeat::parse_with_repeat_type(context, i))
242             {
243                 match type_ {
244                     RepeatType::Normal => {
245                         at_least_one_not_fixed = true;
246                         if auto_repeat_index.is_some() {
247                             // only <fixed-repeat>
248                             return Err(
249                                 input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
250                             );
251                         }
252                     },
253                     RepeatType::Auto => {
254                         if auto_repeat_index.is_some() || at_least_one_not_fixed {
255                             // We've either seen <auto-repeat> earlier, or there's at least one non-fixed value
256                             return Err(
257                                 input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
258                             );
259                         }
260                         auto_repeat_index = Some(values.len());
261                     },
262                     RepeatType::Fixed => {},
263                 }
264 
265                 let vec = mem::replace(&mut current_names, vec![]);
266                 names.push(vec.into());
267                 values.push(TrackListValue::TrackRepeat(repeat));
268             } else {
269                 if values.is_empty() && auto_repeat_index.is_none() {
270                     return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
271                 }
272 
273                 names.push(current_names.into());
274                 break;
275             }
276         }
277 
278         Ok(TrackList {
279             auto_repeat_index: auto_repeat_index.unwrap_or(std::usize::MAX),
280             values: values.into(),
281             line_names: names.into(),
282         })
283     }
284 }
285 
286 #[cfg(feature = "gecko")]
287 #[inline]
allow_grid_template_subgrids() -> bool288 fn allow_grid_template_subgrids() -> bool {
289     static_prefs::pref!("layout.css.grid-template-subgrid-value.enabled")
290 }
291 
292 #[cfg(feature = "servo")]
293 #[inline]
allow_grid_template_subgrids() -> bool294 fn allow_grid_template_subgrids() -> bool {
295     false
296 }
297 
298 #[cfg(feature = "gecko")]
299 #[inline]
allow_grid_template_masonry() -> bool300 fn allow_grid_template_masonry() -> bool {
301     static_prefs::pref!("layout.css.grid-template-masonry-value.enabled")
302 }
303 
304 #[cfg(feature = "servo")]
305 #[inline]
allow_grid_template_masonry() -> bool306 fn allow_grid_template_masonry() -> bool {
307     false
308 }
309 
310 impl Parse for GridTemplateComponent<LengthPercentage, Integer> {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>311     fn parse<'i, 't>(
312         context: &ParserContext,
313         input: &mut Parser<'i, 't>,
314     ) -> Result<Self, ParseError<'i>> {
315         if input.try(|i| i.expect_ident_matching("none")).is_ok() {
316             return Ok(GridTemplateComponent::None);
317         }
318 
319         Self::parse_without_none(context, input)
320     }
321 }
322 
323 impl GridTemplateComponent<LengthPercentage, Integer> {
324     /// Parses a `GridTemplateComponent<LengthPercentage>` except `none` keyword.
parse_without_none<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>325     pub fn parse_without_none<'i, 't>(
326         context: &ParserContext,
327         input: &mut Parser<'i, 't>,
328     ) -> Result<Self, ParseError<'i>> {
329         if allow_grid_template_subgrids() {
330             if let Ok(t) = input.try(|i| LineNameList::parse(context, i)) {
331                 return Ok(GridTemplateComponent::Subgrid(Box::new(t)));
332             }
333         }
334         if allow_grid_template_masonry() {
335             if input.try(|i| i.expect_ident_matching("masonry")).is_ok() {
336                 return Ok(GridTemplateComponent::Masonry);
337             }
338         }
339         let track_list = TrackList::parse(context, input)?;
340         Ok(GridTemplateComponent::TrackList(Box::new(track_list)))
341     }
342 }
343