1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 04:27:44 +00:00

LibWeb: Expose StyleValue parsing method in CSS Parser

Now that StyleResolver is going to deal with StyleComponentValueRules,
it will need to be able to parse those into StyleValues, using
`parse_css_value()`.

Also added StyleValue::is_builtin_or_dynamic(), which returns true for
values that are valid anywhere - things like `initial` and `inherit`,
along with `var()`, `attr()` and `calc()` - which we want to test for
easily.
This commit is contained in:
Sam Atkins 2021-07-18 17:12:23 +01:00 committed by Andreas Kling
parent 57d34f1966
commit 6e0376361a
3 changed files with 116 additions and 55 deletions

View file

@ -1271,17 +1271,36 @@ Optional<float> Parser::try_parse_float(StringView string)
return is_negative ? -value : value; return is_negative ? -value : value;
} }
RefPtr<StyleValue> Parser::parse_single_css_value(PropertyID property_id, StyleComponentValueRule const& component_value) RefPtr<StyleValue> Parser::parse_keyword_or_custom_value(ParsingContext const&, StyleComponentValueRule const& component_value)
{ {
dbgln_if(CSS_PARSER_TRACE, "Parser::parse_single_css_value '{}'", component_value.to_debug_string()); if (component_value.is(Token::Type::Ident)) {
// FIXME: This is mostly copied from the old, deprecated parser. It is probably not to spec. auto ident = component_value.token().ident();
if (ident.equals_ignoring_case("inherit"))
return InheritStyleValue::create();
if (ident.equals_ignoring_case("initial"))
return InitialStyleValue::create();
if (ident.equals_ignoring_case("auto"))
return LengthStyleValue::create(Length::make_auto());
// FIXME: Implement `unset` keyword
}
auto takes_integer_value = [](PropertyID property_id) -> bool { if (component_value.is_function() && component_value.function().name().equals_ignoring_case("var")) {
return property_id == PropertyID::ZIndex // FIXME: Handle fallback value as second parameter
|| property_id == PropertyID::FontWeight // https://www.w3.org/TR/css-variables-1/#using-variables
|| property_id == PropertyID::Custom; if (!component_value.function().values().is_empty()) {
}; auto& property_name_token = component_value.function().values().first();
if (property_name_token.is(Token::Type::Ident))
return CustomStyleValue::create(property_name_token.token().ident());
else
dbgln("First argument to var() function was not an ident: '{}'", property_name_token.to_debug_string());
}
}
return {};
}
RefPtr<StyleValue> Parser::parse_length_value(ParsingContext const& context, StyleComponentValueRule const& component_value)
{
auto parse_length = [&]() -> Optional<Length> { auto parse_length = [&]() -> Optional<Length> {
Length::Type type = Length::Type::Undefined; Length::Type type = Length::Type::Undefined;
Optional<float> numeric_value; Optional<float> numeric_value;
@ -1320,7 +1339,7 @@ RefPtr<StyleValue> Parser::parse_single_css_value(PropertyID property_id, StyleC
type = Length::Type::In; type = Length::Type::In;
} else if (unit_string.equals_ignoring_case("Q")) { } else if (unit_string.equals_ignoring_case("Q")) {
type = Length::Type::Q; type = Length::Type::Q;
} else if (m_context.in_quirks_mode()) { } else if (context.in_quirks_mode()) {
type = Length::Type::Px; type = Length::Type::Px;
} }
@ -1330,10 +1349,14 @@ RefPtr<StyleValue> Parser::parse_single_css_value(PropertyID property_id, StyleC
if (value_string == "0") { if (value_string == "0") {
type = Length::Type::Px; type = Length::Type::Px;
numeric_value = 0; numeric_value = 0;
} else if (m_context.in_quirks_mode()) { } else if (context.in_quirks_mode()) {
type = Length::Type::Px; type = Length::Type::Px;
numeric_value = try_parse_float(value_string); numeric_value = try_parse_float(value_string);
} }
} else if (component_value.is(Token::Type::Percentage)) {
type = Length::Type::Percentage;
auto value_string = component_value.token().m_value.string_view();
numeric_value = try_parse_float(value_string);
} }
if (!numeric_value.has_value()) if (!numeric_value.has_value())
@ -1342,53 +1365,46 @@ RefPtr<StyleValue> Parser::parse_single_css_value(PropertyID property_id, StyleC
return Length(numeric_value.value(), type); return Length(numeric_value.value(), type);
}; };
if (takes_integer_value(property_id) && component_value.is(Token::Type::Number)) { if (component_value.is(Token::Type::Dimension) || component_value.is(Token::Type::Number) || component_value.is(Token::Type::Percentage)) {
auto number = component_value.token();
if (number.m_number_type == Token::NumberType::Integer) {
return LengthStyleValue::create(Length::make_px(number.integer()));
}
}
if (component_value.is(Token::Type::Dimension) || component_value.is(Token::Type::Number)) {
auto length = parse_length(); auto length = parse_length();
if (length.has_value()) if (length.has_value())
return LengthStyleValue::create(length.value()); return LengthStyleValue::create(length.value());
auto value_string = component_value.token().m_value.string_view();
auto float_number = try_parse_float(value_string);
if (float_number.has_value())
return NumericStyleValue::create(float_number.value());
return nullptr;
} }
if (component_value.is(Token::Type::Ident)) { return {};
auto ident = component_value.token().ident(); }
if (ident.equals_ignoring_case("inherit"))
return InheritStyleValue::create();
if (ident.equals_ignoring_case("initial"))
return InitialStyleValue::create();
if (ident.equals_ignoring_case("auto"))
return LengthStyleValue::create(Length::make_auto());
}
if (component_value.is_function() && component_value.function().name().equals_ignoring_case("var")) { RefPtr<StyleValue> Parser::parse_numeric_value(ParsingContext const&, StyleComponentValueRule const& component_value)
// FIXME: Handle fallback value as second parameter {
// https://www.w3.org/TR/css-variables-1/#using-variables if (component_value.is(Token::Type::Number)) {
if (!component_value.function().values().is_empty()) { auto number = component_value.token();
auto& property_name_token = component_value.function().values().first(); if (number.m_number_type == Token::NumberType::Integer) {
if (property_name_token.is(Token::Type::Ident)) // FIXME: This seems wrong, but it's how the old parser did things, as well as it
return CustomStyleValue::create(property_name_token.token().ident()); // whitelisting ZIndex, FontWeight and Custom PropertyIDs to allow this.
else return LengthStyleValue::create(Length::make_px(number.integer()));
dbgln("First argument to var() function was not an ident: '{}'", property_name_token.to_debug_string()); } else {
auto float_value = try_parse_float(number.m_value.string_view());
if (float_value.has_value())
return NumericStyleValue::create(float_value.value());
} }
} }
return {};
}
RefPtr<StyleValue> Parser::parse_identifier_value(ParsingContext const&, StyleComponentValueRule const& component_value)
{
if (component_value.is(Token::Type::Ident)) { if (component_value.is(Token::Type::Ident)) {
auto value_id = value_id_from_string(component_value.token().ident()); auto value_id = value_id_from_string(component_value.token().ident());
if (value_id != ValueID::Invalid) if (value_id != ValueID::Invalid)
return IdentifierStyleValue::create(value_id); return IdentifierStyleValue::create(value_id);
} }
return {};
}
RefPtr<StyleValue> Parser::parse_color_value(ParsingContext const&, StyleComponentValueRule const& component_value)
{
auto parse_css_color = [&]() -> Optional<Color> { auto parse_css_color = [&]() -> Optional<Color> {
if (component_value.is(Token::Type::Ident) && component_value.token().ident().equals_ignoring_case("transparent")) if (component_value.is(Token::Type::Ident) && component_value.token().ident().equals_ignoring_case("transparent"))
return Color::from_rgba(0x00000000); return Color::from_rgba(0x00000000);
@ -1408,6 +1424,11 @@ RefPtr<StyleValue> Parser::parse_single_css_value(PropertyID property_id, StyleC
if (color.has_value()) if (color.has_value())
return ColorStyleValue::create(color.value()); return ColorStyleValue::create(color.value());
return {};
}
RefPtr<StyleValue> Parser::parse_string_value(ParsingContext const&, StyleComponentValueRule const& component_value)
{
if (component_value.is(Token::Type::String)) if (component_value.is(Token::Type::String))
return StringStyleValue::create(component_value.token().string()); return StringStyleValue::create(component_value.token().string());
@ -1438,11 +1459,50 @@ RefPtr<StyleValue> Parser::parse_css_value(PropertyID property_id, TokenStream<S
return {}; return {};
if (component_values.size() == 1) if (component_values.size() == 1)
return parse_single_css_value(property_id, component_values.first()); return parse_css_value(m_context, property_id, component_values.first());
return ValueListStyleValue::create(move(component_values)); return ValueListStyleValue::create(move(component_values));
} }
RefPtr<StyleValue> Parser::parse_css_value(ParsingContext const& context, PropertyID property_id, StyleComponentValueRule const& component_value)
{
dbgln_if(CSS_PARSER_TRACE, "Parser::parse_css_value '{}'", component_value.to_debug_string());
// FIXME: Figure out if we still need takes_integer_value, and if so, move this information
// into Properties.json.
auto takes_integer_value = [](PropertyID property_id) -> bool {
return property_id == PropertyID::ZIndex
|| property_id == PropertyID::FontWeight
|| property_id == PropertyID::Custom;
};
if (takes_integer_value(property_id) && component_value.is(Token::Type::Number)) {
auto number = component_value.token();
if (number.m_number_type == Token::NumberType::Integer) {
return LengthStyleValue::create(Length::make_px(number.integer()));
}
}
if (auto keyword_or_custom = parse_keyword_or_custom_value(context, component_value))
return keyword_or_custom;
if (auto length = parse_length_value(context, component_value))
return length;
if (auto numeric = parse_numeric_value(context, component_value))
return numeric;
if (auto identifier = parse_identifier_value(context, component_value))
return identifier;
if (auto color = parse_color_value(context, component_value))
return color;
if (auto string = parse_string_value(context, component_value))
return string;
return {};
}
Optional<Selector::SimpleSelector::NthChildPattern> Parser::parse_nth_child_pattern(TokenStream<StyleComponentValueRule>& values) Optional<Selector::SimpleSelector::NthChildPattern> Parser::parse_nth_child_pattern(TokenStream<StyleComponentValueRule>& values)
{ {
dbgln_if(CSS_PARSER_TRACE, "Parser::parse_nth_child_pattern"); dbgln_if(CSS_PARSER_TRACE, "Parser::parse_nth_child_pattern");

View file

@ -19,6 +19,7 @@
#include <LibWeb/CSS/Parser/StyleRule.h> #include <LibWeb/CSS/Parser/StyleRule.h>
#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>
namespace Web::CSS { namespace Web::CSS {
@ -26,7 +27,6 @@ class CSSStyleSheet;
class CSSRule; class CSSRule;
class CSSStyleRule; class CSSStyleRule;
struct StyleProperty; struct StyleProperty;
class StyleValue;
enum class PropertyID; enum class PropertyID;
class ParsingContext { class ParsingContext {
@ -127,16 +127,7 @@ public:
NonnullRefPtrVector<Selector> parse_a_relative_selector(TokenStream<T>&); NonnullRefPtrVector<Selector> parse_a_relative_selector(TokenStream<T>&);
RefPtr<StyleValue> parse_css_value(PropertyID, TokenStream<StyleComponentValueRule>&); RefPtr<StyleValue> parse_css_value(PropertyID, TokenStream<StyleComponentValueRule>&);
static RefPtr<StyleValue> parse_css_value(ParsingContext const&, PropertyID, StyleComponentValueRule const&);
// FIXME: https://drafts.csswg.org/css-backgrounds-3/
static Optional<String> as_valid_background_repeat(String input) { return input; }
static Optional<String> as_valid_background_attachment(String input) { return input; }
static Optional<String> as_valid_background_position(String input) { return input; }
static Optional<String> as_valid_background_clip(String input) { return input; }
static Optional<String> as_valid_background_origin(String input) { return input; }
static Optional<String> as_valid_background_size(String input) { return input; }
static Optional<String> as_valid_border_style(String input) { return input; }
static Optional<String> as_valid_border_image_repeat(String input) { return input; }
private: private:
[[nodiscard]] NonnullRefPtrVector<StyleRule> consume_a_list_of_rules(bool top_level); [[nodiscard]] NonnullRefPtrVector<StyleRule> consume_a_list_of_rules(bool top_level);
@ -177,7 +168,12 @@ private:
static Optional<float> try_parse_float(StringView string); static Optional<float> try_parse_float(StringView string);
RefPtr<StyleValue> parse_single_css_value(PropertyID, StyleComponentValueRule const&); static RefPtr<StyleValue> parse_keyword_or_custom_value(ParsingContext const&, StyleComponentValueRule const&);
static RefPtr<StyleValue> parse_length_value(ParsingContext const&, StyleComponentValueRule const&);
static RefPtr<StyleValue> parse_numeric_value(ParsingContext const&, StyleComponentValueRule const&);
static RefPtr<StyleValue> parse_identifier_value(ParsingContext const&, StyleComponentValueRule const&);
static RefPtr<StyleValue> parse_color_value(ParsingContext const&, StyleComponentValueRule const&);
static RefPtr<StyleValue> parse_string_value(ParsingContext const&, StyleComponentValueRule const&);
ParsingContext m_context; ParsingContext m_context;

View file

@ -239,6 +239,11 @@ public:
bool is_numeric() const { return type() == Type::Numeric; } bool is_numeric() const { return type() == Type::Numeric; }
bool is_value_list() const { return type() == Type::ValueList; } bool is_value_list() const { return type() == Type::ValueList; }
bool is_builtin_or_dynamic() const
{
return is_inherit() || is_initial() || is_custom_property();
}
virtual String to_string() const = 0; virtual String to_string() const = 0;
virtual Length to_length() const { return Length::make_auto(); } virtual Length to_length() const { return Length::make_auto(); }
virtual Color to_color(const DOM::Document&) const { return {}; } virtual Color to_color(const DOM::Document&) const { return {}; }