diff options
author | Sam Atkins <atkinssj@serenityos.org> | 2021-10-08 15:54:16 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-10-08 23:02:57 +0200 |
commit | b1f8a73a0578e85b983eaa2edeb1ccf28ef0cfaf (patch) | |
tree | 44a704de26b428ac28e45336814a833c802049d6 /Userland/Libraries/LibWeb | |
parent | 87a30418bfff4d1a8481eec0a11a947f4c3b2485 (diff) | |
download | serenity-b1f8a73a0578e85b983eaa2edeb1ccf28ef0cfaf.zip |
LibWeb: Parse CSS `Supports`
... according to
https://www.w3.org/TR/css-conditional-3/#typedef-supports-condition
This works very similarly to `@media`, but is different enough to
require its own parsing. (Though, the draft of Conditional-4 currently
mentions combining the two into a `@when` rule.)
Made some small changes to parsing code to make this work. Notably,
making `consume_a_declaration()` fail gracefully instead of
`VERIFY()`ing.
Diffstat (limited to 'Userland/Libraries/LibWeb')
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp | 187 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.h | 17 |
2 files changed, 203 insertions, 1 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index a1177b8585..3e07c61ded 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -952,6 +952,176 @@ Optional<MediaQuery::MediaType> Parser::consume_media_type(TokenStream<StyleComp return {}; } +RefPtr<Supports> Parser::parse_as_supports() +{ + return parse_a_supports(m_token_stream); +} + +template<typename T> +RefPtr<Supports> Parser::parse_a_supports(TokenStream<T>& tokens) +{ + auto component_values = parse_a_list_of_component_values(tokens); + TokenStream<StyleComponentValueRule> 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<Supports::Condition> Parser::parse_supports_condition(TokenStream<StyleComponentValueRule>& tokens) +{ + tokens.skip_whitespace(); + auto start_position = tokens.position(); + + auto& peeked_token = tokens.peek_token(); + // `not <supports-in-parens>` + 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 {}; + } + + // ` <supports-in-parens> [ and <supports-in-parens> ]* + // | <supports-in-parens> [ or <supports-in-parens> ]*` + Vector<Supports::InParens> children; + Optional<Supports::Condition::Type> condition_type {}; + auto as_condition_type = [](auto& token) -> Optional<Supports::Condition::Type> { + 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<Supports::InParens> Parser::parse_supports_in_parens(TokenStream<StyleComponentValueRule>& tokens) +{ + tokens.skip_whitespace(); + auto start_position = tokens.position(); + + auto& first_token = tokens.peek_token(); + // `( <supports-condition> )` + 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); + } + + // `<supports-feature>` + if (auto feature = parse_supports_feature(tokens); feature.has_value()) { + return Supports::InParens { + .value = { feature.release_value() } + }; + } + + // `<general-enclosed>` + 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<Supports::Feature> Parser::parse_supports_feature(TokenStream<StyleComponentValueRule>& tokens) +{ + tokens.skip_whitespace(); + auto start_position = tokens.position(); + + auto& first_token = tokens.next_token(); + // `<supports-decl>` + 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::GeneralEnclosed> Parser::parse_general_enclosed() +{ + return parse_general_enclosed(m_token_stream); +} + +template<typename T> +Optional<Parser::GeneralEnclosed> Parser::parse_general_enclosed(TokenStream<T>&) +{ + // FIXME: Actually parse this! https://www.w3.org/TR/mediaqueries-5/#typedef-general-enclosed + return {}; +} + NonnullRefPtrVector<StyleRule> 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<StyleDeclarationRule> Parser::consume_a_declaration() template<typename T> Optional<StyleDeclarationRule> Parser::consume_a_declaration(TokenStream<T>& 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<StyleDeclarationRule> Parser::consume_a_declaration(TokenStream<T>& 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<CSS::MediaQuery> parse_media_query_list(CSS::ParsingContext return parser.parse_as_media_query_list(); } +RefPtr<CSS::Supports> 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<CSS::StyleValue> 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 e52575f8a4..5adb96db8f 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -12,6 +12,7 @@ #include <AK/RefPtr.h> #include <AK/Result.h> #include <AK/Vector.h> +#include <LibWeb/CSS/CSSStyleDeclaration.h> #include <LibWeb/CSS/MediaQuery.h> #include <LibWeb/CSS/Parser/DeclarationOrAtRule.h> #include <LibWeb/CSS/Parser/StyleBlockRule.h> @@ -22,6 +23,7 @@ #include <LibWeb/CSS/Parser/Tokenizer.h> #include <LibWeb/CSS/Selector.h> #include <LibWeb/CSS/StyleValue.h> +#include <LibWeb/CSS/Supports.h> namespace Web::CSS { @@ -104,6 +106,8 @@ public: NonnullRefPtrVector<MediaQuery> parse_as_media_query_list(); RefPtr<MediaQuery> parse_as_media_query(); + RefPtr<Supports> parse_as_supports(); + RefPtr<StyleValue> parse_as_css_value(PropertyID); // FIXME: This is a hack, while CSS::Supports is using a StyleDeclarationRule @@ -142,6 +146,8 @@ private: Result<SelectorList, ParsingResult> parse_a_relative_selector_list(TokenStream<T>&); template<typename T> NonnullRefPtrVector<MediaQuery> parse_a_media_query_list(TokenStream<T>&); + template<typename T> + RefPtr<Supports> parse_a_supports(TokenStream<T>&); Optional<Selector::SimpleSelector::ANPlusBPattern> parse_a_n_plus_b_pattern(TokenStream<StyleComponentValueRule>&); @@ -177,6 +183,12 @@ private: template<typename T> [[nodiscard]] NonnullRefPtr<StyleFunctionRule> consume_a_function(TokenStream<T>&); + struct GeneralEnclosed { + }; + [[nodiscard]] Optional<GeneralEnclosed> parse_general_enclosed(); + template<typename T> + [[nodiscard]] Optional<GeneralEnclosed> parse_general_enclosed(TokenStream<T>&); + [[nodiscard]] RefPtr<CSSRule> convert_to_rule(NonnullRefPtr<StyleRule>); [[nodiscard]] RefPtr<PropertyOwningCSSStyleDeclaration> convert_to_declaration(NonnullRefPtr<StyleBlockRule>); @@ -234,6 +246,10 @@ private: Optional<MediaQuery::MediaFeature> consume_media_feature(TokenStream<StyleComponentValueRule>&); Optional<MediaQuery::MediaType> consume_media_type(TokenStream<StyleComponentValueRule>&); + OwnPtr<Supports::Condition> parse_supports_condition(TokenStream<StyleComponentValueRule>&); + Optional<Supports::InParens> parse_supports_in_parens(TokenStream<StyleComponentValueRule>&); + Optional<Supports::Feature> parse_supports_feature(TokenStream<StyleComponentValueRule>&); + static bool has_ignored_vendor_prefix(StringView const&); ParsingContext m_context; @@ -254,6 +270,7 @@ Optional<CSS::SelectorList> parse_selector(CSS::ParsingContext const&, StringVie RefPtr<CSS::CSSRule> parse_css_rule(CSS::ParsingContext const&, StringView); RefPtr<CSS::MediaQuery> parse_media_query(CSS::ParsingContext const&, StringView const&); NonnullRefPtrVector<CSS::MediaQuery> parse_media_query_list(CSS::ParsingContext const&, StringView const&); +RefPtr<CSS::Supports> parse_css_supports(CSS::ParsingContext const&, StringView const&); RefPtr<CSS::StyleValue> parse_html_length(DOM::Document const&, StringView const&); |