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