diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index a1177b8585e..3e07c61ded3 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -952,6 +952,176 @@ Optional Parser::consume_media_type(TokenStream Parser::parse_as_supports() +{ + return parse_a_supports(m_token_stream); +} + +template +RefPtr Parser::parse_a_supports(TokenStream& tokens) +{ + auto component_values = parse_a_list_of_component_values(tokens); + TokenStream token_stream { component_values }; + auto maybe_condition = parse_supports_condition(token_stream); + token_stream.skip_whitespace(); + if (maybe_condition && !token_stream.has_next_token()) + return Supports::create(maybe_condition.release_nonnull()); + + return {}; +} + +OwnPtr Parser::parse_supports_condition(TokenStream& tokens) +{ + tokens.skip_whitespace(); + auto start_position = tokens.position(); + + auto& peeked_token = tokens.peek_token(); + // `not ` + if (peeked_token.is(Token::Type::Ident) && peeked_token.token().ident().equals_ignoring_case("not")) { + tokens.next_token(); + tokens.skip_whitespace(); + auto child = parse_supports_in_parens(tokens); + if (child.has_value()) { + auto* condition = new Supports::Condition; + condition->type = Supports::Condition::Type::Not; + condition->children.append(child.release_value()); + return adopt_own(*condition); + } + + tokens.rewind_to_position(start_position); + return {}; + } + + // ` [ and ]* + // | [ or ]*` + Vector children; + Optional condition_type {}; + auto as_condition_type = [](auto& token) -> Optional { + if (!token.is(Token::Type::Ident)) + return {}; + auto ident = token.token().ident(); + if (ident.equals_ignoring_case("and")) + return Supports::Condition::Type::And; + if (ident.equals_ignoring_case("or")) + return Supports::Condition::Type::Or; + return {}; + }; + + bool is_invalid = false; + while (tokens.has_next_token()) { + if (!children.is_empty()) { + // Expect `and` or `or` here + auto maybe_combination = as_condition_type(tokens.next_token()); + if (!maybe_combination.has_value()) { + is_invalid = true; + break; + } + if (!condition_type.has_value()) { + condition_type = maybe_combination.value(); + } else if (maybe_combination != condition_type) { + is_invalid = true; + break; + } + } + + tokens.skip_whitespace(); + + if (auto in_parens = parse_supports_in_parens(tokens); in_parens.has_value()) { + children.append(in_parens.release_value()); + } else { + is_invalid = true; + break; + } + + tokens.skip_whitespace(); + } + + if (!is_invalid && !children.is_empty()) { + auto* condition = new Supports::Condition; + condition->type = condition_type.value_or(Supports::Condition::Type::Or); + condition->children = move(children); + return adopt_own(*condition); + } + + tokens.rewind_to_position(start_position); + return {}; +} + +Optional Parser::parse_supports_in_parens(TokenStream& tokens) +{ + tokens.skip_whitespace(); + auto start_position = tokens.position(); + + auto& first_token = tokens.peek_token(); + // `( )` + if (first_token.is_block() && first_token.block().is_paren()) { + tokens.next_token(); + tokens.skip_whitespace(); + + TokenStream child_tokens { first_token.block().values() }; + if (auto condition = parse_supports_condition(child_tokens)) { + if (child_tokens.has_next_token()) { + tokens.rewind_to_position(start_position); + return {}; + } + return Supports::InParens { + .value = { condition.release_nonnull() } + }; + } + + tokens.rewind_to_position(start_position); + } + + // `` + if (auto feature = parse_supports_feature(tokens); feature.has_value()) { + return Supports::InParens { + .value = { feature.release_value() } + }; + } + + // `` + if (auto general_enclosed = parse_general_enclosed(tokens); general_enclosed.has_value()) { + return Supports::InParens { + .value = Supports::GeneralEnclosed {} + }; + } + + tokens.rewind_to_position(start_position); + return {}; +} + +Optional Parser::parse_supports_feature(TokenStream& tokens) +{ + tokens.skip_whitespace(); + auto start_position = tokens.position(); + + auto& first_token = tokens.next_token(); + // `` + if (first_token.is_block() && first_token.block().is_paren()) { + TokenStream block_tokens { first_token.block().values() }; + if (auto declaration = consume_a_declaration(block_tokens); declaration.has_value()) { + return Supports::Feature { + .declaration = declaration.release_value() + }; + } + } + + tokens.rewind_to_position(start_position); + return {}; +} + +Optional Parser::parse_general_enclosed() +{ + return parse_general_enclosed(m_token_stream); +} + +template +Optional Parser::parse_general_enclosed(TokenStream&) +{ + // FIXME: Actually parse this! https://www.w3.org/TR/mediaqueries-5/#typedef-general-enclosed + return {}; +} + NonnullRefPtrVector Parser::consume_a_list_of_rules(bool top_level) { return consume_a_list_of_rules(m_token_stream, top_level); @@ -1175,10 +1345,16 @@ Optional Parser::consume_a_declaration() template Optional Parser::consume_a_declaration(TokenStream& tokens) { + tokens.skip_whitespace(); + auto start_position = tokens.position(); auto& token = tokens.next_token(); + if (!token.is(Token::Type::Ident)) { + tokens.rewind_to_position(start_position); + return {}; + } + StyleDeclarationRule declaration; - VERIFY(token.is(Token::Type::Ident)); declaration.m_name = ((Token)token).ident(); tokens.skip_whitespace(); @@ -1186,6 +1362,7 @@ Optional Parser::consume_a_declaration(TokenStream& tok auto& maybe_colon = tokens.next_token(); if (!maybe_colon.is(Token::Type::Colon)) { log_parse_error(); + tokens.rewind_to_position(start_position); return {}; } @@ -3663,6 +3840,14 @@ NonnullRefPtrVector parse_media_query_list(CSS::ParsingContext return parser.parse_as_media_query_list(); } +RefPtr parse_css_supports(CSS::ParsingContext const& context, StringView const& string) +{ + if (string.is_empty()) + return {}; + CSS::Parser parser(context, string); + return parser.parse_as_supports(); +} + RefPtr parse_html_length(DOM::Document const& document, StringView const& string) { auto integer = string.to_int(); diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index e52575f8a40..5adb96db8f8 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include namespace Web::CSS { @@ -104,6 +106,8 @@ public: NonnullRefPtrVector parse_as_media_query_list(); RefPtr parse_as_media_query(); + RefPtr parse_as_supports(); + RefPtr parse_as_css_value(PropertyID); // FIXME: This is a hack, while CSS::Supports is using a StyleDeclarationRule @@ -142,6 +146,8 @@ private: Result parse_a_relative_selector_list(TokenStream&); template NonnullRefPtrVector parse_a_media_query_list(TokenStream&); + template + RefPtr parse_a_supports(TokenStream&); Optional parse_a_n_plus_b_pattern(TokenStream&); @@ -177,6 +183,12 @@ private: template [[nodiscard]] NonnullRefPtr consume_a_function(TokenStream&); + struct GeneralEnclosed { + }; + [[nodiscard]] Optional parse_general_enclosed(); + template + [[nodiscard]] Optional parse_general_enclosed(TokenStream&); + [[nodiscard]] RefPtr convert_to_rule(NonnullRefPtr); [[nodiscard]] RefPtr convert_to_declaration(NonnullRefPtr); @@ -234,6 +246,10 @@ private: Optional consume_media_feature(TokenStream&); Optional consume_media_type(TokenStream&); + OwnPtr parse_supports_condition(TokenStream&); + Optional parse_supports_in_parens(TokenStream&); + Optional parse_supports_feature(TokenStream&); + static bool has_ignored_vendor_prefix(StringView const&); ParsingContext m_context; @@ -254,6 +270,7 @@ Optional parse_selector(CSS::ParsingContext const&, StringVie RefPtr parse_css_rule(CSS::ParsingContext const&, StringView); RefPtr parse_media_query(CSS::ParsingContext const&, StringView const&); NonnullRefPtrVector parse_media_query_list(CSS::ParsingContext const&, StringView const&); +RefPtr parse_css_supports(CSS::ParsingContext const&, StringView const&); RefPtr parse_html_length(DOM::Document const&, StringView const&);