mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 00:37:35 +00:00
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.
This commit is contained in:
parent
87a30418bf
commit
b1f8a73a05
2 changed files with 203 additions and 1 deletions
|
@ -952,6 +952,176 @@ Optional<MediaQuery::MediaType> Parser::consume_media_type(TokenStream<StyleComp
|
||||||
return {};
|
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)
|
NonnullRefPtrVector<StyleRule> Parser::consume_a_list_of_rules(bool top_level)
|
||||||
{
|
{
|
||||||
return consume_a_list_of_rules(m_token_stream, 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>
|
template<typename T>
|
||||||
Optional<StyleDeclarationRule> Parser::consume_a_declaration(TokenStream<T>& tokens)
|
Optional<StyleDeclarationRule> Parser::consume_a_declaration(TokenStream<T>& tokens)
|
||||||
{
|
{
|
||||||
|
tokens.skip_whitespace();
|
||||||
|
auto start_position = tokens.position();
|
||||||
auto& token = tokens.next_token();
|
auto& token = tokens.next_token();
|
||||||
|
|
||||||
|
if (!token.is(Token::Type::Ident)) {
|
||||||
|
tokens.rewind_to_position(start_position);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
StyleDeclarationRule declaration;
|
StyleDeclarationRule declaration;
|
||||||
VERIFY(token.is(Token::Type::Ident));
|
|
||||||
declaration.m_name = ((Token)token).ident();
|
declaration.m_name = ((Token)token).ident();
|
||||||
|
|
||||||
tokens.skip_whitespace();
|
tokens.skip_whitespace();
|
||||||
|
@ -1186,6 +1362,7 @@ Optional<StyleDeclarationRule> Parser::consume_a_declaration(TokenStream<T>& tok
|
||||||
auto& maybe_colon = tokens.next_token();
|
auto& maybe_colon = tokens.next_token();
|
||||||
if (!maybe_colon.is(Token::Type::Colon)) {
|
if (!maybe_colon.is(Token::Type::Colon)) {
|
||||||
log_parse_error();
|
log_parse_error();
|
||||||
|
tokens.rewind_to_position(start_position);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3663,6 +3840,14 @@ NonnullRefPtrVector<CSS::MediaQuery> parse_media_query_list(CSS::ParsingContext
|
||||||
return parser.parse_as_media_query_list();
|
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)
|
RefPtr<CSS::StyleValue> parse_html_length(DOM::Document const& document, StringView const& string)
|
||||||
{
|
{
|
||||||
auto integer = string.to_int();
|
auto integer = string.to_int();
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <AK/RefPtr.h>
|
#include <AK/RefPtr.h>
|
||||||
#include <AK/Result.h>
|
#include <AK/Result.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
|
#include <LibWeb/CSS/CSSStyleDeclaration.h>
|
||||||
#include <LibWeb/CSS/MediaQuery.h>
|
#include <LibWeb/CSS/MediaQuery.h>
|
||||||
#include <LibWeb/CSS/Parser/DeclarationOrAtRule.h>
|
#include <LibWeb/CSS/Parser/DeclarationOrAtRule.h>
|
||||||
#include <LibWeb/CSS/Parser/StyleBlockRule.h>
|
#include <LibWeb/CSS/Parser/StyleBlockRule.h>
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
#include <LibWeb/CSS/Parser/Tokenizer.h>
|
#include <LibWeb/CSS/Parser/Tokenizer.h>
|
||||||
#include <LibWeb/CSS/Selector.h>
|
#include <LibWeb/CSS/Selector.h>
|
||||||
#include <LibWeb/CSS/StyleValue.h>
|
#include <LibWeb/CSS/StyleValue.h>
|
||||||
|
#include <LibWeb/CSS/Supports.h>
|
||||||
|
|
||||||
namespace Web::CSS {
|
namespace Web::CSS {
|
||||||
|
|
||||||
|
@ -104,6 +106,8 @@ public:
|
||||||
NonnullRefPtrVector<MediaQuery> parse_as_media_query_list();
|
NonnullRefPtrVector<MediaQuery> parse_as_media_query_list();
|
||||||
RefPtr<MediaQuery> parse_as_media_query();
|
RefPtr<MediaQuery> parse_as_media_query();
|
||||||
|
|
||||||
|
RefPtr<Supports> parse_as_supports();
|
||||||
|
|
||||||
RefPtr<StyleValue> parse_as_css_value(PropertyID);
|
RefPtr<StyleValue> parse_as_css_value(PropertyID);
|
||||||
|
|
||||||
// FIXME: This is a hack, while CSS::Supports is using a StyleDeclarationRule
|
// 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>&);
|
Result<SelectorList, ParsingResult> parse_a_relative_selector_list(TokenStream<T>&);
|
||||||
template<typename T>
|
template<typename T>
|
||||||
NonnullRefPtrVector<MediaQuery> parse_a_media_query_list(TokenStream<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>&);
|
Optional<Selector::SimpleSelector::ANPlusBPattern> parse_a_n_plus_b_pattern(TokenStream<StyleComponentValueRule>&);
|
||||||
|
|
||||||
|
@ -177,6 +183,12 @@ private:
|
||||||
template<typename T>
|
template<typename T>
|
||||||
[[nodiscard]] NonnullRefPtr<StyleFunctionRule> consume_a_function(TokenStream<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<CSSRule> convert_to_rule(NonnullRefPtr<StyleRule>);
|
||||||
[[nodiscard]] RefPtr<PropertyOwningCSSStyleDeclaration> convert_to_declaration(NonnullRefPtr<StyleBlockRule>);
|
[[nodiscard]] RefPtr<PropertyOwningCSSStyleDeclaration> convert_to_declaration(NonnullRefPtr<StyleBlockRule>);
|
||||||
|
|
||||||
|
@ -234,6 +246,10 @@ private:
|
||||||
Optional<MediaQuery::MediaFeature> consume_media_feature(TokenStream<StyleComponentValueRule>&);
|
Optional<MediaQuery::MediaFeature> consume_media_feature(TokenStream<StyleComponentValueRule>&);
|
||||||
Optional<MediaQuery::MediaType> consume_media_type(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&);
|
static bool has_ignored_vendor_prefix(StringView const&);
|
||||||
|
|
||||||
ParsingContext m_context;
|
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::CSSRule> parse_css_rule(CSS::ParsingContext const&, StringView);
|
||||||
RefPtr<CSS::MediaQuery> parse_media_query(CSS::ParsingContext const&, StringView const&);
|
RefPtr<CSS::MediaQuery> parse_media_query(CSS::ParsingContext const&, StringView const&);
|
||||||
NonnullRefPtrVector<CSS::MediaQuery> parse_media_query_list(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&);
|
RefPtr<CSS::StyleValue> parse_html_length(DOM::Document const&, StringView const&);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue