mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-19 15:32:31 +00:00
LibWeb: Align CSS Grid properties parsing with the specification
Reimplements `grid`, `grid-template`, `grid-template-rows`, and `grid-template-columns` in a way that uses a separate function for each grammar rule defined in the specification. This change results in many additional passing tests from the already imported WPT suite. Most of the remaining test failures are related to incorrect serialization of grid properties.
This commit is contained in:
parent
7b077595c3
commit
af602b2555
Notes:
github-actions[bot]
2025-06-21 20:08:32 +00:00
Author: https://github.com/kalenikaliaksandr
Commit: af602b2555
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5150
Reviewed-by: https://github.com/tcl3
23 changed files with 894 additions and 574 deletions
|
@ -8,6 +8,7 @@
|
|||
* Copyright (c) 2024, Tommy van der Vorst <tommy@pixelspark.nl>
|
||||
* Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
|
||||
* Copyright (c) 2024, Glenn Skrzypczak <glenn.skrzypczak@gmail.com>
|
||||
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -3210,243 +3211,437 @@ RefPtr<CustomIdentStyleValue const> Parser::parse_custom_ident_value(TokenStream
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
Optional<CSS::GridSize> Parser::parse_grid_size(ComponentValue const& component_value)
|
||||
// https://www.w3.org/TR/css-grid-2/#typedef-track-breadth
|
||||
Optional<GridSize> Parser::parse_grid_track_breadth(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
if (component_value.is_function()) {
|
||||
if (auto maybe_calculated = parse_calculated_value(component_value)) {
|
||||
if (maybe_calculated->is_length())
|
||||
return GridSize(maybe_calculated->as_length().length());
|
||||
if (maybe_calculated->is_percentage())
|
||||
return GridSize(maybe_calculated->as_percentage().percentage());
|
||||
if (maybe_calculated->is_calculated() && maybe_calculated->as_calculated().resolves_to_length_percentage())
|
||||
return GridSize(LengthPercentage(maybe_calculated->as_calculated()));
|
||||
// FIXME: Support calculated <flex>
|
||||
}
|
||||
// <track-breadth> = <length-percentage [0,∞]> | <flex [0,∞]> | min-content | max-content | auto
|
||||
|
||||
return {};
|
||||
if (auto inflexible_breadth = parse_grid_inflexible_breadth(tokens); inflexible_breadth.has_value())
|
||||
return inflexible_breadth;
|
||||
|
||||
// FIXME: Handle calculated flex values.
|
||||
if (auto flex_value = parse_flex_value(tokens); flex_value && flex_value->is_flex()) {
|
||||
if (auto flex = flex_value->as_flex().flex(); flex.raw_value() >= 0)
|
||||
return GridSize(flex);
|
||||
}
|
||||
if (component_value.is_ident("auto"sv))
|
||||
return GridSize::make_auto();
|
||||
if (component_value.is_ident("max-content"sv))
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-grid-2/#typedef-inflexible-breadth
|
||||
Optional<GridSize> Parser::parse_grid_inflexible_breadth(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// <inflexible-breadth> = <length-percentage [0,∞]> | min-content | max-content | auto
|
||||
|
||||
if (auto fixed_breadth = parse_grid_fixed_breadth(tokens); fixed_breadth.has_value())
|
||||
return fixed_breadth;
|
||||
|
||||
auto transaction = tokens.begin_transaction();
|
||||
if (!tokens.has_next_token())
|
||||
return {};
|
||||
|
||||
auto const& token = tokens.consume_a_token();
|
||||
if (token.is_ident("max-content"sv)) {
|
||||
transaction.commit();
|
||||
return GridSize(GridSize::Type::MaxContent);
|
||||
if (component_value.is_ident("min-content"sv))
|
||||
return GridSize(GridSize::Type::MinContent);
|
||||
auto dimension = parse_dimension(component_value);
|
||||
if (!dimension.has_value())
|
||||
return {};
|
||||
if (dimension->is_length())
|
||||
return GridSize(dimension->length());
|
||||
else if (dimension->is_percentage())
|
||||
return GridSize(dimension->percentage());
|
||||
else if (dimension->is_flex())
|
||||
return GridSize(dimension->flex());
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<GridSize> Parser::parse_grid_fit_content(Vector<ComponentValue> const& component_values)
|
||||
{
|
||||
// https://www.w3.org/TR/css-grid-2/#valdef-grid-template-columns-fit-content
|
||||
// 'fit-content( <length-percentage> )'
|
||||
// Represents the formula max(minimum, min(limit, max-content)), where minimum represents an auto minimum (which is often, but not always,
|
||||
// equal to a min-content minimum), and limit is the track sizing function passed as an argument to fit-content().
|
||||
// This is essentially calculated as the smaller of minmax(auto, max-content) and minmax(auto, limit).
|
||||
auto function_tokens = TokenStream(component_values);
|
||||
function_tokens.discard_whitespace();
|
||||
auto maybe_length_percentage = parse_length_percentage(function_tokens);
|
||||
if (maybe_length_percentage.has_value())
|
||||
return GridSize(GridSize::Type::FitContent, maybe_length_percentage.value());
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<CSS::GridMinMax> Parser::parse_min_max(Vector<ComponentValue> const& component_values)
|
||||
{
|
||||
// https://www.w3.org/TR/css-grid-2/#valdef-grid-template-columns-minmax
|
||||
// 'minmax(min, max)'
|
||||
// Defines a size range greater than or equal to min and less than or equal to max. If the max is
|
||||
// less than the min, then the max will be floored by the min (essentially yielding minmax(min,
|
||||
// min)). As a maximum, a <flex> value sets the track’s flex factor; it is invalid as a minimum.
|
||||
auto function_tokens = TokenStream(component_values);
|
||||
auto comma_separated_list = parse_a_comma_separated_list_of_component_values(function_tokens);
|
||||
if (comma_separated_list.size() != 2)
|
||||
return {};
|
||||
|
||||
TokenStream part_one_tokens { comma_separated_list[0] };
|
||||
part_one_tokens.discard_whitespace();
|
||||
if (!part_one_tokens.has_next_token())
|
||||
return {};
|
||||
NonnullRawPtr<ComponentValue const> current_token = part_one_tokens.consume_a_token();
|
||||
auto min_grid_size = parse_grid_size(current_token);
|
||||
|
||||
TokenStream part_two_tokens { comma_separated_list[1] };
|
||||
part_two_tokens.discard_whitespace();
|
||||
if (!part_two_tokens.has_next_token())
|
||||
return {};
|
||||
current_token = part_two_tokens.consume_a_token();
|
||||
auto max_grid_size = parse_grid_size(current_token);
|
||||
|
||||
if (min_grid_size.has_value() && max_grid_size.has_value()) {
|
||||
// https://www.w3.org/TR/css-grid-2/#valdef-grid-template-columns-minmax
|
||||
// As a maximum, a <flex> value sets the track’s flex factor; it is invalid as a minimum.
|
||||
if (min_grid_size.value().is_flexible_length())
|
||||
return {};
|
||||
return CSS::GridMinMax(min_grid_size.value(), max_grid_size.value());
|
||||
}
|
||||
if (token.is_ident("min-content"sv)) {
|
||||
transaction.commit();
|
||||
return GridSize(GridSize::Type::MinContent);
|
||||
}
|
||||
if (token.is_ident("auto"sv)) {
|
||||
transaction.commit();
|
||||
return GridSize::make_auto();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<CSS::GridRepeat> Parser::parse_repeat(Vector<ComponentValue> const& component_values)
|
||||
// https://www.w3.org/TR/css-grid-2/#typedef-fixed-breadth
|
||||
Optional<GridSize> Parser::parse_grid_fixed_breadth(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// https://www.w3.org/TR/css-grid-2/#repeat-syntax
|
||||
// 7.2.3.1. Syntax of repeat()
|
||||
// The generic form of the repeat() syntax is, approximately,
|
||||
// repeat( [ <integer [1,∞]> | auto-fill | auto-fit ] , <track-list> )
|
||||
auto is_auto_fill = false;
|
||||
auto is_auto_fit = false;
|
||||
auto function_tokens = TokenStream(component_values);
|
||||
auto comma_separated_list = parse_a_comma_separated_list_of_component_values(function_tokens);
|
||||
if (comma_separated_list.size() != 2)
|
||||
return {};
|
||||
// The first argument specifies the number of repetitions.
|
||||
TokenStream part_one_tokens { comma_separated_list[0] };
|
||||
part_one_tokens.discard_whitespace();
|
||||
if (!part_one_tokens.has_next_token())
|
||||
return {};
|
||||
auto& current_token = part_one_tokens.consume_a_token();
|
||||
// <fixed-breadth> = <length-percentage [0,∞]>
|
||||
|
||||
auto repeat_count = 0;
|
||||
if (current_token.is(Token::Type::Number) && current_token.token().number().is_integer() && current_token.token().number_value() > 0)
|
||||
repeat_count = current_token.token().number_value();
|
||||
else if (current_token.is_ident("auto-fill"sv))
|
||||
is_auto_fill = true;
|
||||
else if (current_token.is_ident("auto-fit"sv))
|
||||
is_auto_fit = true;
|
||||
|
||||
// The second argument is a track list, which is repeated that number of times.
|
||||
TokenStream part_two_tokens { comma_separated_list[1] };
|
||||
part_two_tokens.discard_whitespace();
|
||||
if (!part_two_tokens.has_next_token())
|
||||
auto transaction = tokens.begin_transaction();
|
||||
auto length_percentage = parse_length_percentage(tokens);
|
||||
if (!length_percentage.has_value())
|
||||
return {};
|
||||
if (length_percentage->is_length() && length_percentage->length().raw_value() < 0)
|
||||
return {};
|
||||
if (length_percentage->is_percentage() && length_percentage->percentage().value() < 0)
|
||||
return {};
|
||||
transaction.commit();
|
||||
return GridSize(length_percentage.release_value());
|
||||
}
|
||||
|
||||
Vector<Variant<ExplicitGridTrack, GridLineNames>> repeat_params;
|
||||
auto last_object_was_line_names = false;
|
||||
while (part_two_tokens.has_next_token()) {
|
||||
auto const& token = part_two_tokens.consume_a_token();
|
||||
Vector<String> line_names;
|
||||
if (token.is_block()) {
|
||||
if (last_object_was_line_names)
|
||||
return {};
|
||||
last_object_was_line_names = true;
|
||||
if (!token.block().is_square())
|
||||
return {};
|
||||
TokenStream block_tokens { token.block().value };
|
||||
while (block_tokens.has_next_token()) {
|
||||
auto const& current_block_token = block_tokens.consume_a_token();
|
||||
line_names.append(current_block_token.token().ident().to_string());
|
||||
block_tokens.discard_whitespace();
|
||||
// https://www.w3.org/TR/css-grid-2/#typedef-line-names
|
||||
Optional<GridLineNames> Parser::parse_grid_line_names(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// <line-names> = '[' <custom-ident>* ']'
|
||||
|
||||
auto transactions = tokens.begin_transaction();
|
||||
GridLineNames line_names;
|
||||
auto const& token = tokens.consume_a_token();
|
||||
if (!token.is_block() || !token.block().is_square())
|
||||
return line_names;
|
||||
|
||||
TokenStream block_tokens { token.block().value };
|
||||
block_tokens.discard_whitespace();
|
||||
while (block_tokens.has_next_token()) {
|
||||
auto maybe_ident = parse_custom_ident(block_tokens, { { "span"sv, "auto"sv } });
|
||||
if (!maybe_ident.has_value())
|
||||
return OptionalNone {};
|
||||
line_names.names.append(maybe_ident.release_value());
|
||||
block_tokens.discard_whitespace();
|
||||
}
|
||||
|
||||
transactions.commit();
|
||||
return line_names;
|
||||
}
|
||||
|
||||
size_t Parser::parse_track_list_impl(TokenStream<ComponentValue>& tokens, GridTrackSizeList& output, GridTrackParser const& track_parsing_callback, AllowTrailingLineNamesForEachTrack allow_trailing_line_names_for_each_track)
|
||||
{
|
||||
size_t parsed_tracks_count = 0;
|
||||
while (tokens.has_next_token()) {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
auto line_names = parse_grid_line_names(tokens);
|
||||
|
||||
tokens.discard_whitespace();
|
||||
auto explicit_grid_track = track_parsing_callback(tokens);
|
||||
tokens.discard_whitespace();
|
||||
|
||||
if (!explicit_grid_track.has_value())
|
||||
break;
|
||||
|
||||
if (line_names.has_value() && !line_names->names.is_empty())
|
||||
output.append(line_names.release_value());
|
||||
|
||||
output.append(explicit_grid_track.release_value());
|
||||
if (allow_trailing_line_names_for_each_track == AllowTrailingLineNamesForEachTrack::Yes) {
|
||||
auto trailing_line_names = parse_grid_line_names(tokens);
|
||||
if (trailing_line_names.has_value() && !trailing_line_names->names.is_empty()) {
|
||||
output.append(trailing_line_names.release_value());
|
||||
}
|
||||
repeat_params.append(GridLineNames { move(line_names) });
|
||||
part_two_tokens.discard_whitespace();
|
||||
} else {
|
||||
last_object_was_line_names = false;
|
||||
auto track_sizing_function = parse_track_sizing_function(token);
|
||||
if (!track_sizing_function.has_value())
|
||||
return {};
|
||||
// However, there are some restrictions:
|
||||
// The repeat() notation can’t be nested.
|
||||
if (track_sizing_function.value().is_repeat())
|
||||
return {};
|
||||
}
|
||||
transaction.commit();
|
||||
parsed_tracks_count++;
|
||||
}
|
||||
|
||||
// Automatic repetitions (auto-fill or auto-fit) cannot be combined with intrinsic or flexible sizes.
|
||||
// Note that 'auto' is also an intrinsic size (and thus not permitted) but we can't use
|
||||
// track_sizing_function.is_auto(..) to check for it, as it requires AvailableSize, which is why there is
|
||||
// a separate check for it below.
|
||||
// https://www.w3.org/TR/css-grid-2/#repeat-syntax
|
||||
// https://www.w3.org/TR/css-grid-2/#intrinsic-sizing-function
|
||||
if (track_sizing_function.value().is_default()
|
||||
&& (track_sizing_function.value().grid_size().is_flexible_length() || token.is_ident("auto"sv))
|
||||
&& (is_auto_fill || is_auto_fit))
|
||||
return {};
|
||||
if ((is_auto_fill || is_auto_fit) && track_sizing_function->is_minmax()) {
|
||||
auto const& minmax = track_sizing_function->minmax();
|
||||
if (!minmax.min_grid_size().is_definite() && !minmax.max_grid_size().is_definite()) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
repeat_params.append(track_sizing_function.value());
|
||||
part_two_tokens.discard_whitespace();
|
||||
if (allow_trailing_line_names_for_each_track == AllowTrailingLineNamesForEachTrack::No) {
|
||||
if (auto trailing_line_names = parse_grid_line_names(tokens); trailing_line_names.has_value() && !trailing_line_names->names.is_empty()) {
|
||||
output.append(trailing_line_names.release_value());
|
||||
}
|
||||
}
|
||||
|
||||
// Thus the precise syntax of the repeat() notation has several forms:
|
||||
// <track-repeat> = repeat( [ <integer [1,∞]> ] , [ <line-names>? <track-size> ]+ <line-names>? )
|
||||
// <auto-repeat> = repeat( [ auto-fill | auto-fit ] , [ <line-names>? <fixed-size> ]+ <line-names>? )
|
||||
// <fixed-repeat> = repeat( [ <integer [1,∞]> ] , [ <line-names>? <fixed-size> ]+ <line-names>? )
|
||||
// <name-repeat> = repeat( [ <integer [1,∞]> | auto-fill ], <line-names>+)
|
||||
|
||||
// The <track-repeat> variant can represent the repetition of any <track-size>, but is limited to a
|
||||
// fixed number of repetitions.
|
||||
|
||||
// The <auto-repeat> variant can repeat automatically to fill a space, but requires definite track
|
||||
// sizes so that the number of repetitions can be calculated. It can only appear once in the track
|
||||
// list, but the same track list can also contain <fixed-repeat>s.
|
||||
|
||||
// The <name-repeat> variant is for adding line names to subgrids. It can only be used with the
|
||||
// subgrid keyword and cannot specify track sizes, only line names.
|
||||
|
||||
// If a repeat() function that is not a <name-repeat> ends up placing two <line-names> adjacent to
|
||||
// each other, the name lists are merged. For example, repeat(2, [a] 1fr [b]) is equivalent to [a]
|
||||
// 1fr [b a] 1fr [b].
|
||||
if (is_auto_fill)
|
||||
return GridRepeat(GridTrackSizeList(move(repeat_params)), GridRepeat::Type::AutoFill);
|
||||
else if (is_auto_fit)
|
||||
return GridRepeat(GridTrackSizeList(move(repeat_params)), GridRepeat::Type::AutoFit);
|
||||
else
|
||||
return GridRepeat(GridTrackSizeList(move(repeat_params)), repeat_count);
|
||||
return parsed_tracks_count;
|
||||
}
|
||||
|
||||
Optional<CSS::ExplicitGridTrack> Parser::parse_track_sizing_function(ComponentValue const& token)
|
||||
Optional<GridRepeat> Parser::parse_grid_track_repeat_impl(TokenStream<ComponentValue>& tokens, GridRepeatTypeParser const& repeat_type_parser, GridTrackParser const& repeat_track_parser)
|
||||
{
|
||||
if (token.is_function()) {
|
||||
auto transaction = tokens.begin_transaction();
|
||||
|
||||
if (!tokens.has_next_token())
|
||||
return {};
|
||||
|
||||
auto const& token = tokens.consume_a_token();
|
||||
if (!token.is_function())
|
||||
return {};
|
||||
|
||||
auto const& function_token = token.function();
|
||||
if (!function_token.name.equals_ignoring_ascii_case("repeat"sv))
|
||||
return {};
|
||||
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function_token.name });
|
||||
|
||||
auto function_tokens = TokenStream(function_token.value);
|
||||
auto comma_separated_list = parse_a_comma_separated_list_of_component_values(function_tokens);
|
||||
if (comma_separated_list.size() != 2)
|
||||
return {};
|
||||
|
||||
TokenStream first_arg_tokens { comma_separated_list[0] };
|
||||
first_arg_tokens.discard_whitespace();
|
||||
if (!first_arg_tokens.has_next_token())
|
||||
return {};
|
||||
|
||||
auto repeat_params = repeat_type_parser(first_arg_tokens);
|
||||
if (!repeat_params.has_value())
|
||||
return {};
|
||||
first_arg_tokens.discard_whitespace();
|
||||
if (first_arg_tokens.has_next_token())
|
||||
return {};
|
||||
|
||||
TokenStream second_arg_tokens { comma_separated_list[1] };
|
||||
second_arg_tokens.discard_whitespace();
|
||||
GridTrackSizeList track_list;
|
||||
if (auto parsed_track_count = parse_track_list_impl(second_arg_tokens, track_list, repeat_track_parser); parsed_track_count == 0)
|
||||
return {};
|
||||
if (second_arg_tokens.has_next_token())
|
||||
return {};
|
||||
transaction.commit();
|
||||
return GridRepeat(GridTrackSizeList(move(track_list)), repeat_params.release_value());
|
||||
}
|
||||
|
||||
Optional<ExplicitGridTrack> Parser::parse_grid_minmax(TokenStream<ComponentValue>& tokens, GridMinMaxParamParser const& min_parser, GridMinMaxParamParser const& max_parser)
|
||||
{
|
||||
auto transaction = tokens.begin_transaction();
|
||||
|
||||
if (!tokens.has_next_token())
|
||||
return {};
|
||||
|
||||
auto const& token = tokens.consume_a_token();
|
||||
if (!token.is_function())
|
||||
return {};
|
||||
|
||||
auto const& function_token = token.function();
|
||||
if (!function_token.name.equals_ignoring_ascii_case("minmax"sv))
|
||||
return {};
|
||||
|
||||
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function_token.name });
|
||||
auto function_tokens = TokenStream(function_token.value);
|
||||
auto comma_separated_list = parse_a_comma_separated_list_of_component_values(function_tokens);
|
||||
if (comma_separated_list.size() != 2)
|
||||
return {};
|
||||
|
||||
TokenStream min_tokens { comma_separated_list[0] };
|
||||
min_tokens.discard_whitespace();
|
||||
auto min_value = min_parser(min_tokens);
|
||||
if (!min_value.has_value())
|
||||
return {};
|
||||
min_tokens.discard_whitespace();
|
||||
if (min_tokens.has_next_token())
|
||||
return {};
|
||||
|
||||
TokenStream max_tokens { comma_separated_list[1] };
|
||||
max_tokens.discard_whitespace();
|
||||
auto max_value = max_parser(max_tokens);
|
||||
if (!max_value.has_value())
|
||||
return {};
|
||||
max_tokens.discard_whitespace();
|
||||
if (max_tokens.has_next_token())
|
||||
return {};
|
||||
|
||||
transaction.commit();
|
||||
return ExplicitGridTrack(GridMinMax(min_value.release_value(), max_value.release_value()));
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-grid-2/#typedef-track-repeat
|
||||
Optional<GridRepeat> Parser::parse_grid_track_repeat(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// <track-repeat> = repeat( [ <integer [1,∞]> ] , [ <line-names>? <track-size> ]+ <line-names>? )
|
||||
|
||||
GridRepeatTypeParser parse_repeat_type = [this](TokenStream<ComponentValue>& tokens) -> Optional<GridRepeatParams> {
|
||||
auto maybe_integer = parse_integer(tokens);
|
||||
if (!maybe_integer.has_value())
|
||||
return {};
|
||||
if (maybe_integer->is_calculated()) {
|
||||
// FIXME: Support calculated repeat counts.
|
||||
return {};
|
||||
}
|
||||
if (maybe_integer->value() < 1)
|
||||
return {};
|
||||
return GridRepeatParams { GridRepeatType::Fixed, static_cast<size_t>(maybe_integer->value()) };
|
||||
};
|
||||
GridTrackParser parse_track = [this](TokenStream<ComponentValue>& tokens) {
|
||||
return parse_grid_track_size(tokens);
|
||||
};
|
||||
return parse_grid_track_repeat_impl(tokens, parse_repeat_type, parse_track);
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-grid-2/#typedef-auto-repeat
|
||||
Optional<GridRepeat> Parser::parse_grid_auto_repeat(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// <auto-repeat> = repeat( [ auto-fill | auto-fit ] , [ <line-names>? <fixed-size> ]+ <line-names>? )
|
||||
|
||||
GridRepeatTypeParser parse_repeat_type = [](TokenStream<ComponentValue>& tokens) -> Optional<GridRepeatParams> {
|
||||
auto const& first_token = tokens.consume_a_token();
|
||||
if (!first_token.is_token() || !first_token.token().is(Token::Type::Ident))
|
||||
return {};
|
||||
|
||||
auto ident_value = first_token.token().ident();
|
||||
if (ident_value.equals_ignoring_ascii_case("auto-fill"sv))
|
||||
return GridRepeatParams { GridRepeatType::AutoFill };
|
||||
if (ident_value.equals_ignoring_ascii_case("auto-fit"sv))
|
||||
return GridRepeatParams { GridRepeatType::AutoFit };
|
||||
return {};
|
||||
};
|
||||
GridTrackParser parse_track = [this](TokenStream<ComponentValue>& tokens) {
|
||||
return parse_grid_fixed_size(tokens);
|
||||
};
|
||||
return parse_grid_track_repeat_impl(tokens, parse_repeat_type, parse_track);
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-grid-2/#typedef-fixed-repeat
|
||||
Optional<GridRepeat> Parser::parse_grid_fixed_repeat(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// <fixed-repeat> = repeat( [ <integer [1,∞]> ] , [ <line-names>? <fixed-size> ]+ <line-names>? )
|
||||
|
||||
GridRepeatTypeParser parse_repeat_type = [this](TokenStream<ComponentValue>& tokens) -> Optional<GridRepeatParams> {
|
||||
auto maybe_integer = parse_integer(tokens);
|
||||
if (!maybe_integer.has_value())
|
||||
return {};
|
||||
if (maybe_integer->is_calculated()) {
|
||||
// FIXME: Support calculated repeat counts.
|
||||
return {};
|
||||
}
|
||||
if (maybe_integer->value() < 1)
|
||||
return {};
|
||||
return GridRepeatParams { GridRepeatType::Fixed, static_cast<size_t>(maybe_integer->value()) };
|
||||
};
|
||||
GridTrackParser parse_track = [this](TokenStream<ComponentValue>& tokens) {
|
||||
return parse_grid_fixed_size(tokens);
|
||||
};
|
||||
return parse_grid_track_repeat_impl(tokens, parse_repeat_type, parse_track);
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-grid-2/#typedef-track-size
|
||||
Optional<ExplicitGridTrack> Parser::parse_grid_track_size(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// <track-size> = <track-breadth> | minmax( <inflexible-breadth> , <track-breadth> ) | fit-content( <length-percentage [0,∞]> )
|
||||
|
||||
if (!tokens.has_next_token())
|
||||
return {};
|
||||
|
||||
if (tokens.peek_token().is_function()) {
|
||||
auto const& token = tokens.peek_token();
|
||||
auto const& function_token = token.function();
|
||||
|
||||
if (function_token.name.equals_ignoring_ascii_case("minmax"sv)) {
|
||||
GridMinMaxParamParser parse_min = [this](auto& tokens) { return parse_grid_inflexible_breadth(tokens); };
|
||||
GridMinMaxParamParser parse_max = [this](auto& tokens) { return parse_grid_track_breadth(tokens); };
|
||||
return parse_grid_minmax(tokens, parse_min, parse_max);
|
||||
}
|
||||
|
||||
auto transaction = tokens.begin_transaction();
|
||||
tokens.discard_a_token();
|
||||
auto context_guard = push_temporary_value_parsing_context(FunctionContext { function_token.name });
|
||||
|
||||
if (function_token.name.equals_ignoring_ascii_case("repeat"sv)) {
|
||||
auto maybe_repeat = parse_repeat(function_token.value);
|
||||
if (maybe_repeat.has_value())
|
||||
return CSS::ExplicitGridTrack(maybe_repeat.value());
|
||||
else
|
||||
if (function_token.name.equals_ignoring_ascii_case("fit-content"sv)) {
|
||||
auto function_tokens = TokenStream(function_token.value);
|
||||
function_tokens.discard_whitespace();
|
||||
auto maybe_length_percentage = parse_grid_fixed_breadth(function_tokens);
|
||||
if (!maybe_length_percentage.has_value())
|
||||
return {};
|
||||
} else if (function_token.name.equals_ignoring_ascii_case("minmax"sv)) {
|
||||
auto maybe_min_max_value = parse_min_max(function_token.value);
|
||||
if (maybe_min_max_value.has_value())
|
||||
return CSS::ExplicitGridTrack(maybe_min_max_value.value());
|
||||
else
|
||||
if (function_tokens.has_next_token())
|
||||
return {};
|
||||
} else if (function_token.name.equals_ignoring_ascii_case("fit-content"sv)) {
|
||||
auto maybe_fit_content_value = parse_grid_fit_content(function_token.value);
|
||||
if (maybe_fit_content_value.has_value())
|
||||
return CSS::ExplicitGridTrack(maybe_fit_content_value.value());
|
||||
return {};
|
||||
} else if (auto maybe_calculated = parse_calculated_value(token)) {
|
||||
if (maybe_calculated->is_length())
|
||||
return ExplicitGridTrack(GridSize(maybe_calculated->as_length().length()));
|
||||
if (maybe_calculated->is_percentage())
|
||||
return ExplicitGridTrack(GridSize(maybe_calculated->as_percentage().percentage()));
|
||||
if (maybe_calculated->is_calculated() && maybe_calculated->as_calculated().resolves_to_length_percentage())
|
||||
return ExplicitGridTrack(GridSize(LengthPercentage(maybe_calculated->as_calculated())));
|
||||
transaction.commit();
|
||||
return ExplicitGridTrack(GridSize(GridSize::Type::FitContent, maybe_length_percentage->length_percentage()));
|
||||
}
|
||||
return {};
|
||||
} else if (token.is_ident("auto"sv)) {
|
||||
return CSS::ExplicitGridTrack(GridSize(Length::make_auto()));
|
||||
} else if (token.is_block()) {
|
||||
return {};
|
||||
} else {
|
||||
auto grid_size = parse_grid_size(token);
|
||||
if (!grid_size.has_value())
|
||||
return {};
|
||||
return CSS::ExplicitGridTrack(grid_size.value());
|
||||
}
|
||||
|
||||
if (auto track_breadth = parse_grid_track_breadth(tokens); track_breadth.has_value()) {
|
||||
return ExplicitGridTrack(track_breadth.value());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-grid-2/#typedef-fixed-size
|
||||
Optional<ExplicitGridTrack> Parser::parse_grid_fixed_size(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// <fixed-size> = <fixed-breadth> | minmax( <fixed-breadth> , <track-breadth> ) | minmax( <inflexible-breadth> , <fixed-breadth> )
|
||||
|
||||
if (!tokens.has_next_token())
|
||||
return {};
|
||||
|
||||
if (tokens.peek_token().is_function()) {
|
||||
auto const& token = tokens.peek_token();
|
||||
auto const& function_token = token.function();
|
||||
if (function_token.name.equals_ignoring_ascii_case("minmax"sv)) {
|
||||
{
|
||||
GridMinMaxParamParser parse_min = [this](auto& tokens) { return parse_grid_fixed_breadth(tokens); };
|
||||
GridMinMaxParamParser parse_max = [this](auto& tokens) { return parse_grid_track_breadth(tokens); };
|
||||
if (auto result = parse_grid_minmax(tokens, parse_min, parse_max); result.has_value())
|
||||
return result;
|
||||
}
|
||||
{
|
||||
GridMinMaxParamParser parse_min = [this](auto& tokens) { return parse_grid_inflexible_breadth(tokens); };
|
||||
GridMinMaxParamParser parse_max = [this](auto& tokens) { return parse_grid_fixed_breadth(tokens); };
|
||||
if (auto result = parse_grid_minmax(tokens, parse_min, parse_max); result.has_value())
|
||||
return result;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (auto fixed_breadth = parse_grid_fixed_breadth(tokens); fixed_breadth.has_value()) {
|
||||
return ExplicitGridTrack(fixed_breadth.value());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-grid-2/#typedef-track-list
|
||||
GridTrackSizeList Parser::parse_grid_track_list(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// <track-list> = [ <line-names>? [ <track-size> | <track-repeat> ] ]+ <line-names>?
|
||||
|
||||
auto transaction = tokens.begin_transaction();
|
||||
GridTrackSizeList track_list;
|
||||
auto parsed_track_count = parse_track_list_impl(tokens, track_list, [&](auto& tokens) -> Optional<ExplicitGridTrack> {
|
||||
if (auto track_repeat = parse_grid_track_repeat(tokens); track_repeat.has_value())
|
||||
return ExplicitGridTrack(track_repeat.value());
|
||||
if (auto track_size = parse_grid_track_size(tokens); track_size.has_value())
|
||||
return ExplicitGridTrack(track_size.value());
|
||||
return Optional<ExplicitGridTrack> {};
|
||||
});
|
||||
if (parsed_track_count == 0)
|
||||
return {};
|
||||
transaction.commit();
|
||||
return track_list;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-grid-2/#typedef-auto-track-list
|
||||
GridTrackSizeList Parser::parse_grid_auto_track_list(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// <auto-track-list> = [ <line-names>? [ <fixed-size> | <fixed-repeat> ] ]* <line-names>? <auto-repeat>
|
||||
// [ <line-names>? [ <fixed-size> | <fixed-repeat> ] ]* <line-names>?
|
||||
|
||||
auto transaction = tokens.begin_transaction();
|
||||
GridTrackSizeList track_list;
|
||||
size_t parsed_track_count = 0;
|
||||
auto parse_zero_or_more_fixed_tracks = [&] {
|
||||
parsed_track_count += parse_track_list_impl(tokens, track_list, [&](auto& tokens) -> Optional<ExplicitGridTrack> {
|
||||
if (auto fixed_repeat = parse_grid_fixed_repeat(tokens); fixed_repeat.has_value())
|
||||
return ExplicitGridTrack(fixed_repeat.value());
|
||||
if (auto fixed_size = parse_grid_fixed_size(tokens); fixed_size.has_value())
|
||||
return ExplicitGridTrack(fixed_size.value());
|
||||
return Optional<ExplicitGridTrack> {};
|
||||
});
|
||||
};
|
||||
|
||||
parse_zero_or_more_fixed_tracks();
|
||||
if (!tokens.has_next_token()) {
|
||||
if (parsed_track_count == 0)
|
||||
return {};
|
||||
transaction.commit();
|
||||
return track_list;
|
||||
}
|
||||
|
||||
if (auto auto_repeat = parse_grid_auto_repeat(tokens); auto_repeat.has_value()) {
|
||||
track_list.append(ExplicitGridTrack(auto_repeat.release_value()));
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
parse_zero_or_more_fixed_tracks();
|
||||
transaction.commit();
|
||||
return track_list;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-grid-2/#typedef-explicit-track-list
|
||||
GridTrackSizeList Parser::parse_explicit_track_list(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// <explicit-track-list> = [ <line-names>? <track-size> ]+ <line-names>?
|
||||
|
||||
auto transaction = tokens.begin_transaction();
|
||||
GridTrackSizeList track_list;
|
||||
auto parsed_track_count = parse_track_list_impl(tokens, track_list, [&](auto& tokens) -> Optional<ExplicitGridTrack> {
|
||||
return parse_grid_track_size(tokens);
|
||||
});
|
||||
if (parsed_track_count == 0)
|
||||
return {};
|
||||
transaction.commit();
|
||||
return track_list;
|
||||
}
|
||||
|
||||
RefPtr<GridTrackPlacementStyleValue const> Parser::parse_grid_track_placement(TokenStream<ComponentValue>& tokens)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue