Auto merge of #17268 - servo:grid, r=wafflespeanut,canaltinova

Add support for subgrids and fix some other grid properties

This covers the `subgrid <line-name-lists>?` for `grid-template` (for gecko, and hence, stylo).

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/17268)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-07-03 17:10:25 -07:00 committed by GitHub
commit 7a71035793
8 changed files with 384 additions and 113 deletions

View file

@ -1097,26 +1097,20 @@ fn static_assert() {
<% self_grid = "self.gecko.mGridTemplate%s" % kind.title() %>
use gecko::values::GeckoStyleCoordConvertible;
use gecko_bindings::structs::{nsTArray, nsStyleGridLine_kMaxLine};
use nsstring::{nsCString, nsStringRepr};
use nsstring::nsStringRepr;
use std::usize;
use values::CustomIdent;
use values::generics::grid::TrackListType::Auto;
use values::generics::grid::{RepeatCount, TrackSize};
use values::generics::grid::{GridTemplateComponent, RepeatCount, TrackSize};
#[inline]
fn set_bitfield(bitfield: &mut u8, pos: u8, val: bool) {
let mask = 1 << (pos - 1);
*bitfield &= !mask;
*bitfield |= (val as u8) << (pos - 1);
}
#[inline]
fn set_line_names(servo_names: &[String], gecko_names: &mut nsTArray<nsStringRepr>) {
fn set_line_names(servo_names: &[CustomIdent], gecko_names: &mut nsTArray<nsStringRepr>) {
unsafe {
bindings::Gecko_ResizeTArrayForStrings(gecko_names, servo_names.len() as u32);
}
for (servo_name, gecko_name) in servo_names.iter().zip(gecko_names.iter_mut()) {
gecko_name.assign_utf8(&nsCString::from(&*servo_name));
gecko_name.assign(servo_name.0.as_slice());
}
}
@ -1141,12 +1135,13 @@ fn static_assert() {
// Set defaults
${self_grid}.mRepeatAutoIndex = -1;
set_bitfield(&mut ${self_grid}._bitfield_1, 1, false); // mIsAutoFill
set_bitfield(&mut ${self_grid}._bitfield_1, 2, false); // mIsSubgrid
// FIXME: mIsSubgrid is false only for <none>, but we don't support subgrid name lists at the moment.
${self_grid}.set_mIsAutoFill(false);
${self_grid}.set_mIsSubgrid(false);
let max_lines = nsStyleGridLine_kMaxLine as usize - 1; // for accounting the final <line-names>
match v {
Either::First(track) => {
GridTemplateComponent::TrackList(track) => {
let mut auto_idx = usize::MAX;
let mut auto_track_size = None;
if let Auto(idx) = track.list_type {
@ -1154,7 +1149,7 @@ fn static_assert() {
let auto_repeat = track.auto_repeat.as_ref().expect("expected <auto-track-repeat> value");
if auto_repeat.count == RepeatCount::AutoFill {
set_bitfield(&mut ${self_grid}._bitfield_1, 1, true);
${self_grid}.set_mIsAutoFill(true);
}
${self_grid}.mRepeatAutoIndex = idx as i16;
@ -1177,7 +1172,6 @@ fn static_assert() {
num_values += 1;
}
let max_lines = nsStyleGridLine_kMaxLine as usize - 1; // for accounting the final <line-names>
num_values = cmp::min(num_values, max_lines);
unsafe {
bindings::Gecko_SetStyleGridTemplateArrayLengths(&mut ${self_grid}, num_values as u32);
@ -1204,7 +1198,7 @@ fn static_assert() {
let final_names = line_names.next().unwrap();
set_line_names(&final_names, ${self_grid}.mLineNameLists.last_mut().unwrap());
},
Either::Second(_none) => {
GridTemplateComponent::None => {
unsafe {
bindings::Gecko_SetStyleGridTemplateArrayLengths(&mut ${self_grid}, 0);
bindings::Gecko_ResizeTArrayForStrings(
@ -1213,6 +1207,27 @@ fn static_assert() {
&mut ${self_grid}.mRepeatAutoLineNameListAfter, 0);
}
},
GridTemplateComponent::Subgrid(list) => {
${self_grid}.set_mIsSubgrid(true);
let num_values = cmp::min(list.names.len(), max_lines + 1);
unsafe {
bindings::Gecko_SetStyleGridTemplateArrayLengths(&mut ${self_grid}, 0);
bindings::Gecko_SetGridTemplateLineNamesLength(&mut ${self_grid}, num_values as u32);
bindings::Gecko_ResizeTArrayForStrings(
&mut ${self_grid}.mRepeatAutoLineNameListBefore, 0);
bindings::Gecko_ResizeTArrayForStrings(
&mut ${self_grid}.mRepeatAutoLineNameListAfter, 0);
}
if let Some(idx) = list.fill_idx {
${self_grid}.set_mIsAutoFill(true);
${self_grid}.mRepeatAutoIndex = idx as i16;
}
for (servo_names, gecko_names) in list.names.iter().zip(${self_grid}.mLineNameLists.iter_mut()) {
set_line_names(servo_names, gecko_names);
}
},
}
}

View file

@ -276,11 +276,9 @@ ${helpers.predefined_type("object-position",
products="gecko",
boxed=True)}
// NOTE: The spec lists only `none | <track-list> | <auto-track-list>`, but gecko seems to support
// `subgrid <line-name-list>?` in addition to this (probably old spec). We should support it soon.
${helpers.predefined_type("grid-template-%ss" % kind,
"TrackListOrNone",
"Either::Second(None_)",
"GridTemplateComponent",
"specified::GenericGridTemplateComponent::None",
products="gecko",
spec="https://drafts.csswg.org/css-grid/#propdef-grid-template-%ss" % kind,
boxed=True,
@ -309,7 +307,9 @@ ${helpers.predefined_type("object-position",
#[derive(PartialEq, Clone, Eq, Copy, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct T {
pub autoflow: AutoFlow,
// This needs to be an Option because we need an indicator to determine whether this property is
// parsed or not in `grid` shorthand. Otherwise we can't properly serialize it.
pub autoflow: Option<AutoFlow>,
pub dense: bool,
}
}
@ -319,7 +319,7 @@ ${helpers.predefined_type("object-position",
impl ToCss for computed_value::T {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
dest.write_str(match self.autoflow {
dest.write_str(match self.autoflow.unwrap_or(computed_value::AutoFlow::Row) {
computed_value::AutoFlow::Column => "column",
computed_value::AutoFlow::Row => "row"
})?;
@ -332,7 +332,8 @@ ${helpers.predefined_type("object-position",
#[inline]
pub fn get_initial_value() -> computed_value::T {
computed_value::T {
autoflow: computed_value::AutoFlow::Row,
// None should resolve to `computed_value::AutoFlow::Row` in layout.
autoflow: None,
dense: false
}
}
@ -369,7 +370,7 @@ ${helpers.predefined_type("object-position",
if value.is_some() || dense {
Ok(computed_value::T {
autoflow: value.unwrap_or(AutoFlow::Row),
autoflow: value,
dense: dense,
})
} else {
@ -384,12 +385,12 @@ ${helpers.predefined_type("object-position",
use self::computed_value::AutoFlow;
SpecifiedValue {
autoflow:
autoflow: Some(
if bits & structs::NS_STYLE_GRID_AUTO_FLOW_ROW as u8 != 0 {
AutoFlow::Row
} else {
AutoFlow::Column
},
}),
dense:
bits & structs::NS_STYLE_GRID_AUTO_FLOW_DENSE as u8 != 0,
}
@ -402,7 +403,7 @@ ${helpers.predefined_type("object-position",
use gecko_bindings::structs;
use self::computed_value::AutoFlow;
let mut result: u8 = match v.autoflow {
let mut result: u8 = match v.autoflow.unwrap_or(AutoFlow::Row) {
AutoFlow::Row => structs::NS_STYLE_GRID_AUTO_FLOW_ROW as u8,
AutoFlow::Column => structs::NS_STYLE_GRID_AUTO_FLOW_COLUMN as u8,
};

View file

@ -241,20 +241,42 @@
disable_when_testing="True"
products="gecko">
use parser::Parse;
use properties::longhands::grid_template_rows;
use properties::longhands::grid_template_areas::TemplateAreas;
use values::{Either, None_};
use values::generics::grid::{TrackSize, TrackList, TrackListType, concat_serialize_idents};
use values::specified::TrackListOrNone;
use values::generics::grid::{LineNameList, TrackSize, TrackList, TrackListType, concat_serialize_idents};
use values::specified::{GridTemplateComponent, GenericGridTemplateComponent};
use values::specified::grid::parse_line_names;
/// Parsing for `<grid-template>` shorthand (also used by `grid` shorthand).
pub fn parse_grid_template<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-> Result<(TrackListOrNone, TrackListOrNone, Either<TemplateAreas, None_>),
ParseError<'i>> {
if input.try(|i| i.expect_ident_matching("none")).is_ok() {
return Ok((Either::Second(None_), Either::Second(None_), Either::Second(None_)))
-> Result<(GridTemplateComponent,
GridTemplateComponent,
Either<TemplateAreas, None_>), ParseError<'i>> {
// Other shorthand sub properties also parse `none` and `subgrid` keywords and this
// shorthand should know after these keywords there is nothing to parse. Otherwise it
// gets confused and rejects the sub properties that contains `none` or `subgrid`.
<% keywords = {
"none": "GenericGridTemplateComponent::None",
"subgrid": "GenericGridTemplateComponent::Subgrid(LineNameList::default())"
}
%>
% for keyword, rust_type in keywords.items():
if let Ok(x) = input.try(|i| {
if i.try(|i| i.expect_ident_matching("${keyword}")).is_ok() {
if i.is_exhausted() {
return Ok((${rust_type},
${rust_type},
Either::Second(None_)))
} else {
return Err(());
}
}
Err(())
}) {
return Ok(x);
}
% endfor
let first_line_names = input.try(parse_line_names).unwrap_or(vec![]);
if let Ok(s) = input.try(Parser::expect_string) {
@ -296,24 +318,34 @@
};
let template_cols = if input.try(|i| i.expect_delim('/')).is_ok() {
let track_list = TrackList::parse(context, input)?;
if track_list.list_type != TrackListType::Explicit {
let value = GridTemplateComponent::parse_without_none(context, input)?;
if let GenericGridTemplateComponent::TrackList(ref list) = value {
if list.list_type != TrackListType::Explicit {
return Err(StyleParseError::UnspecifiedError.into())
}
Either::First(track_list)
} else {
Either::Second(None_)
};
Ok((Either::First(template_rows), template_cols, Either::First(template_areas)))
} else {
let mut template_rows = grid_template_rows::parse(context, input)?;
if let Either::First(ref mut list) = template_rows {
list.line_names[0] = first_line_names; // won't panic
}
Ok((template_rows, grid_template_rows::parse(context, input)?, Either::Second(None_)))
value
} else {
GenericGridTemplateComponent::None
};
Ok((GenericGridTemplateComponent::TrackList(template_rows),
template_cols, Either::First(template_areas)))
} else {
let mut template_rows = GridTemplateComponent::parse(context, input)?;
if let GenericGridTemplateComponent::TrackList(ref mut list) = template_rows {
// Fist line names are parsed already and it shouldn't be parsed again.
// If line names are not empty, that means given property value is not acceptable
if list.line_names[0].is_empty() {
list.line_names[0] = first_line_names; // won't panic
} else {
return Err(StyleParseError::UnspecifiedError.into());
}
}
input.expect_delim('/')?;
Ok((template_rows, GridTemplateComponent::parse(context, input)?, Either::Second(None_)))
}
}
@ -329,25 +361,20 @@
}
/// Serialization for `<grid-template>` shorthand (also used by `grid` shorthand).
pub fn serialize_grid_template<W>(template_rows: &TrackListOrNone,
template_columns: &TrackListOrNone,
pub fn serialize_grid_template<W>(template_rows: &GridTemplateComponent,
template_columns: &GridTemplateComponent,
template_areas: &Either<TemplateAreas, None_>,
dest: &mut W) -> fmt::Result where W: fmt::Write {
match *template_areas {
Either::Second(_none) => {
if template_rows == &Either::Second(None_) &&
template_columns == &Either::Second(None_) {
dest.write_str("none")
} else {
template_rows.to_css(dest)?;
dest.write_str(" / ")?;
template_columns.to_css(dest)
}
},
Either::First(ref areas) => {
let track_list = match *template_rows {
Either::First(ref list) => list,
Either::Second(_none) => unreachable!(), // should exist!
GenericGridTemplateComponent::TrackList(ref list) => list,
_ => unreachable!(), // should exist!
};
let mut names_iter = track_list.line_names.iter();
@ -371,7 +398,7 @@
concat_serialize_idents(" [", "]", names, " ", dest)?;
}
if let Either::First(ref list) = *template_columns {
if let GenericGridTemplateComponent::TrackList(ref list) = *template_columns {
dest.write_str(" / ")?;
list.to_css(dest)?;
}
@ -397,16 +424,17 @@
spec="https://drafts.csswg.org/css-grid/#propdef-grid"
disable_when_testing="True"
products="gecko">
use parser::Parse;
use properties::longhands::{grid_auto_columns, grid_auto_rows, grid_auto_flow};
use properties::longhands::{grid_template_columns, grid_template_rows};
use properties::longhands::grid_auto_flow::computed_value::{AutoFlow, T as SpecifiedAutoFlow};
use values::{Either, None_};
use values::generics::grid::GridTemplateComponent;
use values::specified::{LengthOrPercentage, TrackSize};
pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-> Result<Longhands, ParseError<'i>> {
let mut temp_rows = Either::Second(None_);
let mut temp_cols = Either::Second(None_);
let mut temp_rows = GridTemplateComponent::None;
let mut temp_cols = GridTemplateComponent::None;
let mut temp_areas = Either::Second(None_);
let mut auto_rows = TrackSize::default();
let mut auto_cols = TrackSize::default();
@ -432,7 +460,7 @@
auto_flow.map(|flow| {
SpecifiedAutoFlow {
autoflow: flow,
autoflow: Some(flow),
dense: dense,
}
}).ok_or(StyleParseError::UnspecifiedError.into())
@ -442,16 +470,16 @@
temp_rows = rows;
temp_cols = cols;
temp_areas = areas;
} else if let Ok(rows) = input.try(|i| grid_template_rows::parse(context, i)) {
} else if let Ok(rows) = input.try(|i| GridTemplateComponent::parse(context, i)) {
temp_rows = rows;
input.expect_delim('/')?;
flow = parse_auto_flow(input, false)?;
flow = parse_auto_flow(input, true)?;
auto_cols = grid_auto_columns::parse(context, input).unwrap_or_default();
} else {
flow = parse_auto_flow(input, true)?;
flow = parse_auto_flow(input, false)?;
auto_rows = input.try(|i| grid_auto_rows::parse(context, i)).unwrap_or_default();
input.expect_delim('/')?;
temp_cols = grid_template_columns::parse(context, input)?;
temp_cols = GridTemplateComponent::parse(context, input)?;
}
Ok(expanded! {
@ -469,28 +497,39 @@
impl<'a> ToCss for LonghandsToSerialize<'a> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
if let Either::First(_) = *self.grid_template_areas {
super::grid_template::serialize_grid_template(self.grid_template_rows,
if self.grid_auto_flow.autoflow.is_none() {
return super::grid_template::serialize_grid_template(self.grid_template_rows,
self.grid_template_columns,
self.grid_template_areas, dest)
} else if self.grid_auto_flow.autoflow == AutoFlow::Row {
self.grid_template_areas, dest);
}
// This unwrap is safe because we checked the None part in previous if condition.
if self.grid_auto_flow.autoflow.unwrap() == AutoFlow::Row {
self.grid_template_rows.to_css(dest)?;
dest.write_str(" / auto-flow")?;
if self.grid_auto_flow.dense {
dest.write_str(" dense")?;
}
self.grid_auto_columns.to_css(dest)
if !self.grid_auto_columns.is_default() {
dest.write_str(" ")?;
self.grid_auto_columns.to_css(dest)?;
}
} else {
dest.write_str("auto-flow ")?;
dest.write_str("auto-flow")?;
if self.grid_auto_flow.dense {
dest.write_str("dense ")?;
dest.write_str(" dense")?;
}
if !self.grid_auto_rows.is_default() {
dest.write_str(" ")?;
self.grid_auto_rows.to_css(dest)?;
dest.write_str(" / ")?;
self.grid_template_columns.to_css(dest)
}
dest.write_str(" / ")?;
self.grid_template_columns.to_css(dest)?;
}
Ok(())
}
}
</%helpers:shorthand>

View file

@ -18,6 +18,7 @@ use std::fmt;
use style_traits::ToCss;
use super::{CSSFloat, CSSInteger, RGBA};
use super::generics::grid::{TrackBreadth as GenericTrackBreadth, TrackSize as GenericTrackSize};
use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent;
use super::generics::grid::TrackList as GenericTrackList;
use super::specified;
@ -550,8 +551,8 @@ pub type TrackSize = GenericTrackSize<LengthOrPercentage>;
/// (could also be `<auto-track-list>` or `<explicit-track-list>`)
pub type TrackList = GenericTrackList<TrackSize>;
/// `<track-list> | none`
pub type TrackListOrNone = Either<TrackList, None_>;
/// `<grid-template-rows> | <grid-template-columns>`
pub type GridTemplateComponent = GenericGridTemplateComponent<TrackSize>;
impl ClipRectOrAuto {
/// Return an auto (default for clip-rect and image-region) value

View file

@ -5,13 +5,14 @@
//! Generic types for the handling of
//! [grids](https://drafts.csswg.org/css-grid/).
use cssparser::{Parser, serialize_identifier};
use cssparser::Parser;
use parser::{Parse, ParserContext};
use std::{fmt, mem, usize};
use style_traits::{ToCss, ParseError, StyleParseError};
use values::{CSSFloat, CustomIdent};
use values::computed::{self, ComputedValueAsSpecified, Context, ToComputedValue};
use values::specified::Integer;
use values::specified::grid::parse_line_names;
#[derive(PartialEq, Clone, Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
@ -251,6 +252,13 @@ impl<L> Default for TrackSize<L> {
}
}
impl<L: PartialEq> TrackSize<L> {
/// Returns true if current TrackSize is same as default.
pub fn is_default(&self) -> bool {
*self == TrackSize::default()
}
}
impl<L: ToCss> ToCss for TrackSize<L> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match *self {
@ -307,15 +315,15 @@ impl<L: ToComputedValue> ToComputedValue for TrackSize<L> {
/// Helper function for serializing identifiers with a prefix and suffix, used
/// for serializing <line-names> (in grid).
pub fn concat_serialize_idents<W>(prefix: &str, suffix: &str,
slice: &[String], sep: &str, dest: &mut W) -> fmt::Result
slice: &[CustomIdent], sep: &str, dest: &mut W) -> fmt::Result
where W: fmt::Write
{
if let Some((ref first, rest)) = slice.split_first() {
dest.write_str(prefix)?;
serialize_identifier(first, dest)?;
first.to_css(dest)?;
for thing in rest {
dest.write_str(sep)?;
serialize_identifier(thing, dest)?;
thing.to_css(dest)?;
}
dest.write_str(suffix)?;
@ -372,16 +380,29 @@ pub struct TrackRepeat<L> {
/// If there's no `<line-names>`, then it's represented by an empty vector.
/// For N `<track-size>` values, there will be N+1 `<line-names>`, and so this vector's
/// length is always one value more than that of the `<track-size>`.
pub line_names: Vec<Vec<String>>,
pub line_names: Vec<Vec<CustomIdent>>,
/// `<track-size>` values.
pub track_sizes: Vec<TrackSize<L>>,
}
impl<L: ToCss> ToCss for TrackRepeat<L> {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
// If repeat count is an integer instead of a keyword, it should'n serialized
// with `repeat` function. It should serialized with `N` repeated form.
let repeat_count = match self.count {
RepeatCount::Number(integer) => integer.value(),
_ => {
dest.write_str("repeat(")?;
self.count.to_css(dest)?;
dest.write_str(", ")?;
1
},
};
for i in 0..repeat_count {
if i != 0 {
dest.write_str(" ")?;
}
let mut line_names_iter = self.line_names.iter();
for (i, (ref size, ref names)) in self.track_sizes.iter()
@ -397,8 +418,14 @@ impl<L: ToCss> ToCss for TrackRepeat<L> {
if let Some(line_names_last) = line_names_iter.next() {
concat_serialize_idents(" [", "]", line_names_last, " ", dest)?;
}
}
match self.count {
RepeatCount::AutoFill | RepeatCount::AutoFit => {
dest.write_str(")")?;
},
_ => {},
}
Ok(())
}
}
@ -502,7 +529,7 @@ pub struct TrackList<T> {
/// If there's no `<line-names>`, then it's represented by an empty vector.
/// For N values, there will be N+1 `<line-names>`, and so this vector's
/// length is always one value more than that of the `<track-size>`.
pub line_names: Vec<Vec<String>>,
pub line_names: Vec<Vec<CustomIdent>>,
/// `<auto-repeat>` value after computation. This field is necessary, because
/// the `values` field (after computation) will only contain `<track-size>` values, and
/// we need something to represent this function.
@ -551,3 +578,115 @@ impl<T: ToCss> ToCss for TrackList<T> {
Ok(())
}
}
/// The `<line-name-list>` for subgrids.
///
/// `subgrid [ <line-names> | repeat(<positive-integer> | auto-fill, <line-names>+) ]+`
/// Old spec: https://www.w3.org/TR/2015/WD-css-grid-1-20150917/#typedef-line-name-list
#[derive(Clone, PartialEq, Debug, Default)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct LineNameList {
/// The optional `<line-name-list>`
pub names: Vec<Vec<CustomIdent>>,
/// Indicates the line name that requires `auto-fill`
pub fill_idx: Option<u32>,
}
impl Parse for LineNameList {
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
input.expect_ident_matching("subgrid")?;
let mut line_names = vec![];
let mut fill_idx = None;
loop {
let repeat_parse_result = input.try(|input| {
input.expect_function_matching("repeat")?;
input.parse_nested_block(|input| {
let count = RepeatCount::parse(context, input)?;
input.expect_comma()?;
let mut names_list = vec![];
names_list.push(parse_line_names(input)?); // there should be at least one
while let Ok(names) = input.try(parse_line_names) {
names_list.push(names);
}
Ok((names_list, count))
})
});
if let Ok((mut names_list, count)) = repeat_parse_result {
match count {
RepeatCount::Number(num) =>
line_names.extend(names_list.iter().cloned().cycle()
.take(num.value() as usize * names_list.len())),
RepeatCount::AutoFill if fill_idx.is_none() => {
// `repeat(autof-fill, ..)` should have just one line name.
if names_list.len() > 1 {
return Err(StyleParseError::UnspecifiedError.into());
}
let names = names_list.pop().expect("expected one name list for auto-fill");
line_names.push(names);
fill_idx = Some(line_names.len() as u32 - 1);
},
_ => return Err(StyleParseError::UnspecifiedError.into()),
}
} else if let Ok(names) = input.try(parse_line_names) {
line_names.push(names);
} else {
break
}
}
Ok(LineNameList {
names: line_names,
fill_idx: fill_idx,
})
}
}
impl ToCss for LineNameList {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
dest.write_str("subgrid")?;
let fill_idx = self.fill_idx.map(|v| v as usize).unwrap_or(usize::MAX);
for (i, names) in self.names.iter().enumerate() {
if i == fill_idx {
dest.write_str(" repeat(auto-fill,")?;
}
dest.write_str(" [")?;
if let Some((ref first, rest)) = names.split_first() {
first.to_css(dest)?;
for name in rest {
dest.write_str(" ")?;
name.to_css(dest)?;
}
}
dest.write_str("]")?;
if i == fill_idx {
dest.write_str(")")?;
}
}
Ok(())
}
}
impl ComputedValueAsSpecified for LineNameList {}
no_viewport_percentage!(LineNameList);
/// Variants for `<grid-template-rows> | <grid-template-columns>`
/// Subgrid deferred to Level 2 spec due to lack of implementation.
/// But it's implemented in gecko, so we have to as well.
#[derive(Clone, PartialEq, Debug, ToCss)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub enum GridTemplateComponent<L> {
/// `none` value.
None,
/// The grid `<track-list>`
TrackList(TrackList<L>),
/// A `subgrid <line-name-list>?`
Subgrid(LineNameList),
}

View file

@ -12,8 +12,8 @@ use std::ascii::AsciiExt;
use style_traits::{HasViewportPercentage, ParseError, StyleParseError};
use values::{CSSFloat, CustomIdent, Either};
use values::computed::{self, Context, ToComputedValue};
use values::generics::grid::{RepeatCount, TrackBreadth, TrackKeyword, TrackRepeat};
use values::generics::grid::{TrackSize, TrackList, TrackListType};
use values::generics::grid::{GridTemplateComponent, RepeatCount, TrackBreadth, TrackKeyword, TrackRepeat};
use values::generics::grid::{LineNameList, TrackSize, TrackList, TrackListType};
use values::specified::LengthOrPercentage;
/// Parse a single flexible length.
@ -81,16 +81,13 @@ impl Parse for TrackSize<LengthOrPercentage> {
/// Parse the grid line names into a vector of owned strings.
///
/// https://drafts.csswg.org/css-grid/#typedef-line-names
pub fn parse_line_names<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Vec<String>, ParseError<'i>> {
pub fn parse_line_names<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Vec<CustomIdent>, ParseError<'i>> {
input.expect_square_bracket_block()?;
input.parse_nested_block(|input| {
let mut values = vec![];
while let Ok(ident) = input.try(|i| i.expect_ident()) {
if CustomIdent::from_ident((&*ident).into(), &["span"]).is_err() {
return Err(StyleParseError::UnspecifiedError.into())
}
values.push(ident.into_owned());
let ident = CustomIdent::from_ident(ident, &["span"])?;
values.push(ident);
}
Ok(values)
@ -113,8 +110,7 @@ enum RepeatType {
impl TrackRepeat<LengthOrPercentage> {
fn parse_with_repeat_type<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-> Result<(TrackRepeat<LengthOrPercentage>, RepeatType),
ParseError<'i>> {
-> Result<(TrackRepeat<LengthOrPercentage>, 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)?;
@ -147,6 +143,16 @@ impl TrackRepeat<LengthOrPercentage> {
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 `<auto-repeat>` 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![]));
break
}
} else {
if values.is_empty() {
// expecting at least one <track-size>
@ -353,3 +359,59 @@ impl ToComputedValue for TrackList<TrackSizeOrRepeat> {
}
}
}
impl Parse for GridTemplateComponent<TrackSizeOrRepeat> { // FIXME: Derive Parse (probably with None_)
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
if input.try(|i| i.expect_ident_matching("none")).is_ok() {
return Ok(GridTemplateComponent::None)
}
Self::parse_without_none(context, input)
}
}
impl GridTemplateComponent<TrackSizeOrRepeat> {
/// Parses a `GridTemplateComponent<TrackSizeOrRepeat>` except `none` keyword.
pub fn parse_without_none<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
-> Result<Self, ParseError<'i>> {
if let Ok(t) = input.try(|i| TrackList::parse(context, i)) {
return Ok(GridTemplateComponent::TrackList(t))
}
LineNameList::parse(context, input).map(GridTemplateComponent::Subgrid)
}
}
impl HasViewportPercentage for GridTemplateComponent<TrackSizeOrRepeat> {
#[inline]
fn has_viewport_percentage(&self) -> bool {
match *self {
GridTemplateComponent::TrackList(ref l) => l.has_viewport_percentage(),
_ => false,
}
}
}
impl ToComputedValue for GridTemplateComponent<TrackSizeOrRepeat> {
type ComputedValue = GridTemplateComponent<TrackSize<computed::LengthOrPercentage>>;
#[inline]
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
match *self {
GridTemplateComponent::None => GridTemplateComponent::None,
GridTemplateComponent::TrackList(ref l) => GridTemplateComponent::TrackList(l.to_computed_value(context)),
GridTemplateComponent::Subgrid(ref n) => GridTemplateComponent::Subgrid(n.to_computed_value(context)),
}
}
#[inline]
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
match *computed {
GridTemplateComponent::None => GridTemplateComponent::None,
GridTemplateComponent::TrackList(ref l) =>
GridTemplateComponent::TrackList(ToComputedValue::from_computed_value(l)),
GridTemplateComponent::Subgrid(ref n) =>
GridTemplateComponent::Subgrid(ToComputedValue::from_computed_value(n)),
}
}
}

View file

@ -48,6 +48,7 @@ pub use self::position::{Position, PositionComponent};
pub use self::text::{InitialLetter, LetterSpacing, LineHeight, WordSpacing};
pub use self::transform::{TimingFunction, TransformOrigin};
pub use super::generics::grid::GridLine;
pub use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent;
#[cfg(feature = "gecko")]
pub mod align;
@ -686,8 +687,8 @@ pub type TrackSize = GenericTrackSize<LengthOrPercentage>;
/// (could also be `<auto-track-list>` or `<explicit-track-list>`)
pub type TrackList = GenericTrackList<TrackSizeOrRepeat>;
/// `<track-list> | none`
pub type TrackListOrNone = Either<TrackList, None_>;
/// `<grid-template-rows> | <grid-template-columns>`
pub type GridTemplateComponent = GenericGridTemplateComponent<TrackSizeOrRepeat>;
no_viewport_percentage!(SVGPaint);

View file

@ -191,12 +191,18 @@ fn test_grid_template_rows_columns() {
// <track-size> with <track-breadth> as <length-percentage>
assert_roundtrip_with_context!(grid_template_rows::parse, "calc(4em + 5px)");
// <track-size> with <length> followed by <track-repeat> with `<track-size>{3}` (<flex>, auto, minmax)
assert_roundtrip_with_context!(grid_template_rows::parse, "10px repeat(2, 1fr auto minmax(200px, 1fr))");
assert_roundtrip_with_context!(grid_template_rows::parse,
"10px repeat(2, 1fr auto minmax(200px, 1fr))",
"10px 1fr auto minmax(200px, 1fr) 1fr auto minmax(200px, 1fr)");
// <track-repeat> with `<track-size> <line-names>` followed by <track-size>
assert_roundtrip_with_context!(grid_template_rows::parse, "repeat(4, 10px [col-start] 250px [col-end]) 10px");
assert_roundtrip_with_context!(grid_template_rows::parse,
"repeat(2, 10px [col-start] 250px [col-end]) 10px",
"10px [col-start] 250px [col-end] 10px [col-start] 250px [col-end] 10px");
// mixture of <track-size>, <track-repeat> and <line-names>
assert_roundtrip_with_context!(grid_template_rows::parse,
"[a] auto [b] minmax(min-content, 1fr) [b c d] repeat(2, [e] 40px) repeat(5, [f g] auto [h]) [i]");
"[a] auto [b] minmax(min-content, 1fr) [b c d] repeat(2, 40px [e] 30px) [i]",
"[a] auto [b] minmax(min-content, 1fr) [b c d] 40px [e] 30px 40px [e] 30px [i]");
assert!(parse(grid_template_rows::parse, "subgrid").is_ok());
// no span allowed in <line-names>
assert!(parse(grid_template_rows::parse, "[a span] 10px").is_err());
@ -232,4 +238,11 @@ fn test_computed_grid_template_rows_colums() {
assert_computed_serialization(grid_template_rows::parse,
"10px repeat(2, 1fr auto minmax(200px, 1fr))",
"10px minmax(auto, 1fr) auto minmax(200px, 1fr) minmax(auto, 1fr) auto minmax(200px, 1fr)");
assert_computed_serialization(grid_template_rows::parse,
"subgrid [a] [] repeat(auto-fill, [])", "subgrid [a] [] repeat(auto-fill, [])");
assert_computed_serialization(grid_template_rows::parse,
"subgrid [a] [b] repeat(2, [c d] [] [e]) [] repeat(auto-fill, [])",
"subgrid [a] [b] [c d] [] [e] [c d] [] [e] [] repeat(auto-fill, [])");
}