From 9cd466df703072b1de9f7200bb73fde97dd1dc3f Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Wed, 8 Nov 2023 21:44:36 +0000 Subject: [PATCH] Bug 1583429 - Keep repeat() information for specified/computed value for subgrid. r=layout-reviewers,firefox-style-system-reviewers,emilio We introduce a different rust type for `` for subgrid, which keeps `repeat()` information at computed time. At this moment, we don't expand `repeat()`. Also, we precompute the possible expanded list length, so we can expand `repeat(auto-fill)` in a single iteration in nsGridContainerFrame. If we don't have this precompution, we have to go through `` first to know how many items we have after expanding `repeat(Integer)`. Differential Revision: https://phabricator.services.mozilla.com/D192876 --- layout/generic/nsGridContainerFrame.cpp | 230 +++++++++++------ layout/generic/nsGridContainerFrame.h | 7 + .../test/test_grid_container_shorthands.html | 7 +- .../components/style/values/generics/grid.rs | 235 ++++++++++-------- .../components/style/values/specified/grid.rs | 99 +++++++- .../grid-template-computed-nogrid.html.ini | 54 ---- .../subgrid/grid-template-valid.html.ini | 36 --- .../subgrid/grid-template-invalid.html | 2 + 8 files changed, 386 insertions(+), 284 deletions(-) delete mode 100644 testing/web-platform/meta/css/css-grid/subgrid/grid-template-computed-nogrid.html.ini delete mode 100644 testing/web-platform/meta/css/css-grid/subgrid/grid-template-valid.html.ini diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index 578dfe681822..f534b0ccca0a 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -76,7 +76,9 @@ GridTemplate::LineNameLists(bool aIsSubgrid) const { return AsTrackList()->line_names.AsSpan(); } if (IsSubgrid() && aIsSubgrid) { - return AsSubgrid()->names.AsSpan(); + // For subgrid, we need to resolve from each + // StyleGenericLineNameListValue, so return empty. + return {}; } MOZ_ASSERT(IsNone() || IsMasonry() || (IsSubgrid() && !aIsSubgrid)); return {}; @@ -1453,23 +1455,13 @@ class MOZ_STACK_CLASS nsGridContainerFrame::LineNameMap { if (MOZ_UNLIKELY(aRange)) { // subgrid case mClampMinLine = 1; mClampMaxLine = 1 + aRange->Extent(); + MOZ_ASSERT(aTracks.mTemplate.IsSubgrid(), "Should be subgrid type"); + ExpandRepeatLineNamesForSubgrid(*aTracks.mTemplate.AsSubgrid()); + // we've expanded all subgrid auto-fill lines in + // ExpandRepeatLineNamesForSubgrid() + mRepeatAutoStart = 0; mRepeatAutoEnd = mRepeatAutoStart; - const auto& styleSubgrid = aTracks.mTemplate.AsSubgrid(); - const auto fillLen = styleSubgrid->fill_len; - mHasRepeatAuto = fillLen != 0; - if (mHasRepeatAuto) { - const auto& lineNameLists = styleSubgrid->names; - const int32_t extraAutoFillLineCount = - mClampMaxLine - lineNameLists.Length(); - // Maximum possible number of repeat name lists. This must be reduced - // to a whole number of repetitions of the fill length. - const uint32_t possibleRepeatLength = - std::max(0, extraAutoFillLineCount + fillLen); - const uint32_t repeatRemainder = possibleRepeatLength % fillLen; - mRepeatAutoStart = styleSubgrid->fill_start; - mRepeatAutoEnd = - mRepeatAutoStart + possibleRepeatLength - repeatRemainder; - } + mHasRepeatAuto = false; } else { mClampMinLine = kMinLine; mClampMaxLine = kMaxLine; @@ -1477,8 +1469,8 @@ class MOZ_STACK_CLASS nsGridContainerFrame::LineNameMap { mTrackAutoRepeatLineNames = aTracks.mTemplate.GetRepeatAutoValue()->line_names.AsSpan(); } + ExpandRepeatLineNames(aTracks); } - ExpandRepeatLineNames(!!aRange, aTracks); if (mHasRepeatAuto) { // We need mTemplateLinesEnd to be after all line names. // mExpandedLineNames has one repetition of the repeat(auto-fit/fill) @@ -1501,48 +1493,17 @@ class MOZ_STACK_CLASS nsGridContainerFrame::LineNameMap { } // Store line names into mExpandedLineNames with `repeat(INTEGER, ...)` - // expanded (for non-subgrid), and all `repeat(...)` expanded (for subgrid). - void ExpandRepeatLineNames(bool aIsSubgrid, - const TrackSizingFunctions& aTracks) { - auto lineNameLists = aTracks.mTemplate.LineNameLists(aIsSubgrid); + // expanded for non-subgrid. + void ExpandRepeatLineNames(const TrackSizingFunctions& aTracks) { + auto lineNameLists = aTracks.mTemplate.LineNameLists(false); const auto& trackListValues = aTracks.mTrackListValues; const NameList* nameListToMerge = nullptr; // NOTE(emilio): We rely on std::move clearing out the array. SmallPointerArray names; - // This adjusts for outputting the repeat auto names in subgrid. In that - // case, all of the repeat values are handled in a single iteration. - const uint32_t subgridRepeatDelta = - (aIsSubgrid && mHasRepeatAuto) - ? (aTracks.mTemplate.AsSubgrid()->fill_len - 1) - : 0; - const uint32_t end = std::min( - lineNameLists.Length() - subgridRepeatDelta, mClampMaxLine + 1); + const uint32_t end = + std::min(lineNameLists.Length(), mClampMaxLine + 1); for (uint32_t i = 0; i < end; ++i) { - if (aIsSubgrid) { - if (MOZ_UNLIKELY(mHasRepeatAuto && i == mRepeatAutoStart)) { - // XXX expand 'auto-fill' names for subgrid for now since HasNameAt() - // only deals with auto-repeat **tracks** currently. - const auto& styleSubgrid = aTracks.mTemplate.AsSubgrid(); - MOZ_ASSERT(styleSubgrid->fill_len > 0); - for (auto j = i; j < mRepeatAutoEnd; ++j) { - const auto repeatIndex = (j - i) % styleSubgrid->fill_len; - names.AppendElement( - &lineNameLists[styleSubgrid->fill_start + repeatIndex]); - mExpandedLineNames.AppendElement(std::move(names)); - } - } else if (mHasRepeatAuto && i > mRepeatAutoStart) { - const auto& styleSubgrid = aTracks.mTemplate.AsSubgrid(); - names.AppendElement(&lineNameLists[i + styleSubgrid->fill_len - 1]); - mExpandedLineNames.AppendElement(std::move(names)); - } else { - names.AppendElement(&lineNameLists[i]); - mExpandedLineNames.AppendElement(std::move(names)); - } - // XXX expand repeat(, ...) line names here (bug 1583429) - continue; - } - if (nameListToMerge) { names.AppendElement(nameListToMerge); nameListToMerge = nullptr; @@ -1595,8 +1556,76 @@ class MOZ_STACK_CLASS nsGridContainerFrame::LineNameMap { if (MOZ_UNLIKELY(mExpandedLineNames.Length() > uint32_t(mClampMaxLine))) { mExpandedLineNames.TruncateLength(mClampMaxLine); } - if (MOZ_UNLIKELY(mHasRepeatAuto && aIsSubgrid)) { - mHasRepeatAuto = false; // we've expanded all subgrid auto-fill lines + } + + // Store line names into mExpandedLineNames with `repeat(INTEGER, ...)` + // expanded, and all `repeat(...)` expanded for subgrid. + // https://drafts.csswg.org/css-grid/#resolved-track-list-subgrid + void ExpandRepeatLineNamesForSubgrid( + const StyleGenericLineNameList& aStyleLineNameList) { + const auto& lineNameList = aStyleLineNameList.line_names.AsSpan(); + const uint32_t maxCount = mClampMaxLine + 1; + const uint32_t end = lineNameList.Length(); + for (uint32_t i = 0; i < end && mExpandedLineNames.Length() < maxCount; + ++i) { + const auto& item = lineNameList[i]; + if (item.IsLineNames()) { + // case. Just copy it. + SmallPointerArray names; + names.AppendElement(&item.AsLineNames()); + mExpandedLineNames.AppendElement(std::move(names)); + continue; + } + + MOZ_ASSERT(item.IsRepeat()); + const auto& repeat = item.AsRepeat(); + const auto repeatLineNames = repeat.line_names.AsSpan(); + + if (repeat.count.IsNumber()) { + // Clone all + (repeated by N) into + // |mExpandedLineNames|. + for (uint32_t repeatCount = 0; + repeatCount < (uint32_t)repeat.count.AsNumber(); ++repeatCount) { + for (const NameList& lineNames : repeatLineNames) { + SmallPointerArray names; + names.AppendElement(&lineNames); + mExpandedLineNames.AppendElement(std::move(names)); + if (mExpandedLineNames.Length() >= maxCount) { + break; + } + } + } + continue; + } + + MOZ_ASSERT(repeat.count.IsAutoFill(), + "RepeatCount of subgrid is number or auto-fill"); + + const size_t fillLen = repeatLineNames.Length(); + const int32_t extraAutoFillLineCount = + mClampMaxLine - + (int32_t)aStyleLineNameList.expanded_line_names_length; + // Maximum possible number of repeat name lists. + // Note: |expanded_line_names_length| doesn't include auto repeat. + const uint32_t possibleRepeatLength = + std::max(0, extraAutoFillLineCount); + const uint32_t repeatRemainder = possibleRepeatLength % fillLen; + + // Note: Expand 'auto-fill' names for subgrid for now since + // HasNameAt() only deals with auto-repeat **tracks** currently. + const size_t len = possibleRepeatLength - repeatRemainder; + for (size_t j = 0; j < len; ++j) { + SmallPointerArray names; + names.AppendElement(&repeatLineNames[j % fillLen]); + mExpandedLineNames.AppendElement(std::move(names)); + if (mExpandedLineNames.Length() >= maxCount) { + break; + } + } + } + + if (MOZ_UNLIKELY(mExpandedLineNames.Length() > uint32_t(mClampMaxLine))) { + mExpandedLineNames.TruncateLength(mClampMaxLine); } } @@ -1876,6 +1905,8 @@ class MOZ_STACK_CLASS nsGridContainerFrame::LineNameMap { // The index into mExpandedLineNames to use, if aIndex doesn't point to a // name inside of a auto repeat. uint32_t repeatAdjustedIndex = aIndex; + // Note: For subgrid, |mHasRepeatAuto| is always false because we have + // expanded it in the constructor of LineNameMap. if (mHasRepeatAuto) { // If the index is inside of the auto repeat, use the repeat line // names. Otherwise, if the index is past the end of the repeat it must @@ -1907,6 +1938,8 @@ class MOZ_STACK_CLASS nsGridContainerFrame::LineNameMap { repeatAdjustedIndex += mTrackAutoRepeatLineNames.Length() - 2; } } + MOZ_ASSERT(repeatAdjustedIndex < mExpandedLineNames.Length(), + "Incorrect repeatedAdjustedIndex"); MOZ_ASSERT(names.IsEmpty()); // The index is not inside the repeat tracks, or no repeat tracks exist. const auto& nameLists = mExpandedLineNames[repeatAdjustedIndex]; @@ -3924,39 +3957,68 @@ nsContainerFrame* NS_NewGridContainerFrame(PresShell* aPresShell, return *cb; } +void nsGridContainerFrame::AddImplicitNamedAreasInternal( + LineNameList& aNameList, nsGridContainerFrame::ImplicitNamedAreas* aAreas) { + for (const auto& nameIdent : aNameList.AsSpan()) { + nsAtom* name = nameIdent.AsAtom(); + uint32_t indexOfSuffix; + if (Grid::IsNameWithStartSuffix(name, &indexOfSuffix) || + Grid::IsNameWithEndSuffix(name, &indexOfSuffix)) { + // Extract the name that was found earlier. + nsDependentSubstring areaName(nsDependentAtomString(name), 0, + indexOfSuffix); + + // Lazily create the ImplicitNamedAreas. + if (!aAreas) { + aAreas = new nsGridContainerFrame::ImplicitNamedAreas; + SetProperty(nsGridContainerFrame::ImplicitNamedAreasProperty(), aAreas); + } + + RefPtr name = NS_Atomize(areaName); + auto addPtr = aAreas->lookupForAdd(name); + if (!addPtr) { + if (!aAreas->add(addPtr, name, + nsGridContainerFrame::NamedArea{ + StyleAtom(do_AddRef(name)), {0, 0}, {0, 0}})) { + MOZ_CRASH("OOM while adding grid name lists"); + } + } + } + } +} + void nsGridContainerFrame::AddImplicitNamedAreas( Span aLineNameLists) { // http://dev.w3.org/csswg/css-grid/#implicit-named-areas // Note: recording these names for fast lookup later is just an optimization. - const uint32_t len = std::min(aLineNameLists.Length(), size_t(kMaxLine)); - nsTHashSet currentStarts; ImplicitNamedAreas* areas = GetImplicitNamedAreas(); + const uint32_t len = std::min(aLineNameLists.Length(), size_t(kMaxLine)); for (uint32_t i = 0; i < len; ++i) { - for (const auto& nameIdent : aLineNameLists[i].AsSpan()) { - nsAtom* name = nameIdent.AsAtom(); - uint32_t indexOfSuffix; - if (Grid::IsNameWithStartSuffix(name, &indexOfSuffix) || - Grid::IsNameWithEndSuffix(name, &indexOfSuffix)) { - // Extract the name that was found earlier. - nsDependentSubstring areaName(nsDependentAtomString(name), 0, - indexOfSuffix); + AddImplicitNamedAreasInternal(aLineNameLists[i], areas); + } +} - // Lazily create the ImplicitNamedAreas. - if (!areas) { - areas = new ImplicitNamedAreas; - SetProperty(ImplicitNamedAreasProperty(), areas); - } - - RefPtr name = NS_Atomize(areaName); - auto addPtr = areas->lookupForAdd(name); - if (!addPtr) { - if (!areas->add( - addPtr, name, - NamedArea{StyleAtom(do_AddRef(name)), {0, 0}, {0, 0}})) { - MOZ_CRASH("OOM while adding grid name lists"); - } - } +void nsGridContainerFrame::AddImplicitNamedAreas( + Span aLineNameList) { + // http://dev.w3.org/csswg/css-grid/#implicit-named-areas + // Note: recording these names for fast lookup later is just an optimization. + uint32_t count = 0; + ImplicitNamedAreas* areas = GetImplicitNamedAreas(); + for (const auto& nameList : aLineNameList) { + if (nameList.IsRepeat()) { + for (const auto& repeatNameList : + nameList.AsRepeat().line_names.AsSpan()) { + AddImplicitNamedAreasInternal(repeatNameList, areas); + ++count; } + } else { + MOZ_ASSERT(nameList.IsLineNames()); + AddImplicitNamedAreasInternal(nameList.AsLineNames(), areas); + ++count; + } + + if (count >= size_t(kMaxLine)) { + break; } } } @@ -3976,6 +4038,12 @@ void nsGridContainerFrame::InitImplicitNamedAreas( AddImplicitNamedAreas(value.AsTrackRepeat().line_names.AsSpan()); } } + + if (aIsSubgrid && aTemplate.IsSubgrid()) { + // For subgrid, |aTemplate.LineNameLists(aIsSubgrid)| returns an empty + // list so we have to manually add each item. + AddImplicitNamedAreas(aTemplate.AsSubgrid()->line_names.AsSpan()); + } }; Add(aStyle->mGridTemplateColumns, IsSubgrid(eLogicalAxisInline)); Add(aStyle->mGridTemplateRows, IsSubgrid(eLogicalAxisBlock)); diff --git a/layout/generic/nsGridContainerFrame.h b/layout/generic/nsGridContainerFrame.h index fc3c7764fc36..637c261bfd2a 100644 --- a/layout/generic/nsGridContainerFrame.h +++ b/layout/generic/nsGridContainerFrame.h @@ -338,6 +338,9 @@ class nsGridContainerFrame final : public nsContainerFrame, using LineNameList = const mozilla::StyleOwnedSlice; void AddImplicitNamedAreas(mozilla::Span); + using StyleLineNameListValue = + const mozilla::StyleGenericLineNameListValue; + void AddImplicitNamedAreas(mozilla::Span); /** * Reflow and place our children. @@ -512,6 +515,10 @@ class nsGridContainerFrame final : public nsContainerFrame, void StoreUsedTrackSizes(LogicalAxis aAxis, const nsTArray& aSizes); + // The internal implementation for AddImplicitNamedAreas(). + void AddImplicitNamedAreasInternal(LineNameList& aNameList, + ImplicitNamedAreas* aAreas); + /** * Cached values to optimize GetMinISize/GetPrefISize. */ diff --git a/layout/style/test/test_grid_container_shorthands.html b/layout/style/test/test_grid_container_shorthands.html index 2c13bccb1a7e..1b7a434208d2 100644 --- a/layout/style/test/test_grid_container_shorthands.html +++ b/layout/style/test/test_grid_container_shorthands.html @@ -155,15 +155,14 @@ var grid_template_test_cases = [ { specified: "subgrid [foo] repeat(3, [] [a b] [c]) / subgrid", gridTemplateColumns: "subgrid", - gridTemplateRows: "subgrid [foo] [] [a b] [c] [] [a b] [c] [] [a b] [c]", + gridTemplateRows: "subgrid [foo] repeat(3, [] [a b] [c])", }, { // Test that the number of lines is clamped to kMaxLine = 10000. + // https://drafts.csswg.org/css-grid/#overlarge-grids specified: "subgrid [foo] repeat(999999999, [a]) / subgrid", gridTemplateColumns: "subgrid", - // Array(n).join(s) is a hack for the non-standard s.repeat(n - 1) . - // [foo] + 9999 [a] gives us 10000 lines. - gridTemplateRows: "subgrid [foo]" + Array(10000).join(" [a]"), + gridTemplateRows: "subgrid [foo] repeat(10000, [a])", }, { specified: "subgrid [bar]/ subgrid [] [foo", diff --git a/servo/components/style/values/generics/grid.rs b/servo/components/style/values/generics/grid.rs index 3db1c6818a0f..b62ed4e3b7b7 100644 --- a/servo/components/style/values/generics/grid.rs +++ b/servo/components/style/values/generics/grid.rs @@ -7,7 +7,6 @@ use crate::parser::{Parse, ParserContext}; use crate::values::specified; -use crate::values::specified::grid::parse_line_names; use crate::values::{CSSFloat, CustomIdent}; use crate::{Atom, Zero}; use cssparser::Parser; @@ -415,7 +414,16 @@ where /// /// #[derive( - Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, + Clone, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, )] #[repr(C, u8)] pub enum RepeatCount { @@ -446,9 +454,6 @@ impl Parse for RepeatCount { } /// The structure containing `` and `` values. -/// -/// It can also hold `repeat()` function parameters, which expands into the respective -/// values in its computed form. #[derive( Clone, Debug, @@ -635,11 +640,114 @@ impl ToCss for TrackList { } } +/// The `` for subgrids. +/// +/// = repeat( [ | auto-fill ], +) +/// +/// https://drafts.csswg.org/css-grid/#typedef-name-repeat +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C)] +pub struct GenericNameRepeat { + /// The number of times for the value to be repeated (could also be `auto-fill`). + /// Note: `RepeatCount` accepts `auto-fit`, so we should reject it after parsing it. + pub count: RepeatCount, + /// This represents `+`. The length of the outer vector is at least one. + pub line_names: crate::OwnedSlice>, +} + +pub use self::GenericNameRepeat as NameRepeat; + +impl ToCss for NameRepeat { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + dest.write_str("repeat(")?; + self.count.to_css(dest)?; + dest.write_char(',')?; + + for ref names in self.line_names.iter() { + if names.is_empty() { + // Note: concat_serialize_idents() skip the empty list so we have to handle it + // manually for NameRepeat. + dest.write_str(" []")?; + } else { + concat_serialize_idents(" [", "]", names, " ", dest)?; + } + } + + dest.write_char(')') + } +} + +impl NameRepeat { + /// Returns true if it is auto-fill. + #[inline] + pub fn is_auto_fill(&self) -> bool { + matches!(self.count, RepeatCount::AutoFill) + } +} + +/// A single value for `` or ``. +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum GenericLineNameListValue { + /// ``. + LineNames(crate::OwnedSlice), + /// ``. + Repeat(GenericNameRepeat), +} + +pub use self::GenericLineNameListValue as LineNameListValue; + +impl ToCss for LineNameListValue { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + match *self { + Self::Repeat(ref r) => r.to_css(dest), + Self::LineNames(ref names) => { + dest.write_char('[')?; + + if let Some((ref first, rest)) = names.split_first() { + first.to_css(dest)?; + for name in rest { + dest.write_char(' ')?; + name.to_css(dest)?; + } + } + + dest.write_char(']') + }, + } + } +} + /// The `` for subgrids. /// -/// `subgrid [ | repeat( | auto-fill, +) ]+` +/// = [ | ]+ +/// = repeat( [ | auto-fill ], +) /// -/// https://drafts.csswg.org/css-grid-2/#typedef-line-name-list +/// https://drafts.csswg.org/css-grid/#typedef-line-name-list #[derive( Clone, Debug, @@ -652,114 +760,27 @@ impl ToCss for TrackList { ToShmem, )] #[repr(C)] -pub struct LineNameList { - /// The optional `` - pub names: crate::OwnedSlice>, - /// Indicates the starting line names that requires `auto-fill`, if in bounds. - pub fill_start: usize, - /// Indicates the number of line names in the auto-fill - pub fill_len: usize, +pub struct GenericLineNameList{ + /// The pre-computed length of line_names, without the length of repeat(auto-fill, ...). + // We precomputed this at parsing time, so we can avoid an extra loop when expanding + // repeat(auto-fill). + pub expanded_line_names_length: usize, + /// The line name list. + pub line_names: crate::OwnedSlice>, } -impl Parse for LineNameList { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - input.expect_ident_matching("subgrid")?; - let mut line_names = vec![]; - let mut fill_data = None; - // Rather than truncating the result after inserting values, just - // have a maximum number of values. This gives us an early out on very - // large name lists, but more importantly prevents OOM on huge repeat - // expansions. (bug 1583429) - let mut max_remaining = MAX_GRID_LINE as usize; +pub use self::GenericLineNameList as LineNameList; - loop { - let repeat_parse_result = input.try_parse(|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(parse_line_names) { - names_list.push(names); - } - Ok((names_list, count)) - }) - }); - if let Ok((names_list, count)) = repeat_parse_result { - let mut handle_size = |n| { - let n = cmp::min(n, max_remaining); - max_remaining -= n; - n - }; - match count { - // FIXME(emilio): we shouldn't expand repeat() at - // parse time for subgrid. (bug 1583429) - RepeatCount::Number(num) => { - let n = handle_size(num.value() as usize * names_list.len()); - line_names.extend(names_list.iter().cloned().cycle().take(n)); - }, - RepeatCount::AutoFill if fill_data.is_none() => { - let fill_idx = line_names.len(); - let fill_len = names_list.len(); - fill_data = Some((fill_idx, fill_len)); - let n = handle_size(fill_len); - line_names.extend(names_list.into_iter().take(n)); - }, - _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), - } - } else if let Ok(names) = input.try_parse(parse_line_names) { - if max_remaining > 0 { - line_names.push(names); - max_remaining -= 1; - } - } else { - break; - } - } - - debug_assert!(line_names.len() <= MAX_GRID_LINE as usize); - - let (fill_start, fill_len) = fill_data.unwrap_or((0, 0)); - - Ok(LineNameList { - names: line_names.into(), - fill_start: fill_start, - fill_len: fill_len, - }) - } -} - -impl ToCss for LineNameList { +impl ToCss for LineNameList { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { dest.write_str("subgrid")?; - let fill_start = self.fill_start; - let fill_len = self.fill_len; - for (i, names) in self.names.iter().enumerate() { - if fill_len > 0 && i == fill_start { - 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_char(' ')?; - name.to_css(dest)?; - } - } - - dest.write_char(']')?; - if fill_len > 0 && i == fill_start + fill_len - 1 { - dest.write_char(')')?; - } + for value in self.line_names.iter() { + dest.write_char(' ')?; + value.to_css(dest)?; } Ok(()) @@ -795,7 +816,7 @@ pub enum GenericGridTemplateComponent { /// A `subgrid ?` /// TODO: Support animations for this after subgrid is addressed in [grid-2] spec. #[animation(error)] - Subgrid(Box), + Subgrid(Box>), /// `masonry` value. /// https://github.com/w3c/csswg-drafts/issues/4650 Masonry, diff --git a/servo/components/style/values/specified/grid.rs b/servo/components/style/values/specified/grid.rs index 14f773b10087..0b25e0b78474 100644 --- a/servo/components/style/values/specified/grid.rs +++ b/servo/components/style/values/specified/grid.rs @@ -7,8 +7,8 @@ use crate::parser::{Parse, ParserContext}; use crate::values::generics::grid::{GridTemplateComponent, ImplicitGridTracks, RepeatCount}; -use crate::values::generics::grid::{LineNameList, TrackBreadth, TrackRepeat, TrackSize}; -use crate::values::generics::grid::{TrackList, TrackListValue}; +use crate::values::generics::grid::{LineNameList, LineNameListValue, NameRepeat, TrackBreadth}; +use crate::values::generics::grid::{TrackList, TrackListValue, TrackRepeat, TrackSize}; use crate::values::specified::{Integer, LengthPercentage}; use crate::values::{CSSFloat, CustomIdent}; use cssparser::{Parser, Token}; @@ -344,3 +344,98 @@ impl GridTemplateComponent { Ok(GridTemplateComponent::TrackList(Box::new(track_list))) } } + +impl Parse for NameRepeat { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + input.expect_function_matching("repeat")?; + input.parse_nested_block(|i| { + let count = RepeatCount::parse(context, i)?; + // NameRepeat doesn't accept `auto-fit` + // https://drafts.csswg.org/css-grid/#typedef-name-repeat + if matches!(count, RepeatCount::AutoFit) { + return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + i.expect_comma()?; + let mut names_list = vec![]; + names_list.push(parse_line_names(i)?); // there should be at least one + while let Ok(names) = i.try_parse(parse_line_names) { + names_list.push(names); + } + + Ok(NameRepeat { + count, + line_names: names_list.into(), + }) + }) + } +} + +impl Parse for LineNameListValue { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + if let Ok(repeat) = input.try_parse(|i| NameRepeat::parse(context, i)) { + return Ok(LineNameListValue::Repeat(repeat)); + } + + parse_line_names(input).map(LineNameListValue::LineNames) + } +} + +impl LineNameListValue { + /// Returns the length of `` after expanding repeat(N, ...). This returns zero for + /// repeat(auto-fill, ...). + #[inline] + pub fn line_names_length(&self) -> usize { + match *self { + Self::LineNames(..) => 1, + Self::Repeat(ref r) => { + match r.count { + // Note: RepeatCount is always >= 1. + RepeatCount::Number(v) => r.line_names.len() * v.value() as usize, + _ => 0, + } + }, + } + } +} + +impl Parse for LineNameList { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + input.expect_ident_matching("subgrid")?; + + let mut auto_repeat = false; + let mut expanded_line_names_length = 0; + let mut line_names = vec![]; + while let Ok(value) = input.try_parse(|i| LineNameListValue::parse(context, i)) { + match value { + LineNameListValue::Repeat(ref r) if r.is_auto_fill() => { + if auto_repeat { + // On a subgridded axis, the auto-fill keyword is only valid once per + // . + // https://drafts.csswg.org/css-grid/#auto-repeat + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + auto_repeat = true; + }, + _ => (), + }; + + expanded_line_names_length += value.line_names_length(); + line_names.push(value); + } + + Ok(LineNameList{ + expanded_line_names_length, + line_names : line_names.into(), + }) + } +} diff --git a/testing/web-platform/meta/css/css-grid/subgrid/grid-template-computed-nogrid.html.ini b/testing/web-platform/meta/css/css-grid/subgrid/grid-template-computed-nogrid.html.ini deleted file mode 100644 index 1675b4e04b28..000000000000 --- a/testing/web-platform/meta/css/css-grid/subgrid/grid-template-computed-nogrid.html.ini +++ /dev/null @@ -1,54 +0,0 @@ -[grid-template-computed-nogrid.html] - [Property grid-template-columns value 'subgrid [a\] repeat(2, [c\] [d e\])'] - expected: FAIL - - [Property grid-template-columns value 'subgrid repeat(1, [\])'] - expected: FAIL - - [Property grid-template-columns value 'subgrid repeat(2, [\])'] - expected: FAIL - - [Property grid-template-columns value 'subgrid repeat(2, [a\])'] - expected: FAIL - - [Property grid-template-columns value 'subgrid repeat(2, [a\] [\])'] - expected: FAIL - - [Property grid-template-columns value 'subgrid repeat(2, [\] [a\] [\])'] - expected: FAIL - - [Property grid-template-columns value 'subgrid repeat(2, [\] [\] [\]) repeat(auto-fill, [\] [\] [\])'] - expected: FAIL - - [Property grid-template-columns value 'subgrid repeat(1, [a b\])'] - expected: FAIL - - [Property grid-template-columns value 'subgrid repeat(2, [a b\])'] - expected: FAIL - - [Property grid-template-columns value 'subgrid repeat(1, [a\] [b\])'] - expected: FAIL - - [Property grid-template-columns value 'subgrid repeat(2, [a\] [b\])'] - expected: FAIL - - [Property grid-template-columns value 'subgrid [a\] repeat(2, [b\])'] - expected: FAIL - - [Property grid-template-columns value 'subgrid repeat(2, [a\]) [b\]'] - expected: FAIL - - [Property grid-template-columns value 'subgrid [a\] repeat(2, [b\] [c d\]) [e\]'] - expected: FAIL - - [Property grid-template-columns value 'subgrid repeat(2, [a b\]) repeat(auto-fill, [c\] [d e\])'] - expected: FAIL - - [Property grid-template-columns value 'subgrid repeat(auto-fill, [a\] [b c\]) repeat(2, [d e\])'] - expected: FAIL - - [Property grid-template-columns value 'subgrid repeat(2, [a b\]) repeat(auto-fill, [c\] [d e\]) repeat(2, [f g\])'] - expected: FAIL - - [Property grid-template-columns value 'subgrid [a\] [b c\] repeat(2, [d e\]) [f\] [g h\] repeat(auto-fill, [i\] [j k\]) [l\] repeat(2, [m n\]) [o\]'] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-grid/subgrid/grid-template-valid.html.ini b/testing/web-platform/meta/css/css-grid/subgrid/grid-template-valid.html.ini deleted file mode 100644 index d20c0a1dc623..000000000000 --- a/testing/web-platform/meta/css/css-grid/subgrid/grid-template-valid.html.ini +++ /dev/null @@ -1,36 +0,0 @@ -[grid-template-valid.html] - [e.style['grid-template-rows'\] = "subgrid repeat(2, [a\])" should set the property value] - expected: FAIL - - [e.style['grid-template-rows'\] = "subgrid repeat(2, [a\] [b\])" should set the property value] - expected: FAIL - - [e.style['grid-template-rows'\] = "subgrid [a\] repeat(2, [b\])" should set the property value] - expected: FAIL - - [e.style['grid-template-rows'\] = "subgrid [a\] repeat(2, [b\]) [c\]" should set the property value] - expected: FAIL - - [e.style['grid-template-rows'\] = "subgrid [a\] repeat(2, [b\]) repeat(auto-fill, [c\]) [d\]" should set the property value] - expected: FAIL - - [e.style['grid-template-columns'\] = "subgrid repeat(2, [a\])" should set the property value] - expected: FAIL - - [e.style['grid-template-columns'\] = "subgrid repeat(2, [a\] [b\])" should set the property value] - expected: FAIL - - [e.style['grid-template-columns'\] = "subgrid [a\] repeat(2, [b\])" should set the property value] - expected: FAIL - - [e.style['grid-template-columns'\] = "subgrid [a\] repeat(2, [b\]) [c\]" should set the property value] - expected: FAIL - - [e.style['grid-template-columns'\] = "subgrid [a\] repeat(2, [b\]) repeat(auto-fill, [c\]) [d\]" should set the property value] - expected: FAIL - - [e.style['grid-template-rows'\] = "subgrid [\] repeat(2, [\]) [\]" should set the property value] - expected: FAIL - - [e.style['grid-template-columns'\] = "subgrid [\] repeat(2, [\]) [\]" should set the property value] - expected: FAIL diff --git a/testing/web-platform/tests/css/css-grid/subgrid/grid-template-invalid.html b/testing/web-platform/tests/css/css-grid/subgrid/grid-template-invalid.html index d7c12be3fde5..49d781e91f8f 100644 --- a/testing/web-platform/tests/css/css-grid/subgrid/grid-template-invalid.html +++ b/testing/web-platform/tests/css/css-grid/subgrid/grid-template-invalid.html @@ -19,6 +19,7 @@ test_invalid_value("grid-template-rows", 'subgrid 1px'); test_invalid_value("grid-template-rows", 'subgrid [a] 1px'); test_invalid_value("grid-template-rows", 'subgrid repeat(auto-fill, 1px)'); test_invalid_value("grid-template-rows", 'subgrid repeat(auto-fill, line)'); +test_invalid_value("grid-template-rows", 'subgrid repeat(auto-fit, [a])'); test_invalid_value("grid-template-rows", 'subgrid repeat(2, 1px)'); test_invalid_value("grid-template-rows", 'subgrid repeat(2, line)'); test_invalid_value("grid-template-rows", 'subgrid repeat(2,'); @@ -30,6 +31,7 @@ test_invalid_value("grid-template-columns", 'subgrid 1px'); test_invalid_value("grid-template-columns", 'subgrid [a] 1px'); test_invalid_value("grid-template-columns", 'subgrid repeat(auto-fill, 1px)'); test_invalid_value("grid-template-columns", 'subgrid repeat(auto-fill, line)'); +test_invalid_value("grid-template-columns", 'subgrid repeat(auto-fit, [a])'); test_invalid_value("grid-template-columns", 'subgrid repeat(2, 1px)'); test_invalid_value("grid-template-columns", 'subgrid repeat(2, line)'); test_invalid_value("grid-template-columns", 'subgrid repeat(2,');