diff --git a/Tests/LibWeb/Text/expected/css/CSSPropertyRule-invalid-rules.txt b/Tests/LibWeb/Text/expected/css/CSSPropertyRule-invalid-rules.txt
new file mode 100644
index 00000000000..cfb6f7b54fe
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/css/CSSPropertyRule-invalid-rules.txt
@@ -0,0 +1 @@
+Number of parsed css rules: 1 (expected: 1)
diff --git a/Tests/LibWeb/Text/expected/css/CSSPropertyRule-properties-readonly.txt b/Tests/LibWeb/Text/expected/css/CSSPropertyRule-properties-readonly.txt
new file mode 100644
index 00000000000..1957c8d6563
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/css/CSSPropertyRule-properties-readonly.txt
@@ -0,0 +1,6 @@
+@property rule syntax value: *
+@property rule syntax value: *
+@property rule inherits value: false
+@property rule inherits value: false
+@property rule initialValue value: blue
+@property rule initialValue value: blue
diff --git a/Tests/LibWeb/Text/input/css/CSSPropertyRule-invalid-rules.html b/Tests/LibWeb/Text/input/css/CSSPropertyRule-invalid-rules.html
new file mode 100644
index 00000000000..8f40aaa7668
--- /dev/null
+++ b/Tests/LibWeb/Text/input/css/CSSPropertyRule-invalid-rules.html
@@ -0,0 +1,44 @@
+
+
+
This text shouldn't be visible
+
+
diff --git a/Tests/LibWeb/Text/input/css/CSSPropertyRule-properties-readonly.html b/Tests/LibWeb/Text/input/css/CSSPropertyRule-properties-readonly.html
new file mode 100644
index 00000000000..7855480c8fc
--- /dev/null
+++ b/Tests/LibWeb/Text/input/css/CSSPropertyRule-properties-readonly.html
@@ -0,0 +1,26 @@
+
+
+This text shouldn't be visible
+
+
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
index eee1958cf92..7af3cfcc7b4 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
+++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
@@ -26,6 +26,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -1362,6 +1363,9 @@ JS::GCPtr Parser::convert_to_rule(Rule const& rule, Nested nested)
if (at_rule.name.equals_ignoring_ascii_case("supports"sv))
return convert_to_supports_rule(at_rule, nested);
+ if (at_rule.name.equals_ignoring_ascii_case("property"sv))
+ return convert_to_property_rule(at_rule);
+
// FIXME: More at rules!
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized CSS at-rule: @{}", at_rule.name);
return {};
@@ -1753,6 +1757,102 @@ JS::GCPtr Parser::convert_to_supports_rule(AtRule const& rule,
auto rule_list = CSSRuleList::create(m_context.realm(), child_rules);
return CSSSupportsRule::create(m_context.realm(), supports.release_nonnull(), rule_list);
}
+JS::GCPtr Parser::convert_to_property_rule(AtRule const& rule)
+{
+ // https://drafts.css-houdini.org/css-properties-values-api-1/#at-ruledef-property
+ // @property {
+ //
+ // }
+
+ if (rule.prelude.is_empty()) {
+ dbgln_if(CSS_PARSER_DEBUG, "Failed to parse @property rule: Empty prelude.");
+ return {};
+ }
+
+ auto prelude_stream = TokenStream { rule.prelude };
+ prelude_stream.discard_whitespace();
+ auto const& token = prelude_stream.consume_a_token();
+ if (!token.is_token()) {
+ dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @property has invalid prelude, prelude = {}; discarding.", rule.prelude);
+ return {};
+ }
+
+ auto name_token = token.token();
+ prelude_stream.discard_whitespace();
+
+ if (prelude_stream.has_next_token()) {
+ dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @property has invalid prelude, prelude = {}; discarding.", rule.prelude);
+ return {};
+ }
+
+ if (!name_token.is(Token::Type::Ident)) {
+ dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @property name is invalid: {}; discarding.", name_token.to_debug_string());
+ return {};
+ }
+
+ if (!is_a_custom_property_name_string(name_token.ident())) {
+ dbgln_if(CSS_PARSER_DEBUG, "CSSParser: @property name doesn't start with '--': {}; discarding.", name_token.ident());
+ return {};
+ }
+
+ auto const& name = name_token.ident();
+
+ Optional syntax_maybe;
+ Optional inherits_maybe;
+ Optional initial_value_maybe;
+
+ rule.for_each_as_declaration_list([&](auto& declaration) {
+ if (declaration.name.equals_ignoring_ascii_case("syntax"sv)) {
+ TokenStream token_stream { declaration.value };
+ token_stream.discard_whitespace();
+
+ auto const& syntax_token = token_stream.consume_a_token();
+ if (syntax_token.is(Token::Type::String)) {
+ token_stream.discard_whitespace();
+ if (token_stream.has_next_token()) {
+ dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in syntax");
+ } else {
+ syntax_maybe = syntax_token.token().string();
+ }
+ } else {
+ dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected value for @property \"syntax\": {}; discarding.", declaration.to_string());
+ }
+ return;
+ }
+ if (declaration.name.equals_ignoring_ascii_case("inherits"sv)) {
+ TokenStream token_stream { declaration.value };
+ token_stream.discard_whitespace();
+
+ auto const& inherits_token = token_stream.consume_a_token();
+ if (inherits_token.is_ident("true"sv) || inherits_token.is_ident("false"sv)) {
+ auto const& ident = inherits_token.token().ident();
+ token_stream.discard_whitespace();
+ if (token_stream.has_next_token()) {
+ dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Unexpected trailing tokens in inherits");
+ } else {
+ inherits_maybe = (ident == "true");
+ }
+ } else {
+ dbgln_if(CSS_PARSER_DEBUG, "CSSParser: Expected true/false for @property \"inherits\" value, got: {}; discarding.", inherits_token.to_debug_string());
+ }
+ return;
+ }
+ if (declaration.name.equals_ignoring_ascii_case("initial-value"sv)) {
+ // FIXME: Ensure that the initial value matches the syntax, and parse the correct CSSValue out
+ StringBuilder initial_value_sb;
+ for (auto const& component : declaration.value) {
+ initial_value_sb.append(component.to_string());
+ }
+ initial_value_maybe = MUST(initial_value_sb.to_string());
+ return;
+ }
+ });
+
+ if (syntax_maybe.has_value() && inherits_maybe.has_value()) {
+ return CSSPropertyRule::create(m_context.realm(), name, syntax_maybe.value(), inherits_maybe.value(), std::move(initial_value_maybe));
+ }
+ return {};
+}
Parser::PropertiesAndCustomProperties Parser::extract_properties(Vector const& rules_and_lists_of_declarations)
{
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h
index d5df355d5c4..32c248fb2fa 100644
--- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h
+++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h
@@ -187,6 +187,7 @@ private:
JS::GCPtr convert_to_media_rule(AtRule const&, Nested);
JS::GCPtr convert_to_namespace_rule(AtRule const&);
JS::GCPtr convert_to_supports_rule(AtRule const&, Nested);
+ JS::GCPtr convert_to_property_rule(AtRule const& rule);
PropertyOwningCSSStyleDeclaration* convert_to_style_declaration(Vector const&);
Optional convert_to_style_property(Declaration const&);