LibWeb/CSS: Rewrite CSS Parser core methods according to new spec

CSS Syntax 3 (https://drafts.csswg.org/css-syntax) has changed
significantly since we implemented it a couple of years ago. Just about
every parsing algorithm has been rewritten in terms of the new token
stream concept, and to support nested styles. As all of those
algorithms call into each other, this is an unfortunately chonky diff.

As part of this, the transitory types (Declaration, Function, AtRule...)
have been rewritten. That's both because we have new requirements of
what they should be and contain, and also because the spec asks us to
create and then gradually modify them in place, which is easier if they
are plain structs.
This commit is contained in:
Sam Atkins 2024-10-11 11:17:10 +01:00 committed by Andreas Kling
parent f11c0e6cc0
commit e0be17e4fb
Notes: github-actions[bot] 2024-10-14 06:09:33 +00:00
24 changed files with 1126 additions and 1278 deletions

View file

@ -118,7 +118,7 @@ static Optional<RoundingStrategy> parse_rounding_strategy(Vector<ComponentValue>
OwnPtr<CalculationNode> Parser::parse_math_function(PropertyID property_id, Function const& function)
{
TokenStream stream { function.values() };
TokenStream stream { function.value };
auto arguments = parse_a_comma_separated_list_of_component_values(stream);
)~~~");
@ -129,7 +129,7 @@ OwnPtr<CalculationNode> Parser::parse_math_function(PropertyID property_id, Func
auto function_generator = generator.fork();
function_generator.set("name:lowercase", name);
function_generator.set("name:titlecase", title_casify(name));
function_generator.appendln(" if (function.name().equals_ignoring_ascii_case(\"@name:lowercase@\"sv)) {");
function_generator.appendln(" if (function.name.equals_ignoring_ascii_case(\"@name:lowercase@\"sv)) {");
if (function_data.get_bool("is-variadic"sv).value_or(false)) {
// Variadic function
function_generator.append(R"~~~(

View file

@ -2,19 +2,15 @@ source_set("Parser") {
configs += [ "//Userland/Libraries/LibWeb:configs" ]
deps = [ "//Userland/Libraries/LibWeb:all_generated" ]
sources = [
"Block.cpp",
"ComponentValue.cpp",
"Declaration.cpp",
"DeclarationOrAtRule.cpp",
"Function.cpp",
"GradientParsing.cpp",
"Helpers.cpp",
"MediaParsing.cpp",
"Parser.cpp",
"ParsingContext.cpp",
"Rule.cpp",
"SelectorParsing.cpp",
"Token.cpp",
"Tokenizer.cpp",
"Types.cpp",
]
}

View file

@ -72,20 +72,16 @@ set(SOURCES
CSS/MediaQuery.cpp
CSS/MediaQueryList.cpp
CSS/MediaQueryListEvent.cpp
CSS/Parser/Block.cpp
CSS/Parser/ComponentValue.cpp
CSS/Parser/Declaration.cpp
CSS/Parser/DeclarationOrAtRule.cpp
CSS/Parser/Function.cpp
CSS/Parser/GradientParsing.cpp
CSS/Parser/Helpers.cpp
CSS/Parser/MediaParsing.cpp
CSS/Parser/Parser.cpp
CSS/Parser/ParsingContext.cpp
CSS/Parser/Rule.cpp
CSS/Parser/SelectorParsing.cpp
CSS/Parser/Token.cpp
CSS/Parser/Tokenizer.cpp
CSS/Parser/Types.cpp
CSS/ParsedFontFace.cpp
CSS/PercentageOr.cpp
CSS/PreferredColorScheme.cpp

View file

@ -1,31 +0,0 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/CSS/Parser/Block.h>
namespace Web::CSS::Parser {
Block::Block(Token token, Vector<ComponentValue>&& values)
: m_token(move(token))
, m_values(move(values))
{
}
Block::~Block() = default;
String Block::to_string() const
{
StringBuilder builder;
builder.append(m_token.bracket_string());
builder.join(' ', m_values);
builder.append(m_token.bracket_mirror_string());
return MUST(builder.to_string());
}
}

View file

@ -1,42 +0,0 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/RefCounted.h>
#include <AK/Vector.h>
#include <LibWeb/CSS/Parser/ComponentValue.h>
#include <LibWeb/CSS/Parser/Token.h>
#include <LibWeb/Forward.h>
namespace Web::CSS::Parser {
class Block : public RefCounted<Block> {
public:
static NonnullRefPtr<Block> create(Token token, Vector<ComponentValue>&& values)
{
return adopt_ref(*new Block(move(token), move(values)));
}
~Block();
bool is_curly() const { return m_token.is(Token::Type::OpenCurly); }
bool is_paren() const { return m_token.is(Token::Type::OpenParen); }
bool is_square() const { return m_token.is(Token::Type::OpenSquare); }
Token const& token() const { return m_token; }
Vector<ComponentValue> const& values() const { return m_values; }
String to_string() const;
private:
Block(Token, Vector<ComponentValue>&&);
Token m_token;
Vector<ComponentValue> m_values;
};
}

View file

@ -5,9 +5,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/CSS/Parser/Block.h>
#include <LibWeb/CSS/Parser/ComponentValue.h>
#include <LibWeb/CSS/Parser/Function.h>
namespace Web::CSS::Parser {
@ -15,11 +13,11 @@ ComponentValue::ComponentValue(Token token)
: m_value(token)
{
}
ComponentValue::ComponentValue(NonnullRefPtr<Function> function)
ComponentValue::ComponentValue(Function&& function)
: m_value(function)
{
}
ComponentValue::ComponentValue(NonnullRefPtr<Block> block)
ComponentValue::ComponentValue(SimpleBlock&& block)
: m_value(block)
{
}
@ -28,7 +26,7 @@ ComponentValue::~ComponentValue() = default;
bool ComponentValue::is_function(StringView name) const
{
return is_function() && function().name().equals_ignoring_ascii_case(name);
return is_function() && function().name.equals_ignoring_ascii_case(name);
}
bool ComponentValue::is_ident(StringView ident) const
@ -40,8 +38,8 @@ String ComponentValue::to_string() const
{
return m_value.visit(
[](Token const& token) { return token.to_string(); },
[](NonnullRefPtr<Block> const& block) { return block->to_string(); },
[](NonnullRefPtr<Function> const& function) { return function->to_string(); });
[](SimpleBlock const& block) { return block.to_string(); },
[](Function const& function) { return function.to_string(); });
}
String ComponentValue::to_debug_string() const
@ -50,11 +48,11 @@ String ComponentValue::to_debug_string() const
[](Token const& token) {
return MUST(String::formatted("Token: {}", token.to_debug_string()));
},
[](NonnullRefPtr<Block> const& block) {
return MUST(String::formatted("Block: {}", block->to_string()));
[](SimpleBlock const& block) {
return MUST(String::formatted("Block: {}", block.to_string()));
},
[](NonnullRefPtr<Function> const& function) {
return MUST(String::formatted("Function: {}", function->to_string()));
[](Function const& function) {
return MUST(String::formatted("Function: {}", function.to_string()));
});
}

View file

@ -11,24 +11,26 @@
#include <AK/NonnullRefPtr.h>
#include <AK/RefPtr.h>
#include <LibWeb/CSS/Parser/Token.h>
#include <LibWeb/Forward.h>
#include <LibWeb/CSS/Parser/Types.h>
namespace Web::CSS::Parser {
// https://www.w3.org/TR/css-syntax-3/#component-value
// https://drafts.csswg.org/css-syntax/#component-value
class ComponentValue {
public:
ComponentValue(Token);
explicit ComponentValue(NonnullRefPtr<Function>);
explicit ComponentValue(NonnullRefPtr<Block>);
explicit ComponentValue(Function&&);
explicit ComponentValue(SimpleBlock&&);
~ComponentValue();
bool is_block() const { return m_value.has<NonnullRefPtr<Block>>(); }
Block& block() const { return m_value.get<NonnullRefPtr<Block>>(); }
bool is_block() const { return m_value.has<SimpleBlock>(); }
SimpleBlock& block() { return m_value.get<SimpleBlock>(); }
SimpleBlock const& block() const { return m_value.get<SimpleBlock>(); }
bool is_function() const { return m_value.has<NonnullRefPtr<Function>>(); }
bool is_function() const { return m_value.has<Function>(); }
bool is_function(StringView name) const;
Function& function() const { return m_value.get<NonnullRefPtr<Function>>(); }
Function& function() { return m_value.get<Function>(); }
Function const& function() const { return m_value.get<Function>(); }
bool is_token() const { return m_value.has<Token>(); }
bool is(Token::Type type) const { return is_token() && token().is(type); }
@ -41,7 +43,7 @@ public:
String to_debug_string() const;
private:
Variant<Token, NonnullRefPtr<Function>, NonnullRefPtr<Block>> m_value;
Variant<Token, Function, SimpleBlock> m_value;
};
}

View file

@ -1,36 +0,0 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/CSS/Parser/Declaration.h>
#include <LibWeb/CSS/Serialize.h>
namespace Web::CSS::Parser {
Declaration::Declaration(FlyString name, Vector<ComponentValue> values, Important important)
: m_name(move(name))
, m_values(move(values))
, m_important(move(important))
{
}
Declaration::~Declaration() = default;
String Declaration::to_string() const
{
StringBuilder builder;
serialize_an_identifier(builder, m_name);
builder.append(": "sv);
builder.join(' ', m_values);
if (m_important == Important::Yes)
builder.append(" !important"sv);
return MUST(builder.to_string());
}
}

View file

@ -1,34 +0,0 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/FlyString.h>
#include <AK/Vector.h>
#include <LibWeb/CSS/CSSStyleDeclaration.h>
#include <LibWeb/CSS/Parser/ComponentValue.h>
namespace Web::CSS::Parser {
class Declaration {
public:
Declaration(FlyString name, Vector<ComponentValue> values, Important);
~Declaration();
FlyString const& name() const { return m_name; }
Vector<ComponentValue> const& values() const { return m_values; }
Important importance() const { return m_important; }
String to_string() const;
private:
FlyString m_name;
Vector<ComponentValue> m_values;
Important m_important { Important::No };
};
}

View file

@ -1,27 +0,0 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/CSS/Parser/DeclarationOrAtRule.h>
#include <LibWeb/CSS/Parser/Function.h>
namespace Web::CSS::Parser {
DeclarationOrAtRule::DeclarationOrAtRule(RefPtr<Rule> at)
: m_type(DeclarationType::At)
, m_at(move(at))
{
}
DeclarationOrAtRule::DeclarationOrAtRule(Declaration declaration)
: m_type(DeclarationType::Declaration)
, m_declaration(move(declaration))
{
}
DeclarationOrAtRule::~DeclarationOrAtRule() = default;
}

View file

@ -1,47 +0,0 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/CSS/Parser/Declaration.h>
#include <LibWeb/CSS/Parser/Rule.h>
namespace Web::CSS::Parser {
class DeclarationOrAtRule {
public:
explicit DeclarationOrAtRule(RefPtr<Rule> at);
explicit DeclarationOrAtRule(Declaration declaration);
~DeclarationOrAtRule();
enum class DeclarationType {
At,
Declaration,
};
bool is_at_rule() const { return m_type == DeclarationType::At; }
bool is_declaration() const { return m_type == DeclarationType::Declaration; }
Rule const& at_rule() const
{
VERIFY(is_at_rule());
return *m_at;
}
Declaration const& declaration() const
{
VERIFY(is_declaration());
return *m_declaration;
}
private:
DeclarationType m_type;
RefPtr<Rule> m_at;
Optional<Declaration> m_declaration;
};
}

View file

@ -1,34 +0,0 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/CSS/Parser/Function.h>
#include <LibWeb/CSS/Serialize.h>
namespace Web::CSS::Parser {
Function::Function(FlyString name, Vector<ComponentValue>&& values)
: m_name(move(name))
, m_values(move(values))
{
}
Function::~Function() = default;
String Function::to_string() const
{
StringBuilder builder;
serialize_an_identifier(builder, m_name);
builder.append('(');
for (auto& item : m_values)
builder.append(item.to_string());
builder.append(')');
return MUST(builder.to_string());
}
}

View file

@ -1,39 +0,0 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/FlyString.h>
#include <AK/RefCounted.h>
#include <AK/String.h>
#include <AK/Vector.h>
#include <LibWeb/CSS/Parser/ComponentValue.h>
#include <LibWeb/Forward.h>
namespace Web::CSS::Parser {
class Function : public RefCounted<Function> {
public:
static NonnullRefPtr<Function> create(FlyString name, Vector<ComponentValue>&& values)
{
return adopt_ref(*new Function(move(name), move(values)));
}
~Function();
FlyString const& name() const { return m_name; }
Vector<ComponentValue> const& values() const { return m_values; }
String to_string() const;
private:
Function(FlyString name, Vector<ComponentValue>&& values);
FlyString m_name;
Vector<ComponentValue> m_values;
};
}

View file

@ -148,7 +148,7 @@ RefPtr<CSSStyleValue> Parser::parse_linear_gradient_function(TokenStream<Compone
GradientRepeating repeating_gradient = GradientRepeating::No;
GradientType gradient_type { GradientType::Standard };
auto function_name = component_value.function().name().bytes_as_string_view();
auto function_name = component_value.function().name.bytes_as_string_view();
function_name = consume_if_starts_with(function_name, "-webkit-"sv, [&] {
gradient_type = GradientType::WebKit;
@ -163,7 +163,7 @@ RefPtr<CSSStyleValue> Parser::parse_linear_gradient_function(TokenStream<Compone
// linear-gradient() = linear-gradient([ <angle> | to <side-or-corner> ]?, <color-stop-list>)
TokenStream tokens { component_value.function().values() };
TokenStream tokens { component_value.function().value };
tokens.discard_whitespace();
if (!tokens.has_next_token())
@ -277,7 +277,7 @@ RefPtr<CSSStyleValue> Parser::parse_conic_gradient_function(TokenStream<Componen
GradientRepeating repeating_gradient = GradientRepeating::No;
auto function_name = component_value.function().name().bytes_as_string_view();
auto function_name = component_value.function().name.bytes_as_string_view();
function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] {
repeating_gradient = GradientRepeating::Yes;
@ -286,7 +286,7 @@ RefPtr<CSSStyleValue> Parser::parse_conic_gradient_function(TokenStream<Componen
if (!function_name.equals_ignoring_ascii_case("conic-gradient"sv))
return nullptr;
TokenStream tokens { component_value.function().values() };
TokenStream tokens { component_value.function().value };
tokens.discard_whitespace();
if (!tokens.has_next_token())
@ -387,7 +387,7 @@ RefPtr<CSSStyleValue> Parser::parse_radial_gradient_function(TokenStream<Compone
auto repeating_gradient = GradientRepeating::No;
auto function_name = component_value.function().name().bytes_as_string_view();
auto function_name = component_value.function().name.bytes_as_string_view();
function_name = consume_if_starts_with(function_name, "repeating-"sv, [&] {
repeating_gradient = GradientRepeating::Yes;
@ -396,7 +396,7 @@ RefPtr<CSSStyleValue> Parser::parse_radial_gradient_function(TokenStream<Compone
if (!function_name.equals_ignoring_ascii_case("radial-gradient"sv))
return nullptr;
TokenStream tokens { component_value.function().values() };
TokenStream tokens { component_value.function().value };
tokens.discard_whitespace();
if (!tokens.has_next_token())
return nullptr;

View file

@ -522,7 +522,7 @@ OwnPtr<MediaCondition> Parser::parse_media_in_parens(TokenStream<ComponentValue>
// `( <media-condition> ) | ( <media-feature> )`
auto const& first_token = tokens.next_token();
if (first_token.is_block() && first_token.block().is_paren()) {
TokenStream inner_token_stream { first_token.block().values() };
TokenStream inner_token_stream { first_token.block().value };
if (auto maybe_media_condition = parse_media_condition(inner_token_stream, MediaCondition::AllowOr::Yes)) {
tokens.discard_a_token();
transaction.commit();
@ -620,24 +620,17 @@ Optional<MediaFeatureValue> Parser::parse_media_feature_value(MediaFeatureID med
return {};
}
JS::GCPtr<CSSMediaRule> Parser::convert_to_media_rule(Rule& rule)
JS::GCPtr<CSSMediaRule> Parser::convert_to_media_rule(AtRule const& rule)
{
if (!rule.block()) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @media rule: No block.");
return {};
}
auto media_query_tokens = TokenStream { rule.prelude() };
auto media_query_tokens = TokenStream { rule.prelude };
auto media_query_list = parse_a_media_query_list(media_query_tokens);
auto child_tokens = TokenStream { rule.block()->values() };
auto parser_rules = parse_a_list_of_rules(child_tokens);
JS::MarkedVector<CSSRule*> child_rules(m_context.realm().heap());
for (auto& raw_rule : parser_rules) {
if (auto child_rule = convert_to_rule(raw_rule))
child_rules.append(child_rule);
}
auto media_list = MediaList::create(m_context.realm(), move(media_query_list));
JS::MarkedVector<CSSRule*> child_rules { m_context.realm().heap() };
rule.for_each_as_rule_list([&](auto& rule) {
if (auto child_rule = convert_to_rule(rule))
child_rules.append(child_rule);
});
auto rule_list = CSSRuleList::create(m_context.realm(), child_rules);
return CSSMediaRule::create(m_context.realm(), media_list, rule_list);
}

File diff suppressed because it is too large Load diff

View file

@ -16,16 +16,12 @@
#include <LibWeb/CSS/GeneralEnclosed.h>
#include <LibWeb/CSS/MediaQuery.h>
#include <LibWeb/CSS/ParsedFontFace.h>
#include <LibWeb/CSS/Parser/Block.h>
#include <LibWeb/CSS/Parser/ComponentValue.h>
#include <LibWeb/CSS/Parser/Declaration.h>
#include <LibWeb/CSS/Parser/DeclarationOrAtRule.h>
#include <LibWeb/CSS/Parser/Dimension.h>
#include <LibWeb/CSS/Parser/Function.h>
#include <LibWeb/CSS/Parser/ParsingContext.h>
#include <LibWeb/CSS/Parser/Rule.h>
#include <LibWeb/CSS/Parser/TokenStream.h>
#include <LibWeb/CSS/Parser/Tokenizer.h>
#include <LibWeb/CSS/Parser/Types.h>
#include <LibWeb/CSS/PropertyID.h>
#include <LibWeb/CSS/Ratio.h>
#include <LibWeb/CSS/Selector.h>
@ -91,35 +87,34 @@ private:
// "Parse a stylesheet" is intended to be the normal parser entry point, for parsing stylesheets.
struct ParsedStyleSheet {
Optional<URL::URL> location;
Vector<NonnullRefPtr<Rule>> rules;
Vector<Rule> rules;
};
template<typename T>
ParsedStyleSheet parse_a_stylesheet(TokenStream<T>&, Optional<URL::URL> location);
// "Parse a list of rules" is intended for the content of at-rules such as @media. It differs from "Parse a stylesheet" in the handling of <CDO-token> and <CDC-token>.
// "Parse a stylesheets contents" is intended for use by the CSSStyleSheet replace() method, and similar, which parse text into the contents of an existing stylesheet.
template<typename T>
Vector<NonnullRefPtr<Rule>> parse_a_list_of_rules(TokenStream<T>&);
Vector<Rule> parse_a_stylesheets_contents(TokenStream<T>&);
// "Parse a blocks contents" is intended for parsing the contents of any block in CSS (including things like the style attribute),
// and APIs such as the CSSStyleDeclaration cssText attribute.
template<typename T>
Vector<RuleOrListOfDeclarations> parse_a_blocks_contents(TokenStream<T>&);
// "Parse a rule" is intended for use by the CSSStyleSheet#insertRule method, and similar functions which might exist, which parse text into a single rule.
template<typename T>
RefPtr<Rule> parse_a_rule(TokenStream<T>&);
Optional<Rule> parse_a_rule(TokenStream<T>&);
// "Parse a declaration" is used in @supports conditions. [CSS3-CONDITIONAL]
template<typename T>
Optional<Declaration> parse_a_declaration(TokenStream<T>&);
template<typename T>
Vector<DeclarationOrAtRule> parse_a_style_blocks_contents(TokenStream<T>&);
// "Parse a list of declarations" is for the contents of a style attribute, which parses text into the contents of a single style rule.
template<typename T>
Vector<DeclarationOrAtRule> parse_a_list_of_declarations(TokenStream<T>&);
// "Parse a component value" is for things that need to consume a single value, like the parsing rules for attr().
template<typename T>
Optional<ComponentValue> parse_a_component_value(TokenStream<T>&);
// "Parse a list of component values" is for the contents of presentational attributes, which parse text into a single declarations value, or for parsing a stand-alone selector [SELECT] or list of Media Queries [MEDIAQ], as in Selectors API or the media HTML attribute.
// "Parse a list of component values" is for the contents of presentational attributes, which parse text into a single declarations value,
// or for parsing a stand-alone selector [SELECT] or list of Media Queries [MEDIAQ], as in Selectors API or the media HTML attribute.
template<typename T>
Vector<ComponentValue> parse_a_list_of_component_values(TokenStream<T>&);
@ -140,33 +135,37 @@ private:
Optional<Selector::SimpleSelector::ANPlusBPattern> parse_a_n_plus_b_pattern(TokenStream<ComponentValue>&);
enum class TopLevel {
template<typename T>
[[nodiscard]] Vector<Rule> consume_a_stylesheets_contents(TokenStream<T>&);
enum class Nested {
No,
Yes
Yes,
};
template<typename T>
[[nodiscard]] Vector<NonnullRefPtr<Rule>> consume_a_list_of_rules(TokenStream<T>&, TopLevel);
Optional<AtRule> consume_an_at_rule(TokenStream<T>&, Nested nested = Nested::No);
struct InvalidRuleError { };
template<typename T>
[[nodiscard]] NonnullRefPtr<Rule> consume_an_at_rule(TokenStream<T>&);
Variant<Empty, QualifiedRule, InvalidRuleError> consume_a_qualified_rule(TokenStream<T>&, Optional<Token::Type> stop_token = {}, Nested = Nested::No);
template<typename T>
RefPtr<Rule> consume_a_qualified_rule(TokenStream<T>&);
Vector<RuleOrListOfDeclarations> consume_a_block(TokenStream<T>&);
template<typename T>
[[nodiscard]] Vector<DeclarationOrAtRule> consume_a_style_blocks_contents(TokenStream<T>&);
Vector<RuleOrListOfDeclarations> consume_a_blocks_contents(TokenStream<T>&);
template<typename T>
[[nodiscard]] Vector<DeclarationOrAtRule> consume_a_list_of_declarations(TokenStream<T>&);
Optional<Declaration> consume_a_declaration(TokenStream<T>&, Nested = Nested::No);
template<typename T>
Optional<Declaration> consume_a_declaration(TokenStream<T>&);
void consume_the_remnants_of_a_bad_declaration(TokenStream<T>&, Nested);
template<typename T>
[[nodiscard]] Vector<ComponentValue> consume_a_list_of_component_values(TokenStream<T>&, Optional<Token::Type> stop_token = {}, Nested = Nested::No);
template<typename T>
[[nodiscard]] ComponentValue consume_a_component_value(TokenStream<T>&);
template<typename T>
NonnullRefPtr<Block> consume_a_simple_block(TokenStream<T>&);
SimpleBlock consume_a_simple_block(TokenStream<T>&);
template<typename T>
NonnullRefPtr<Function> consume_a_function(TokenStream<T>&);
Function consume_a_function(TokenStream<T>&);
// TODO: consume_a_unicode_range_value()
Optional<GeneralEnclosed> parse_general_enclosed(TokenStream<ComponentValue>&);
JS::GCPtr<CSSFontFaceRule> parse_font_face_rule(TokenStream<ComponentValue>&);
template<typename T>
Vector<ParsedFontFace::Source> parse_font_face_src(TokenStream<T>&);
@ -176,15 +175,19 @@ private:
};
Optional<FlyString> parse_layer_name(TokenStream<ComponentValue>&, AllowBlankLayerName);
JS::GCPtr<CSSRule> convert_to_rule(NonnullRefPtr<Rule>);
JS::GCPtr<CSSKeyframesRule> convert_to_keyframes_rule(Rule&);
JS::GCPtr<CSSImportRule> convert_to_import_rule(Rule&);
JS::GCPtr<CSSRule> convert_to_layer_rule(Rule&);
JS::GCPtr<CSSMediaRule> convert_to_media_rule(Rule&);
JS::GCPtr<CSSNamespaceRule> convert_to_namespace_rule(Rule&);
JS::GCPtr<CSSSupportsRule> convert_to_supports_rule(Rule&);
bool is_valid_in_the_current_context(Declaration&);
bool is_valid_in_the_current_context(AtRule&);
bool is_valid_in_the_current_context(QualifiedRule&);
JS::GCPtr<CSSRule> convert_to_rule(Rule const&);
JS::GCPtr<CSSFontFaceRule> convert_to_font_face_rule(AtRule const&);
JS::GCPtr<CSSKeyframesRule> convert_to_keyframes_rule(AtRule const&);
JS::GCPtr<CSSImportRule> convert_to_import_rule(AtRule const&);
JS::GCPtr<CSSRule> convert_to_layer_rule(AtRule const&);
JS::GCPtr<CSSMediaRule> convert_to_media_rule(AtRule const&);
JS::GCPtr<CSSNamespaceRule> convert_to_namespace_rule(AtRule const&);
JS::GCPtr<CSSSupportsRule> convert_to_supports_rule(AtRule const&);
PropertyOwningCSSStyleDeclaration* convert_to_style_declaration(Vector<DeclarationOrAtRule> const& declarations);
PropertyOwningCSSStyleDeclaration* convert_to_style_declaration(Vector<Declaration> const&);
Optional<StyleProperty> convert_to_style_property(Declaration const&);
Optional<Dimension> parse_dimension(ComponentValue const&);
@ -376,7 +379,8 @@ private:
HashMap<FlyString, StyleProperty> custom_properties;
};
PropertiesAndCustomProperties extract_properties(Vector<DeclarationOrAtRule> const&);
PropertiesAndCustomProperties extract_properties(Vector<RuleOrListOfDeclarations> const&);
void extract_property(Declaration const&, Parser::PropertiesAndCustomProperties&);
ParsingContext m_context;

View file

@ -1,22 +0,0 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/CSS/Parser/Rule.h>
namespace Web::CSS::Parser {
Rule::Rule(Rule::Type type, FlyString name, Vector<ComponentValue> prelude, RefPtr<Block> block)
: m_type(type)
, m_at_rule_name(move(name))
, m_prelude(move(prelude))
, m_block(move(block))
{
}
Rule::~Rule() = default;
}

View file

@ -1,53 +0,0 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/FlyString.h>
#include <AK/RefCounted.h>
#include <AK/Vector.h>
#include <LibWeb/CSS/Parser/Block.h>
#include <LibWeb/CSS/Parser/ComponentValue.h>
namespace Web::CSS::Parser {
class Rule : public RefCounted<Rule> {
public:
enum class Type {
At,
Qualified,
};
static NonnullRefPtr<Rule> make_at_rule(FlyString name, Vector<ComponentValue> prelude, RefPtr<Block> block)
{
return adopt_ref(*new Rule(Type::At, move(name), move(prelude), move(block)));
}
static NonnullRefPtr<Rule> make_qualified_rule(Vector<ComponentValue> prelude, RefPtr<Block> block)
{
return adopt_ref(*new Rule(Type::Qualified, {}, move(prelude), move(block)));
}
~Rule();
bool is_qualified_rule() const { return m_type == Type::Qualified; }
bool is_at_rule() const { return m_type == Type::At; }
Vector<ComponentValue> const& prelude() const { return m_prelude; }
RefPtr<Block const> block() const { return m_block; }
StringView at_rule_name() const { return m_at_rule_name; }
private:
Rule(Type, FlyString name, Vector<ComponentValue> prelude, RefPtr<Block>);
Type const m_type;
FlyString m_at_rule_name;
Vector<ComponentValue> m_prelude;
RefPtr<Block> m_block;
};
}

View file

@ -240,7 +240,7 @@ Optional<Selector::SimpleSelector::QualifiedName> Parser::parse_selector_qualifi
Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_attribute_simple_selector(ComponentValue const& first_value)
{
auto attribute_tokens = TokenStream { first_value.block().values() };
auto attribute_tokens = TokenStream { first_value.block().value };
attribute_tokens.discard_whitespace();
@ -496,34 +496,34 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
};
auto const& pseudo_function = pseudo_class_token.function();
auto maybe_pseudo_class = pseudo_class_from_string(pseudo_function.name());
auto maybe_pseudo_class = pseudo_class_from_string(pseudo_function.name);
if (!maybe_pseudo_class.has_value()) {
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class function: ':{}'()", pseudo_function.name());
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized pseudo-class function: ':{}'()", pseudo_function.name);
return ParseError::SyntaxError;
}
auto pseudo_class = maybe_pseudo_class.value();
auto metadata = pseudo_class_metadata(pseudo_class);
if (!metadata.is_valid_as_function) {
dbgln_if(CSS_PARSER_DEBUG, "Pseudo-class ':{}' is not valid as a function", pseudo_function.name());
dbgln_if(CSS_PARSER_DEBUG, "Pseudo-class ':{}' is not valid as a function", pseudo_function.name);
return ParseError::SyntaxError;
}
if (pseudo_function.values().is_empty()) {
dbgln_if(CSS_PARSER_DEBUG, "Empty :{}() selector", pseudo_function.name());
if (pseudo_function.value.is_empty()) {
dbgln_if(CSS_PARSER_DEBUG, "Empty :{}() selector", pseudo_function.name);
return ParseError::SyntaxError;
}
switch (metadata.parameter_type) {
case PseudoClassMetadata::ParameterType::ANPlusB:
return parse_nth_child_selector(pseudo_class, pseudo_function.values(), false);
return parse_nth_child_selector(pseudo_class, pseudo_function.value, false);
case PseudoClassMetadata::ParameterType::ANPlusBOf:
return parse_nth_child_selector(pseudo_class, pseudo_function.values(), true);
return parse_nth_child_selector(pseudo_class, pseudo_function.value, true);
case PseudoClassMetadata::ParameterType::CompoundSelector: {
auto function_token_stream = TokenStream(pseudo_function.values());
auto function_token_stream = TokenStream(pseudo_function.value);
auto compound_selector_or_error = parse_compound_selector(function_token_stream);
if (compound_selector_or_error.is_error() || !compound_selector_or_error.value().has_value()) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter as a compound selector", pseudo_function.name());
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter as a compound selector", pseudo_function.name);
return ParseError::SyntaxError;
}
@ -542,7 +542,7 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
}
case PseudoClassMetadata::ParameterType::ForgivingRelativeSelectorList:
case PseudoClassMetadata::ParameterType::ForgivingSelectorList: {
auto function_token_stream = TokenStream(pseudo_function.values());
auto function_token_stream = TokenStream(pseudo_function.value);
auto selector_type = metadata.parameter_type == PseudoClassMetadata::ParameterType::ForgivingSelectorList
? SelectorType::Standalone
: SelectorType::Relative;
@ -557,18 +557,18 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
};
}
case PseudoClassMetadata::ParameterType::Ident: {
auto function_token_stream = TokenStream(pseudo_function.values());
auto function_token_stream = TokenStream(pseudo_function.value);
function_token_stream.discard_whitespace();
auto maybe_keyword_token = function_token_stream.consume_a_token();
function_token_stream.discard_whitespace();
if (!maybe_keyword_token.is(Token::Type::Ident) || function_token_stream.has_next_token()) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter as a keyword: not an ident", pseudo_function.name());
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter as a keyword: not an ident", pseudo_function.name);
return ParseError::SyntaxError;
}
auto maybe_keyword = keyword_from_string(maybe_keyword_token.token().ident());
if (!maybe_keyword.has_value()) {
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter as a keyword: unrecognized keyword", pseudo_function.name());
dbgln_if(CSS_PARSER_DEBUG, "Failed to parse :{}() parameter as a keyword: unrecognized keyword", pseudo_function.name);
return ParseError::SyntaxError;
}
@ -581,7 +581,7 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
}
case PseudoClassMetadata::ParameterType::LanguageRanges: {
Vector<FlyString> languages;
auto function_token_stream = TokenStream(pseudo_function.values());
auto function_token_stream = TokenStream(pseudo_function.value);
auto language_token_lists = parse_a_comma_separated_list_of_component_values(function_token_stream);
for (auto language_token_list : language_token_lists) {
@ -589,7 +589,7 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
language_token_stream.discard_whitespace();
auto language_token = language_token_stream.consume_a_token();
if (!(language_token.is(Token::Type::Ident) || language_token.is(Token::Type::String))) {
dbgln_if(CSS_PARSER_DEBUG, "Invalid language range in :{}() - not a string/ident", pseudo_function.name());
dbgln_if(CSS_PARSER_DEBUG, "Invalid language range in :{}() - not a string/ident", pseudo_function.name);
return ParseError::SyntaxError;
}
@ -598,7 +598,7 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
language_token_stream.discard_whitespace();
if (language_token_stream.has_next_token()) {
dbgln_if(CSS_PARSER_DEBUG, "Invalid language range in :{}() - trailing tokens", pseudo_function.name());
dbgln_if(CSS_PARSER_DEBUG, "Invalid language range in :{}() - trailing tokens", pseudo_function.name);
return ParseError::SyntaxError;
}
}
@ -611,7 +611,7 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_pseudo_simple_selec
};
}
case PseudoClassMetadata::ParameterType::SelectorList: {
auto function_token_stream = TokenStream(pseudo_function.values());
auto function_token_stream = TokenStream(pseudo_function.value);
auto not_selector = TRY(parse_a_selector_list(function_token_stream, SelectorType::Standalone));
return Selector::SimpleSelector {

View file

@ -0,0 +1,142 @@
/*
* Copyright (c) 2020-2021, the SerenityOS developers.
* Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <LibWeb/CSS/Parser/ComponentValue.h>
#include <LibWeb/CSS/Parser/Types.h>
#include <LibWeb/CSS/Serialize.h>
namespace Web::CSS::Parser {
String Declaration::to_string() const
{
if (original_text.has_value())
return original_text.value();
StringBuilder builder;
serialize_an_identifier(builder, name);
builder.append(": "sv);
builder.join(' ', value);
if (important == Important::Yes)
builder.append(" !important"sv);
return MUST(builder.to_string());
}
String SimpleBlock::to_string() const
{
StringBuilder builder;
builder.append(token.bracket_string());
builder.join(' ', value);
builder.append(token.bracket_mirror_string());
return builder.to_string_without_validation();
}
String Function::to_string() const
{
StringBuilder builder;
serialize_an_identifier(builder, name);
builder.append('(');
for (auto& item : value)
builder.append(item.to_string());
builder.append(')');
return builder.to_string_without_validation();
}
void AtRule::for_each(AtRuleVisitor&& visit_at_rule, QualifiedRuleVisitor&& visit_qualified_rule, DeclarationVisitor&& visit_declaration) const
{
for (auto const& child : child_rules_and_lists_of_declarations) {
child.visit(
[&](Rule const& rule) {
rule.visit(
[&](AtRule const& at_rule) { visit_at_rule(at_rule); },
[&](QualifiedRule const& qualified_rule) { visit_qualified_rule(qualified_rule); });
},
[&](Vector<Declaration> const& declarations) {
for (auto const& declaration : declarations)
visit_declaration(declaration);
});
}
}
// https://drafts.csswg.org/css-syntax/#typedef-declaration-list
void AtRule::for_each_as_declaration_list(DeclarationVisitor&& visit) const
{
// <declaration-list>: only declarations are allowed; at-rules and qualified rules are automatically invalid.
for_each(
[](auto const& at_rule) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal @{} rule in `<declaration-list>`; discarding.", at_rule.name); },
[](auto const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal qualified rule in `<declaration-list>`; discarding."); },
move(visit));
}
// https://drafts.csswg.org/css-syntax/#typedef-qualified-rule-list
void AtRule::for_each_as_qualified_rule_list(QualifiedRuleVisitor&& visit) const
{
// <qualified-rule-list>: only qualified rules are allowed; declarations and at-rules are automatically invalid.
for_each(
[](auto const& at_rule) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal @{} rule in `<qualified-rule-list>`; discarding.", at_rule.name); },
move(visit),
[](auto const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal list of declarations in `<qualified-rule-list>`; discarding."); });
}
// https://drafts.csswg.org/css-syntax/#typedef-at-rule-list
void AtRule::for_each_as_at_rule_list(AtRuleVisitor&& visit) const
{
// <at-rule-list>: only at-rules are allowed; declarations and qualified rules are automatically invalid.
for_each(
move(visit),
[](auto const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal qualified rule in `<at-rule-list>`; discarding."); },
[](auto const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal list of declarations in `<at-rule-list>`; discarding."); });
}
// https://drafts.csswg.org/css-syntax/#typedef-declaration-rule-list
void AtRule::for_each_as_declaration_rule_list(AtRuleVisitor&& visit_at_rule, DeclarationVisitor&& visit_declaration) const
{
// <declaration-rule-list>: declarations and at-rules are allowed; qualified rules are automatically invalid.
for_each(
move(visit_at_rule),
[](auto const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal qualified rule in `<declaration-rule-list>`; discarding."); },
move(visit_declaration));
}
// https://drafts.csswg.org/css-syntax/#typedef-rule-list
void AtRule::for_each_as_rule_list(RuleVisitor&& visit) const
{
// <rule-list>: qualified rules and at-rules are allowed; declarations are automatically invalid.
for (auto const& child : child_rules_and_lists_of_declarations) {
child.visit(
[&](Rule const& rule) { visit(rule); },
[&](Vector<Declaration> const&) { dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal list of declarations in `<rule-list>`; discarding."); });
}
}
// https://drafts.csswg.org/css-syntax/#typedef-declaration-list
void QualifiedRule::for_each_as_declaration_list(DeclarationVisitor&& visit) const
{
// <declaration-list>: only declarations are allowed; at-rules and qualified rules are automatically invalid.
for (auto const& declaration : declarations)
visit(declaration);
for (auto const& child : child_rules) {
child.visit(
[&](Rule const&) {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Found illegal qualified rule in `<declaration-list>`; discarding.");
},
[&](Vector<Declaration> const& declarations) {
for (auto const& declaration : declarations)
visit(declaration);
});
}
}
}

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/FlyString.h>
#include <AK/Function.h>
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibWeb/CSS/StyleProperty.h>
#include <LibWeb/Forward.h>
namespace Web::CSS::Parser {
// https://drafts.csswg.org/css-syntax/#css-rule
using Rule = Variant<AtRule, QualifiedRule>;
using RuleOrListOfDeclarations = Variant<Rule, Vector<Declaration, 0>>;
using AtRuleVisitor = AK::Function<void(AtRule const&)>;
using QualifiedRuleVisitor = AK::Function<void(QualifiedRule const&)>;
using RuleVisitor = AK::Function<void(Rule const&)>;
using DeclarationVisitor = AK::Function<void(Declaration const&)>;
// https://drafts.csswg.org/css-syntax/#ref-for-at-rule%E2%91%A0%E2%91%A1
struct AtRule {
FlyString name;
Vector<ComponentValue> prelude;
Vector<RuleOrListOfDeclarations> child_rules_and_lists_of_declarations;
void for_each(AtRuleVisitor&& visit_at_rule, QualifiedRuleVisitor&& visit_qualified_rule, DeclarationVisitor&& visit_declaration) const;
void for_each_as_declaration_list(DeclarationVisitor&& visit) const;
void for_each_as_qualified_rule_list(QualifiedRuleVisitor&& visit) const;
void for_each_as_at_rule_list(AtRuleVisitor&& visit) const;
void for_each_as_declaration_rule_list(AtRuleVisitor&& visit_at_rule, DeclarationVisitor&& visit_declaration) const;
void for_each_as_rule_list(RuleVisitor&& visit) const;
};
// https://drafts.csswg.org/css-syntax/#qualified-rule
struct QualifiedRule {
Vector<ComponentValue> prelude;
Vector<Declaration> declarations;
Vector<RuleOrListOfDeclarations> child_rules;
void for_each_as_declaration_list(DeclarationVisitor&& visit) const;
};
// https://drafts.csswg.org/css-syntax/#declaration
struct Declaration {
FlyString name;
Vector<ComponentValue> value;
Important important = Important::No;
Optional<String> original_text = {};
// FIXME: Only needed by our janky @supports re-serialization-re-parse code.
String to_string() const;
};
// https://drafts.csswg.org/css-syntax/#simple-block
struct SimpleBlock {
Token token;
Vector<ComponentValue> value;
bool is_curly() const { return token.is(Token::Type::OpenCurly); }
bool is_paren() const { return token.is(Token::Type::OpenParen); }
bool is_square() const { return token.is(Token::Type::OpenSquare); }
String to_string() const;
};
// https://drafts.csswg.org/css-syntax/#function
struct Function {
FlyString name;
Vector<ComponentValue> value;
String to_string() const;
};
}

View file

@ -12,7 +12,6 @@
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibWeb/CSS/GeneralEnclosed.h>
#include <LibWeb/CSS/Parser/Declaration.h>
namespace Web::CSS {

View file

@ -227,13 +227,13 @@ struct BackgroundLayerData;
}
namespace Web::CSS::Parser {
class Block;
struct AtRule;
class ComponentValue;
class Declaration;
class DeclarationOrAtRule;
class Function;
struct Declaration;
struct Function;
class Parser;
class Rule;
struct QualifiedRule;
struct SimpleBlock;
class Token;
class Tokenizer;
}