/* 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 http://mozilla.org/MPL/2.0/. */ //! CSS handling for the computed value of //! [grids](https://drafts.csswg.org/css-grid/) use cssparser::{Parser, Token, ParseError as CssParseError}; use parser::{Parse, ParserContext}; use std::ascii::AsciiExt; use std::mem; use style_traits::{ParseError, StyleParseErrorKind}; use values::{CSSFloat, CustomIdent}; use values::computed::{self, Context, ToComputedValue}; use values::generics::grid::{GridTemplateComponent, RepeatCount, TrackBreadth, TrackKeyword, TrackRepeat}; use values::generics::grid::{LineNameList, TrackSize, TrackList, TrackListType, TrackListValue}; use values::specified::{LengthOrPercentage, Integer}; /// Parse a single flexible length. pub fn parse_flex<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { let location = input.current_source_location(); match *input.next()? { Token::Dimension { value, ref unit, .. } if unit.eq_ignore_ascii_case("fr") && value.is_sign_positive() => Ok(value), ref t => Err(location.new_unexpected_token_error(t.clone())), } } impl Parse for TrackBreadth { fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { if let Ok(lop) = input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) { return Ok(TrackBreadth::Breadth(lop)) } if let Ok(f) = input.try(parse_flex) { return Ok(TrackBreadth::Flex(f)) } TrackKeyword::parse(input).map(TrackBreadth::Keyword) } } impl Parse for TrackSize { fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { if let Ok(b) = input.try(|i| TrackBreadth::parse(context, i)) { return Ok(TrackSize::Breadth(b)) } if input.try(|i| i.expect_function_matching("minmax")).is_ok() { return input.parse_nested_block(|input| { let inflexible_breadth = match input.try(|i| LengthOrPercentage::parse_non_negative(context, i)) { Ok(lop) => TrackBreadth::Breadth(lop), Err(..) => { let keyword = TrackKeyword::parse(input)?; TrackBreadth::Keyword(keyword) } }; input.expect_comma()?; Ok(TrackSize::Minmax(inflexible_breadth, TrackBreadth::parse(context, input)?)) }); } input.expect_function_matching("fit-content")?; let lop = input.parse_nested_block(|i| LengthOrPercentage::parse_non_negative(context, i))?; Ok(TrackSize::FitContent(lop)) } } /// Parse the grid line names into a vector of owned strings. /// /// pub fn parse_line_names<'i, 't>(input: &mut Parser<'i, 't>) -> Result, ParseError<'i>> { input.expect_square_bracket_block()?; input.parse_nested_block(|input| { let mut values = vec![]; while let Ok((loc, ident)) = input.try(|i| -> Result<_, CssParseError<()>> { Ok((i.current_source_location(), i.expect_ident_cloned()?)) }) { let ident = CustomIdent::from_ident(loc, &ident, &["span"])?; values.push(ident); } Ok(values.into_boxed_slice()) }) } /// The type of `repeat` function (only used in parsing). /// /// #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(MallocSizeOf))] enum RepeatType { /// [``](https://drafts.csswg.org/css-grid/#typedef-auto-repeat) Auto, /// [``](https://drafts.csswg.org/css-grid/#typedef-track-repeat) Normal, /// [``](https://drafts.csswg.org/css-grid/#typedef-fixed-repeat) Fixed, } impl TrackRepeat { fn parse_with_repeat_type<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<(Self, RepeatType), ParseError<'i>> { input.try(|i| i.expect_function_matching("repeat").map_err(|e| e.into())).and_then(|_| { input.parse_nested_block(|input| { let count = RepeatCount::parse(context, input)?; input.expect_comma()?; let is_auto = count == RepeatCount::AutoFit || count == RepeatCount::AutoFill; let mut repeat_type = if is_auto { RepeatType::Auto } else { // is a subset of , so it should work for both RepeatType::Fixed }; let mut names = vec![]; let mut values = vec![]; let mut current_names; loop { current_names = input.try(parse_line_names).unwrap_or(vec![].into_boxed_slice()); if let Ok(track_size) = input.try(|i| TrackSize::parse(context, i)) { if !track_size.is_fixed() { if is_auto { // should be for return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } if repeat_type == RepeatType::Fixed { repeat_type = RepeatType::Normal // for sure } } values.push(track_size); names.push(current_names); if is_auto { // FIXME: In the older version of the spec // (https://www.w3.org/TR/2015/WD-css-grid-1-20150917/#typedef-auto-repeat), // if the repeat type is `` we shouldn't try to parse more than // one `TrackSize`. But in current version of the spec, this is deprecated // but we are adding this for gecko parity. We should remove this when // gecko implements new spec. names.push(input.try(parse_line_names).unwrap_or(vec![].into_boxed_slice())); break } } else { if values.is_empty() { // expecting at least one return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } names.push(current_names); // final `` break // no more , breaking } } let repeat = TrackRepeat { count: count, track_sizes: values, line_names: names.into_boxed_slice(), }; Ok((repeat, repeat_type)) }) }) } } impl Parse for TrackList { fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { let mut current_names = vec![]; let mut names = vec![]; let mut values = vec![]; let mut list_type = TrackListType::Explicit; // assume it's the simplest case // holds value. It can only be only one in a TrackList. let mut auto_repeat = None; // if there is any the list will be of type TrackListType::Auto(idx) // where idx points to the position of the in the track list. If there // is any repeat before , we need to take the number of repetitions into // account to set the position of so it remains the same while computing // values. let mut auto_offset = 0; // assume that everything is . This flag is useful when we encounter let mut atleast_one_not_fixed = false; loop { current_names.extend_from_slice(&mut input.try(parse_line_names).unwrap_or(vec![].into_boxed_slice())); if let Ok(track_size) = input.try(|i| TrackSize::parse(context, i)) { if !track_size.is_fixed() { atleast_one_not_fixed = true; if auto_repeat.is_some() { // only accepts and return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } } let vec = mem::replace(&mut current_names, vec![]); names.push(vec.into_boxed_slice()); values.push(TrackListValue::TrackSize(track_size)); } else if let Ok((repeat, type_)) = input.try(|i| TrackRepeat::parse_with_repeat_type(context, i)) { if list_type == TrackListType::Explicit { list_type = TrackListType::Normal; // doesn't contain repeat() } match type_ { RepeatType::Normal => { atleast_one_not_fixed = true; if auto_repeat.is_some() { // only return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } }, RepeatType::Auto => { if auto_repeat.is_some() || atleast_one_not_fixed { // We've either seen earlier, or there's at least one non-fixed value return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } list_type = TrackListType::Auto(values.len() as u16 + auto_offset); auto_repeat = Some(repeat); let vec = mem::replace(&mut current_names, vec![]); names.push(vec.into_boxed_slice()); continue; }, RepeatType::Fixed => (), } let vec = mem::replace(&mut current_names, vec![]); names.push(vec.into_boxed_slice()); if let RepeatCount::Number(num) = repeat.count { auto_offset += (num.value() - 1) as u16; } values.push(TrackListValue::TrackRepeat(repeat)); } else { if values.is_empty() && auto_repeat.is_none() { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } names.push(current_names.into_boxed_slice()); break } } Ok(TrackList { list_type: list_type, values: values, line_names: names.into_boxed_slice(), auto_repeat: auto_repeat, }) } } impl ToComputedValue for TrackList { type ComputedValue = TrackList; #[inline] fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { // Merge the line names while computing values. The resulting values will // all be bunch of `` and one . // // For example, // `[a b] 100px [c d] repeat(1, 30px [g]) [h]` will be merged as `[a b] 100px [c d] 30px [g h]` // whereas, `[a b] repeat(2, [c] 50px [d]) [e f] repeat(auto-fill, [g] 12px) 10px [h]` will be merged as // `[a b c] 50px [d c] 50px [d e f] repeat(auto-fill, [g] 12px) 10px [h]`, with the `` value // set in the `auto_repeat` field, and the `idx` in TrackListType::Auto pointing to the values after // `` (in this case, `10px [h]`). let mut prev_names = vec![]; let mut line_names = Vec::with_capacity(self.line_names.len() + 1); let mut values = Vec::with_capacity(self.values.len() + 1); for (pos, names) in self.line_names.iter().enumerate() { prev_names.extend_from_slice(&names); if pos >= self.values.len() { let vec = mem::replace(&mut prev_names, vec![]); line_names.push(vec.into_boxed_slice()); continue; } match self.values[pos] { TrackListValue::TrackSize(ref size) => { let vec = mem::replace(&mut prev_names, vec![]); line_names.push(vec.into_boxed_slice()); values.push(TrackListValue::TrackSize(size.to_computed_value(context))); }, TrackListValue::TrackRepeat(ref repeat) => { // If the repeat count is numeric, we expand and merge the values. let mut repeat = repeat.expand(); let mut repeat_names_iter = repeat.line_names.iter(); for (size, repeat_names) in repeat.track_sizes.drain(..).zip(&mut repeat_names_iter) { prev_names.extend_from_slice(&repeat_names); let vec = mem::replace(&mut prev_names, vec![]); line_names.push(vec.into_boxed_slice()); values.push(TrackListValue::TrackSize(size.to_computed_value(context))); } if let Some(names) = repeat_names_iter.next() { prev_names.extend_from_slice(&names); } }, } } TrackList { list_type: self.list_type.to_computed_value(context), values: values, line_names: line_names.into_boxed_slice(), auto_repeat: self.auto_repeat.clone().map(|repeat| repeat.to_computed_value(context)), } } #[inline] fn from_computed_value(computed: &Self::ComputedValue) -> Self { let mut values = Vec::with_capacity(computed.values.len() + 1); for value in computed.values.iter().map(ToComputedValue::from_computed_value) { values.push(value); } TrackList { list_type: computed.list_type, values: values, line_names: computed.line_names.clone(), auto_repeat: computed.auto_repeat.clone().map(|ref repeat| TrackRepeat::from_computed_value(repeat)), } } } impl Parse for GridTemplateComponent { // FIXME: Derive Parse (probably with None_) fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { if input.try(|i| i.expect_ident_matching("none")).is_ok() { return Ok(GridTemplateComponent::None) } Self::parse_without_none(context, input) } } impl GridTemplateComponent { /// Parses a `GridTemplateComponent` except `none` keyword. pub fn parse_without_none<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { if let Ok(t) = input.try(|i| TrackList::parse(context, i)) { return Ok(GridTemplateComponent::TrackList(t)) } LineNameList::parse(context, input).map(GridTemplateComponent::Subgrid) } }