grid.rs (16280B)
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::derives::*; 9 use crate::parser::{Parse, ParserContext}; 10 use crate::values::generics::grid::{GridTemplateComponent, ImplicitGridTracks, RepeatCount}; 11 use crate::values::generics::grid::{LineNameList, LineNameListValue, NameRepeat, TrackBreadth}; 12 use crate::values::generics::grid::{TrackList, TrackListValue, TrackRepeat, TrackSize}; 13 use crate::values::specified::{Integer, LengthPercentage}; 14 use crate::values::{CSSFloat, CustomIdent}; 15 use cssparser::{Parser, Token}; 16 use std::mem; 17 use style_traits::{ParseError, StyleParseErrorKind}; 18 19 /// Parse a single flexible length. 20 pub 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 30 impl<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 47 impl 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 68 impl 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 102 impl 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> 120 pub 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))] 139 enum 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 148 impl 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 214 impl 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] 290 fn allow_grid_template_subgrids() -> bool { 291 true 292 } 293 294 #[cfg(feature = "servo")] 295 #[inline] 296 fn allow_grid_template_subgrids() -> bool { 297 false 298 } 299 300 #[cfg(feature = "gecko")] 301 #[inline] 302 fn allow_grid_template_masonry() -> bool { 303 static_prefs::pref!("layout.css.grid-template-masonry-value.enabled") 304 } 305 306 #[cfg(feature = "servo")] 307 #[inline] 308 fn allow_grid_template_masonry() -> bool { 309 false 310 } 311 312 impl 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 325 impl 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 349 impl 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 378 impl 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 391 impl 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 409 impl 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 }