style/values/specified/
grid.rs

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
8use crate::derives::*;
9use crate::parser::{Parse, ParserContext};
10use crate::values::generics::grid::{GridTemplateComponent, ImplicitGridTracks, RepeatCount};
11use crate::values::generics::grid::{LineNameList, LineNameListValue, NameRepeat, TrackBreadth};
12use crate::values::generics::grid::{TrackList, TrackListValue, TrackRepeat, TrackSize};
13use crate::values::specified::{Integer, LengthPercentage};
14use crate::values::{CSSFloat, CustomIdent};
15use cssparser::{Parser, Token};
16use std::mem;
17use style_traits::{ParseError, StyleParseErrorKind};
18
19/// Parse a single flexible length.
20pub fn parse_flex<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CSSFloat, ParseError<'i>> {
21    let location = input.current_source_location();
22    match *input.next()? {
23        Token::Dimension {
24            value, ref unit, ..
25        } if unit.eq_ignore_ascii_case("fr") && value.is_sign_positive() => Ok(value),
26        ref t => Err(location.new_unexpected_token_error(t.clone())),
27    }
28}
29
30impl<L> TrackBreadth<L> {
31    fn parse_keyword<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
32        #[derive(Parse)]
33        enum TrackKeyword {
34            Auto,
35            MaxContent,
36            MinContent,
37        }
38
39        Ok(match TrackKeyword::parse(input)? {
40            TrackKeyword::Auto => TrackBreadth::Auto,
41            TrackKeyword::MaxContent => TrackBreadth::MaxContent,
42            TrackKeyword::MinContent => TrackBreadth::MinContent,
43        })
44    }
45}
46
47impl Parse for TrackBreadth<LengthPercentage> {
48    fn parse<'i, 't>(
49        context: &ParserContext,
50        input: &mut Parser<'i, 't>,
51    ) -> Result<Self, ParseError<'i>> {
52        // FIXME: This and other callers in this file should use
53        // NonNegativeLengthPercentage instead.
54        //
55        // Though it seems these cannot be animated so it's ~ok.
56        if let Ok(lp) = input.try_parse(|i| LengthPercentage::parse_non_negative(context, i)) {
57            return Ok(TrackBreadth::Breadth(lp));
58        }
59
60        if let Ok(f) = input.try_parse(parse_flex) {
61            return Ok(TrackBreadth::Fr(f));
62        }
63
64        Self::parse_keyword(input)
65    }
66}
67
68impl Parse for TrackSize<LengthPercentage> {
69    fn parse<'i, 't>(
70        context: &ParserContext,
71        input: &mut Parser<'i, 't>,
72    ) -> Result<Self, ParseError<'i>> {
73        if let Ok(b) = input.try_parse(|i| TrackBreadth::parse(context, i)) {
74            return Ok(TrackSize::Breadth(b));
75        }
76
77        if input
78            .try_parse(|i| i.expect_function_matching("minmax"))
79            .is_ok()
80        {
81            return input.parse_nested_block(|input| {
82                let inflexible_breadth =
83                    match input.try_parse(|i| LengthPercentage::parse_non_negative(context, i)) {
84                        Ok(lp) => TrackBreadth::Breadth(lp),
85                        Err(..) => TrackBreadth::parse_keyword(input)?,
86                    };
87
88                input.expect_comma()?;
89                Ok(TrackSize::Minmax(
90                    inflexible_breadth,
91                    TrackBreadth::parse(context, input)?,
92                ))
93            });
94        }
95
96        input.expect_function_matching("fit-content")?;
97        let lp = input.parse_nested_block(|i| LengthPercentage::parse_non_negative(context, i))?;
98        Ok(TrackSize::FitContent(TrackBreadth::Breadth(lp)))
99    }
100}
101
102impl Parse for ImplicitGridTracks<TrackSize<LengthPercentage>> {
103    fn parse<'i, 't>(
104        context: &ParserContext,
105        input: &mut Parser<'i, 't>,
106    ) -> Result<Self, ParseError<'i>> {
107        use style_traits::{Separator, Space};
108        let track_sizes = Space::parse(input, |i| TrackSize::parse(context, i))?;
109        if track_sizes.len() == 1 && track_sizes[0].is_initial() {
110            // A single track with the initial value is always represented by an empty slice.
111            return Ok(Default::default());
112        }
113        return Ok(ImplicitGridTracks(track_sizes.into()));
114    }
115}
116
117/// Parse the grid line names into a vector of owned strings.
118///
119/// <https://drafts.csswg.org/css-grid/#typedef-line-names>
120pub fn parse_line_names<'i, 't>(
121    input: &mut Parser<'i, 't>,
122) -> Result<crate::OwnedSlice<CustomIdent>, ParseError<'i>> {
123    input.expect_square_bracket_block()?;
124    input.parse_nested_block(|input| {
125        let mut values = vec![];
126        while let Ok(ident) = input.try_parse(|i| CustomIdent::parse(i, &["span", "auto"])) {
127            values.push(ident);
128        }
129
130        Ok(values.into())
131    })
132}
133
134/// The type of `repeat` function (only used in parsing).
135///
136/// <https://drafts.csswg.org/css-grid/#typedef-track-repeat>
137#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo)]
138#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
139enum RepeatType {
140    /// [`<auto-repeat>`](https://drafts.csswg.org/css-grid/#typedef-auto-repeat)
141    Auto,
142    /// [`<track-repeat>`](https://drafts.csswg.org/css-grid/#typedef-track-repeat)
143    Normal,
144    /// [`<fixed-repeat>`](https://drafts.csswg.org/css-grid/#typedef-fixed-repeat)
145    Fixed,
146}
147
148impl TrackRepeat<LengthPercentage, Integer> {
149    fn parse_with_repeat_type<'i, 't>(
150        context: &ParserContext,
151        input: &mut Parser<'i, 't>,
152    ) -> Result<(Self, RepeatType), ParseError<'i>> {
153        input
154            .try_parse(|i| i.expect_function_matching("repeat").map_err(|e| e.into()))
155            .and_then(|_| {
156                input.parse_nested_block(|input| {
157                    let count = RepeatCount::parse(context, input)?;
158                    input.expect_comma()?;
159
160                    let is_auto = count == RepeatCount::AutoFit || count == RepeatCount::AutoFill;
161                    let mut repeat_type = if is_auto {
162                        RepeatType::Auto
163                    } else {
164                        // <fixed-size> is a subset of <track-size>, so it should work for both
165                        RepeatType::Fixed
166                    };
167
168                    let mut names = vec![];
169                    let mut values = vec![];
170                    let mut current_names;
171
172                    loop {
173                        current_names = input.try_parse(parse_line_names).unwrap_or_default();
174                        if let Ok(track_size) = input.try_parse(|i| TrackSize::parse(context, i)) {
175                            if !track_size.is_fixed() {
176                                if is_auto {
177                                    // should be <fixed-size> for <auto-repeat>
178                                    return Err(input
179                                        .new_custom_error(StyleParseErrorKind::UnspecifiedError));
180                                }
181
182                                if repeat_type == RepeatType::Fixed {
183                                    repeat_type = RepeatType::Normal // <track-size> for sure
184                                }
185                            }
186
187                            values.push(track_size);
188                            names.push(current_names);
189                        } else {
190                            if values.is_empty() {
191                                // expecting at least one <track-size>
192                                return Err(
193                                    input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
194                                );
195                            }
196
197                            names.push(current_names); // final `<line-names>`
198                            break; // no more <track-size>, breaking
199                        }
200                    }
201
202                    let repeat = TrackRepeat {
203                        count,
204                        track_sizes: values.into(),
205                        line_names: names.into(),
206                    };
207
208                    Ok((repeat, repeat_type))
209                })
210            })
211    }
212}
213
214impl Parse for TrackList<LengthPercentage, Integer> {
215    fn parse<'i, 't>(
216        context: &ParserContext,
217        input: &mut Parser<'i, 't>,
218    ) -> Result<Self, ParseError<'i>> {
219        let mut current_names = vec![];
220        let mut names = vec![];
221        let mut values = vec![];
222
223        // Whether we've parsed an `<auto-repeat>` value.
224        let mut auto_repeat_index = None;
225        // assume that everything is <fixed-size>. This flag is useful when we encounter <auto-repeat>
226        let mut at_least_one_not_fixed = false;
227        loop {
228            current_names
229                .extend_from_slice(&mut input.try_parse(parse_line_names).unwrap_or_default());
230            if let Ok(track_size) = input.try_parse(|i| TrackSize::parse(context, i)) {
231                if !track_size.is_fixed() {
232                    at_least_one_not_fixed = true;
233                    if auto_repeat_index.is_some() {
234                        // <auto-track-list> only accepts <fixed-size> and <fixed-repeat>
235                        return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
236                    }
237                }
238
239                let vec = mem::replace(&mut current_names, vec![]);
240                names.push(vec.into());
241                values.push(TrackListValue::TrackSize(track_size));
242            } else if let Ok((repeat, type_)) =
243                input.try_parse(|i| TrackRepeat::parse_with_repeat_type(context, i))
244            {
245                match type_ {
246                    RepeatType::Normal => {
247                        at_least_one_not_fixed = true;
248                        if auto_repeat_index.is_some() {
249                            // only <fixed-repeat>
250                            return Err(
251                                input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
252                            );
253                        }
254                    },
255                    RepeatType::Auto => {
256                        if auto_repeat_index.is_some() || at_least_one_not_fixed {
257                            // We've either seen <auto-repeat> earlier, or there's at least one non-fixed value
258                            return Err(
259                                input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
260                            );
261                        }
262                        auto_repeat_index = Some(values.len());
263                    },
264                    RepeatType::Fixed => {},
265                }
266
267                let vec = mem::replace(&mut current_names, vec![]);
268                names.push(vec.into());
269                values.push(TrackListValue::TrackRepeat(repeat));
270            } else {
271                if values.is_empty() && auto_repeat_index.is_none() {
272                    return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
273                }
274
275                names.push(current_names.into());
276                break;
277            }
278        }
279
280        Ok(TrackList {
281            auto_repeat_index: auto_repeat_index.unwrap_or(std::usize::MAX),
282            values: values.into(),
283            line_names: names.into(),
284        })
285    }
286}
287
288#[cfg(feature = "gecko")]
289#[inline]
290fn allow_grid_template_subgrids() -> bool {
291    true
292}
293
294#[cfg(feature = "servo")]
295#[inline]
296fn allow_grid_template_subgrids() -> bool {
297    false
298}
299
300#[cfg(feature = "gecko")]
301#[inline]
302fn allow_grid_template_masonry() -> bool {
303    static_prefs::pref!("layout.css.grid-template-masonry-value.enabled")
304}
305
306#[cfg(feature = "servo")]
307#[inline]
308fn allow_grid_template_masonry() -> bool {
309    false
310}
311
312impl Parse for GridTemplateComponent<LengthPercentage, Integer> {
313    fn parse<'i, 't>(
314        context: &ParserContext,
315        input: &mut Parser<'i, 't>,
316    ) -> Result<Self, ParseError<'i>> {
317        if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
318            return Ok(GridTemplateComponent::None);
319        }
320
321        Self::parse_without_none(context, input)
322    }
323}
324
325impl GridTemplateComponent<LengthPercentage, Integer> {
326    /// Parses a `GridTemplateComponent<LengthPercentage>` except `none` keyword.
327    pub fn parse_without_none<'i, 't>(
328        context: &ParserContext,
329        input: &mut Parser<'i, 't>,
330    ) -> Result<Self, ParseError<'i>> {
331        if allow_grid_template_subgrids() {
332            if let Ok(t) = input.try_parse(|i| LineNameList::parse(context, i)) {
333                return Ok(GridTemplateComponent::Subgrid(Box::new(t)));
334            }
335        }
336        if allow_grid_template_masonry() {
337            if input
338                .try_parse(|i| i.expect_ident_matching("masonry"))
339                .is_ok()
340            {
341                return Ok(GridTemplateComponent::Masonry);
342            }
343        }
344        let track_list = TrackList::parse(context, input)?;
345        Ok(GridTemplateComponent::TrackList(Box::new(track_list)))
346    }
347}
348
349impl Parse for NameRepeat<Integer> {
350    fn parse<'i, 't>(
351        context: &ParserContext,
352        input: &mut Parser<'i, 't>,
353    ) -> Result<Self, ParseError<'i>> {
354        input.expect_function_matching("repeat")?;
355        input.parse_nested_block(|i| {
356            let count = RepeatCount::parse(context, i)?;
357            // NameRepeat doesn't accept `auto-fit`
358            // https://drafts.csswg.org/css-grid/#typedef-name-repeat
359            if matches!(count, RepeatCount::AutoFit) {
360                return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError));
361            }
362
363            i.expect_comma()?;
364            let mut names_list = vec![];
365            names_list.push(parse_line_names(i)?); // there should be at least one
366            while let Ok(names) = i.try_parse(parse_line_names) {
367                names_list.push(names);
368            }
369
370            Ok(NameRepeat {
371                count,
372                line_names: names_list.into(),
373            })
374        })
375    }
376}
377
378impl Parse for LineNameListValue<Integer> {
379    fn parse<'i, 't>(
380        context: &ParserContext,
381        input: &mut Parser<'i, 't>,
382    ) -> Result<Self, ParseError<'i>> {
383        if let Ok(repeat) = input.try_parse(|i| NameRepeat::parse(context, i)) {
384            return Ok(LineNameListValue::Repeat(repeat));
385        }
386
387        parse_line_names(input).map(LineNameListValue::LineNames)
388    }
389}
390
391impl LineNameListValue<Integer> {
392    /// Returns the length of `<line-names>` after expanding repeat(N, ...). This returns zero for
393    /// repeat(auto-fill, ...).
394    #[inline]
395    pub fn line_names_length(&self) -> usize {
396        match *self {
397            Self::LineNames(..) => 1,
398            Self::Repeat(ref r) => {
399                match r.count {
400                    // Note: RepeatCount is always >= 1.
401                    RepeatCount::Number(v) => r.line_names.len() * v.value() as usize,
402                    _ => 0,
403                }
404            },
405        }
406    }
407}
408
409impl Parse for LineNameList<Integer> {
410    fn parse<'i, 't>(
411        context: &ParserContext,
412        input: &mut Parser<'i, 't>,
413    ) -> Result<Self, ParseError<'i>> {
414        input.expect_ident_matching("subgrid")?;
415
416        let mut auto_repeat = false;
417        let mut expanded_line_names_length = 0;
418        let mut line_names = vec![];
419        while let Ok(value) = input.try_parse(|i| LineNameListValue::parse(context, i)) {
420            match value {
421                LineNameListValue::Repeat(ref r) if r.is_auto_fill() => {
422                    if auto_repeat {
423                        // On a subgridded axis, the auto-fill keyword is only valid once per
424                        // <line-name-list>.
425                        // https://drafts.csswg.org/css-grid/#auto-repeat
426                        return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
427                    }
428                    auto_repeat = true;
429                },
430                _ => (),
431            };
432
433            expanded_line_names_length += value.line_names_length();
434            line_names.push(value);
435        }
436
437        Ok(LineNameList {
438            expanded_line_names_length,
439            line_names: line_names.into(),
440        })
441    }
442}