| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | /*
 | 
					
						
							|  |  |  |  |  * Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org> | 
					
						
							|  |  |  |  |  * Copyright (c) 2020-2021, the SerenityOS developers. | 
					
						
							| 
									
										
											  
											
												LibWeb/CSS: Merge style declaration subclasses into CSSStyleProperties
We previously had PropertyOwningCSSStyleDeclaration and
ResolvedCSSStyleDeclaration, representing the current style properties
and resolved style respectively. Both of these were the
CSSStyleDeclaration type in the CSSOM. (We also had
ElementInlineCSSStyleDeclaration but I removed that in a previous
commit.)
In the meantime, the spec has changed so that these should now be a new
CSSStyleProperties type in the CSSOM. Also, we need to subclass
CSSStyleDeclaration for things like CSSFontFaceRule's list of
descriptors, which means it wouldn't hold style properties.
So, this commit does the fairly messy work of combining these two types
into a new CSSStyleProperties class. A lot of what previously was done
as separate methods in the two classes, now follows the spec steps of
"if the readonly flag is set, do X" instead, which is hopefully easier
to follow too.
There is still some functionality in CSSStyleDeclaration that belongs in
CSSStyleProperties, but I'll do that next. To avoid a huge diff for
"CSSStyleDeclaration-all-supported-properties-and-default-values.txt"
both here and in the following commit, we don't apply the (currently
empty) CSSStyleProperties prototype yet.
											
										 
											2025-03-17 17:50:49 +00:00
										 |  |  |  |  * Copyright (c) 2021-2025, Sam Atkins <sam@ladybird.org> | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |  * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org> | 
					
						
							|  |  |  |  |  * Copyright (c) 2022, MacDue <macdue@dueutil.tech> | 
					
						
							|  |  |  |  |  * Copyright (c) 2024, Shannon Booth <shannon@serenityos.org> | 
					
						
							|  |  |  |  |  * Copyright (c) 2024, Tommy van der Vorst <tommy@pixelspark.nl> | 
					
						
							|  |  |  |  |  * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org> | 
					
						
							|  |  |  |  |  * Copyright (c) 2024, Glenn Skrzypczak <glenn.skrzypczak@gmail.com> | 
					
						
							|  |  |  |  |  * | 
					
						
							|  |  |  |  |  * SPDX-License-Identifier: BSD-2-Clause | 
					
						
							|  |  |  |  |  */ | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | #include <LibWeb/CSS/CSSFontFaceRule.h>
 | 
					
						
							|  |  |  |  | #include <LibWeb/CSS/CSSImportRule.h>
 | 
					
						
							|  |  |  |  | #include <LibWeb/CSS/CSSKeyframeRule.h>
 | 
					
						
							|  |  |  |  | #include <LibWeb/CSS/CSSKeyframesRule.h>
 | 
					
						
							|  |  |  |  | #include <LibWeb/CSS/CSSLayerBlockRule.h>
 | 
					
						
							|  |  |  |  | #include <LibWeb/CSS/CSSLayerStatementRule.h>
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:48:56 +01:00
										 |  |  |  | #include <LibWeb/CSS/CSSMarginRule.h>
 | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | #include <LibWeb/CSS/CSSMediaRule.h>
 | 
					
						
							|  |  |  |  | #include <LibWeb/CSS/CSSNamespaceRule.h>
 | 
					
						
							|  |  |  |  | #include <LibWeb/CSS/CSSNestedDeclarations.h>
 | 
					
						
							| 
									
										
										
										
											2025-05-13 12:17:41 +01:00
										 |  |  |  | #include <LibWeb/CSS/CSSPageRule.h>
 | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | #include <LibWeb/CSS/CSSPropertyRule.h>
 | 
					
						
							| 
									
										
											  
											
												LibWeb/CSS: Merge style declaration subclasses into CSSStyleProperties
We previously had PropertyOwningCSSStyleDeclaration and
ResolvedCSSStyleDeclaration, representing the current style properties
and resolved style respectively. Both of these were the
CSSStyleDeclaration type in the CSSOM. (We also had
ElementInlineCSSStyleDeclaration but I removed that in a previous
commit.)
In the meantime, the spec has changed so that these should now be a new
CSSStyleProperties type in the CSSOM. Also, we need to subclass
CSSStyleDeclaration for things like CSSFontFaceRule's list of
descriptors, which means it wouldn't hold style properties.
So, this commit does the fairly messy work of combining these two types
into a new CSSStyleProperties class. A lot of what previously was done
as separate methods in the two classes, now follows the spec steps of
"if the readonly flag is set, do X" instead, which is hopefully easier
to follow too.
There is still some functionality in CSSStyleDeclaration that belongs in
CSSStyleProperties, but I'll do that next. To avoid a huge diff for
"CSSStyleDeclaration-all-supported-properties-and-default-values.txt"
both here and in the following commit, we don't apply the (currently
empty) CSSStyleProperties prototype yet.
											
										 
											2025-03-17 17:50:49 +00:00
										 |  |  |  | #include <LibWeb/CSS/CSSStyleProperties.h>
 | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | #include <LibWeb/CSS/CSSStyleRule.h>
 | 
					
						
							|  |  |  |  | #include <LibWeb/CSS/CSSSupportsRule.h>
 | 
					
						
							| 
									
										
										
										
											2025-03-28 12:42:41 +00:00
										 |  |  |  | #include <LibWeb/CSS/FontFace.h>
 | 
					
						
							| 
									
										
										
										
											2025-07-23 10:27:53 +01:00
										 |  |  |  | #include <LibWeb/CSS/Parser/ErrorReporter.h>
 | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | #include <LibWeb/CSS/Parser/Parser.h>
 | 
					
						
							| 
									
										
										
										
											2025-07-23 12:20:01 +02:00
										 |  |  |  | #include <LibWeb/CSS/Parser/Syntax.h>
 | 
					
						
							|  |  |  |  | #include <LibWeb/CSS/Parser/SyntaxParsing.h>
 | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | #include <LibWeb/CSS/PropertyName.h>
 | 
					
						
							| 
									
										
										
										
											2025-03-24 17:20:08 +00:00
										 |  |  |  | #include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
 | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | #include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
 | 
					
						
							| 
									
										
										
										
											2025-08-08 10:28:41 +01:00
										 |  |  |  | #include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
 | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | #include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
 | 
					
						
							|  |  |  |  | #include <LibWeb/CSS/StyleValues/OpenTypeTaggedStyleValue.h>
 | 
					
						
							|  |  |  |  | #include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
 | 
					
						
							|  |  |  |  | #include <LibWeb/CSS/StyleValues/StringStyleValue.h>
 | 
					
						
							|  |  |  |  | #include <LibWeb/CSS/StyleValues/StyleValueList.h>
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | namespace Web::CSS::Parser { | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-13 13:54:43 +01:00
										 |  |  |  | // A helper that ensures only the last instance of each descriptor is included, while also handling shorthands.
 | 
					
						
							|  |  |  |  | class DescriptorList { | 
					
						
							|  |  |  |  | public: | 
					
						
							|  |  |  |  |     DescriptorList(AtRuleID at_rule) | 
					
						
							|  |  |  |  |         : m_at_rule(at_rule) | 
					
						
							|  |  |  |  |     { | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     void append(Descriptor&& descriptor) | 
					
						
							|  |  |  |  |     { | 
					
						
							|  |  |  |  |         if (is_shorthand(m_at_rule, descriptor.descriptor_id)) { | 
					
						
							|  |  |  |  |             for_each_expanded_longhand(m_at_rule, descriptor.descriptor_id, descriptor.value, [this](auto longhand_id, auto longhand_value) { | 
					
						
							|  |  |  |  |                 append_internal(Descriptor { longhand_id, longhand_value.release_nonnull() }); | 
					
						
							|  |  |  |  |             }); | 
					
						
							|  |  |  |  |             return; | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         append_internal(move(descriptor)); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     Vector<Descriptor> release_descriptors() | 
					
						
							|  |  |  |  |     { | 
					
						
							|  |  |  |  |         return move(m_descriptors); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | private: | 
					
						
							|  |  |  |  |     void append_internal(Descriptor&& descriptor) | 
					
						
							|  |  |  |  |     { | 
					
						
							|  |  |  |  |         if (m_seen_descriptor_ids.contains(descriptor.descriptor_id)) { | 
					
						
							|  |  |  |  |             m_descriptors.remove_first_matching([&descriptor](Descriptor const& existing) { | 
					
						
							|  |  |  |  |                 return existing.descriptor_id == descriptor.descriptor_id; | 
					
						
							|  |  |  |  |             }); | 
					
						
							|  |  |  |  |         } else { | 
					
						
							|  |  |  |  |             m_seen_descriptor_ids.set(descriptor.descriptor_id); | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  |         m_descriptors.append(move(descriptor)); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     AtRuleID m_at_rule; | 
					
						
							|  |  |  |  |     Vector<Descriptor> m_descriptors; | 
					
						
							|  |  |  |  |     HashTable<DescriptorID> m_seen_descriptor_ids; | 
					
						
							|  |  |  |  | }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-15 04:01:23 +13:00
										 |  |  |  | GC::Ptr<CSSRule> Parser::convert_to_rule(Rule const& rule, Nested nested) | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | { | 
					
						
							|  |  |  |  |     return rule.visit( | 
					
						
							| 
									
										
										
										
											2024-11-15 04:01:23 +13:00
										 |  |  |  |         [this, nested](AtRule const& at_rule) -> GC::Ptr<CSSRule> { | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |             if (has_ignored_vendor_prefix(at_rule.name)) | 
					
						
							|  |  |  |  |                 return {}; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if (at_rule.name.equals_ignoring_ascii_case("font-face"sv)) | 
					
						
							|  |  |  |  |                 return convert_to_font_face_rule(at_rule); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if (at_rule.name.equals_ignoring_ascii_case("import"sv)) | 
					
						
							|  |  |  |  |                 return convert_to_import_rule(at_rule); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if (at_rule.name.equals_ignoring_ascii_case("keyframes"sv)) | 
					
						
							|  |  |  |  |                 return convert_to_keyframes_rule(at_rule); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if (at_rule.name.equals_ignoring_ascii_case("layer"sv)) | 
					
						
							|  |  |  |  |                 return convert_to_layer_rule(at_rule, nested); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:48:56 +01:00
										 |  |  |  |             if (is_margin_rule_name(at_rule.name)) | 
					
						
							|  |  |  |  |                 return convert_to_margin_rule(at_rule); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |             if (at_rule.name.equals_ignoring_ascii_case("media"sv)) | 
					
						
							|  |  |  |  |                 return convert_to_media_rule(at_rule, nested); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if (at_rule.name.equals_ignoring_ascii_case("namespace"sv)) | 
					
						
							|  |  |  |  |                 return convert_to_namespace_rule(at_rule); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-13 12:17:41 +01:00
										 |  |  |  |             if (at_rule.name.equals_ignoring_ascii_case("page"sv)) | 
					
						
							|  |  |  |  |                 return convert_to_page_rule(at_rule); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |             if (at_rule.name.equals_ignoring_ascii_case("property"sv)) | 
					
						
							|  |  |  |  |                 return convert_to_property_rule(at_rule); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-13 12:17:41 +01:00
										 |  |  |  |             if (at_rule.name.equals_ignoring_ascii_case("supports"sv)) | 
					
						
							|  |  |  |  |                 return convert_to_supports_rule(at_rule, nested); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |             // FIXME: More at rules!
 | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |             ErrorReporter::the().report(UnknownRuleError { .rule_name = MUST(String::formatted("@{}", at_rule.name)) }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |             return {}; | 
					
						
							|  |  |  |  |         }, | 
					
						
							| 
									
										
										
										
											2024-11-15 04:01:23 +13:00
										 |  |  |  |         [this, nested](QualifiedRule const& qualified_rule) -> GC::Ptr<CSSRule> { | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |             return convert_to_style_rule(qualified_rule, nested); | 
					
						
							|  |  |  |  |         }); | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-15 04:01:23 +13:00
										 |  |  |  | GC::Ptr<CSSStyleRule> Parser::convert_to_style_rule(QualifiedRule const& qualified_rule, Nested nested) | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | { | 
					
						
							|  |  |  |  |     TokenStream prelude_stream { qualified_rule.prelude }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     auto maybe_selectors = parse_a_selector_list(prelude_stream, | 
					
						
							|  |  |  |  |         nested == Nested::Yes ? SelectorType::Relative : SelectorType::Standalone); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     if (maybe_selectors.is_error()) { | 
					
						
							|  |  |  |  |         if (maybe_selectors.error() == ParseError::SyntaxError) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |             ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |                 .rule_name = "style"_fly_string, | 
					
						
							|  |  |  |  |                 .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |                 .description = "Selectors invalid."_string, | 
					
						
							|  |  |  |  |             }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         } | 
					
						
							|  |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     if (maybe_selectors.value().is_empty()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "style"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Empty selector."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     SelectorList selectors = maybe_selectors.release_value(); | 
					
						
							| 
									
										
										
										
											2024-11-08 17:50:38 +00:00
										 |  |  |  |     if (nested == Nested::Yes) | 
					
						
							|  |  |  |  |         selectors = adapt_nested_relative_selector_list(selectors); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-18 13:41:46 +00:00
										 |  |  |  |     auto declaration = convert_to_style_declaration(qualified_rule.declarations); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-14 16:02:29 +01:00
										 |  |  |  |     GC::RootVector<GC::Ref<CSSRule>> child_rules { realm().heap() }; | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |     for (auto& child : qualified_rule.child_rules) { | 
					
						
							|  |  |  |  |         child.visit( | 
					
						
							|  |  |  |  |             [&](Rule const& rule) { | 
					
						
							|  |  |  |  |                 // "In addition to nested style rules, this specification allows nested group rules inside of style rules:
 | 
					
						
							|  |  |  |  |                 // any at-rule whose body contains style rules can be nested inside of a style rule as well."
 | 
					
						
							|  |  |  |  |                 // https://drafts.csswg.org/css-nesting-1/#nested-group-rules
 | 
					
						
							|  |  |  |  |                 if (auto converted_rule = convert_to_rule(rule, Nested::Yes)) { | 
					
						
							|  |  |  |  |                     if (is<CSSGroupingRule>(*converted_rule)) { | 
					
						
							| 
									
										
										
										
											2025-04-14 16:02:29 +01:00
										 |  |  |  |                         child_rules.append(*converted_rule); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |                     } else { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:27:53 +01:00
										 |  |  |  |                         ErrorReporter::the().report(InvalidRuleLocationError { | 
					
						
							|  |  |  |  |                             .outer_rule_name = "style"_fly_string, | 
					
						
							|  |  |  |  |                             .inner_rule_name = MUST(FlyString::from_utf8(converted_rule->class_name())), | 
					
						
							|  |  |  |  |                         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |                     } | 
					
						
							|  |  |  |  |                 } | 
					
						
							|  |  |  |  |             }, | 
					
						
							|  |  |  |  |             [&](Vector<Declaration> const& declarations) { | 
					
						
							| 
									
										
										
										
											2025-03-18 13:41:46 +00:00
										 |  |  |  |                 child_rules.append(CSSNestedDeclarations::create(realm(), *convert_to_style_declaration(declarations))); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |             }); | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-04-14 16:02:29 +01:00
										 |  |  |  |     auto nested_rules = CSSRuleList::create(realm(), child_rules); | 
					
						
							| 
									
										
										
										
											2025-02-05 12:08:27 +00:00
										 |  |  |  |     return CSSStyleRule::create(realm(), move(selectors), *declaration, *nested_rules); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-15 04:01:23 +13:00
										 |  |  |  | GC::Ptr<CSSImportRule> Parser::convert_to_import_rule(AtRule const& rule) | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | { | 
					
						
							|  |  |  |  |     // https://drafts.csswg.org/css-cascade-5/#at-import
 | 
					
						
							|  |  |  |  |     // @import [ <url> | <string> ]
 | 
					
						
							|  |  |  |  |     //         [ layer | layer(<layer-name>) ]?
 | 
					
						
							|  |  |  |  |     //         <import-conditions> ;
 | 
					
						
							|  |  |  |  |     //
 | 
					
						
							|  |  |  |  |     // <import-conditions> = [ supports( [ <supports-condition> | <declaration> ] ) ]?
 | 
					
						
							|  |  |  |  |     //                      <media-query-list>?
 | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |     TokenStream tokens { rule.prelude }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  |     if (rule.is_block_rule) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@import"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = tokens.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Must be a statement, not a block."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  |     if (rule.prelude.is_empty()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@import"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = tokens.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Empty prelude."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     tokens.discard_whitespace(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-08 14:33:01 +01:00
										 |  |  |  |     Optional<URL> url = parse_url_function(tokens); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |     if (!url.has_value() && tokens.next_token().is(Token::Type::String)) | 
					
						
							| 
									
										
										
										
											2025-04-08 14:33:01 +01:00
										 |  |  |  |         url = URL { tokens.consume_a_token().token().string().to_string() }; | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     if (!url.has_value()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@import"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = tokens.dump_string(), | 
					
						
							|  |  |  |  |             .description = MUST(String::formatted("Unable to parse `{}` as URL.", tokens.next_token().to_debug_string())), | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2025-03-19 11:23:33 +00:00
										 |  |  |  |     // FIXME: Implement layer support.
 | 
					
						
							|  |  |  |  |     RefPtr<Supports> supports {}; | 
					
						
							|  |  |  |  |     if (tokens.next_token().is_function("supports"sv)) { | 
					
						
							|  |  |  |  |         auto component_value = tokens.consume_a_token(); | 
					
						
							|  |  |  |  |         TokenStream supports_tokens { component_value.function().value }; | 
					
						
							|  |  |  |  |         if (supports_tokens.next_token().is_block()) { | 
					
						
							|  |  |  |  |             supports = parse_a_supports(supports_tokens); | 
					
						
							|  |  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2025-04-14 17:10:20 +01:00
										 |  |  |  |             m_rule_context.append(RuleContext::SupportsCondition); | 
					
						
							| 
									
										
										
										
											2025-03-19 11:23:33 +00:00
										 |  |  |  |             auto declaration = consume_a_declaration(supports_tokens); | 
					
						
							|  |  |  |  |             m_rule_context.take_last(); | 
					
						
							|  |  |  |  |             if (declaration.has_value()) { | 
					
						
							|  |  |  |  |                 auto supports_declaration = Supports::Declaration::create(declaration->to_string(), convert_to_style_property(*declaration).has_value()); | 
					
						
							|  |  |  |  |                 supports = Supports::create(supports_declaration.release_nonnull<BooleanExpression>()); | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-01 14:32:11 +01:00
										 |  |  |  |     auto media_query_list = parse_a_media_query_list(tokens); | 
					
						
							| 
									
										
										
										
											2025-03-19 11:23:33 +00:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |     if (tokens.has_next_token()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@import"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = tokens.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Trailing tokens in prelude."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-08 18:16:38 +01:00
										 |  |  |  |     return CSSImportRule::create(realm(), url.release_value(), const_cast<DOM::Document*>(m_document.ptr()), supports, move(media_query_list)); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | Optional<FlyString> Parser::parse_layer_name(TokenStream<ComponentValue>& tokens, AllowBlankLayerName allow_blank_layer_name) | 
					
						
							|  |  |  |  | { | 
					
						
							|  |  |  |  |     // https://drafts.csswg.org/css-cascade-5/#typedef-layer-name
 | 
					
						
							|  |  |  |  |     // <layer-name> = <ident> [ '.' <ident> ]*
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     // "The CSS-wide keywords are reserved for future use, and cause the rule to be invalid at parse time if used as an <ident> in the <layer-name>."
 | 
					
						
							|  |  |  |  |     auto is_valid_layer_name_part = [](auto& token) { | 
					
						
							|  |  |  |  |         return token.is(Token::Type::Ident) && !is_css_wide_keyword(token.token().ident()); | 
					
						
							|  |  |  |  |     }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     auto transaction = tokens.begin_transaction(); | 
					
						
							|  |  |  |  |     tokens.discard_whitespace(); | 
					
						
							|  |  |  |  |     if (!tokens.has_next_token() && allow_blank_layer_name == AllowBlankLayerName::Yes) { | 
					
						
							|  |  |  |  |         // No name present, just return a blank one
 | 
					
						
							|  |  |  |  |         return FlyString(); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     auto& first_name_token = tokens.consume_a_token(); | 
					
						
							|  |  |  |  |     if (!is_valid_layer_name_part(first_name_token)) | 
					
						
							|  |  |  |  |         return {}; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     StringBuilder builder; | 
					
						
							|  |  |  |  |     builder.append(first_name_token.token().ident()); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     while (tokens.has_next_token()) { | 
					
						
							|  |  |  |  |         // Repeatedly parse `'.' <ident>`
 | 
					
						
							|  |  |  |  |         if (!tokens.next_token().is_delim('.')) | 
					
						
							|  |  |  |  |             break; | 
					
						
							|  |  |  |  |         tokens.discard_a_token(); // '.'
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         auto& name_token = tokens.consume_a_token(); | 
					
						
							|  |  |  |  |         if (!is_valid_layer_name_part(name_token)) | 
					
						
							|  |  |  |  |             return {}; | 
					
						
							|  |  |  |  |         builder.appendff(".{}", name_token.token().ident()); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     transaction.commit(); | 
					
						
							|  |  |  |  |     return builder.to_fly_string_without_validation(); | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-15 04:01:23 +13:00
										 |  |  |  | GC::Ptr<CSSRule> Parser::convert_to_layer_rule(AtRule const& rule, Nested nested) | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | { | 
					
						
							|  |  |  |  |     // https://drafts.csswg.org/css-cascade-5/#at-layer
 | 
					
						
							| 
									
										
										
										
											2025-06-17 08:01:49 +01:00
										 |  |  |  |     if (rule.is_block_rule) { | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         // CSSLayerBlockRule
 | 
					
						
							|  |  |  |  |         // @layer <layer-name>? {
 | 
					
						
							|  |  |  |  |         //   <rule-list>
 | 
					
						
							|  |  |  |  |         // }
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         // First, the name
 | 
					
						
							|  |  |  |  |         FlyString layer_name = {}; | 
					
						
							|  |  |  |  |         auto prelude_tokens = TokenStream { rule.prelude }; | 
					
						
							|  |  |  |  |         if (auto maybe_name = parse_layer_name(prelude_tokens, AllowBlankLayerName::Yes); maybe_name.has_value()) { | 
					
						
							|  |  |  |  |             layer_name = maybe_name.release_value(); | 
					
						
							|  |  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |             ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |                 .rule_name = "@layer"_fly_string, | 
					
						
							|  |  |  |  |                 .prelude = prelude_tokens.dump_string(), | 
					
						
							|  |  |  |  |                 .description = "Not a valid layer name."_string, | 
					
						
							|  |  |  |  |             }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |             return {}; | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         prelude_tokens.discard_whitespace(); | 
					
						
							|  |  |  |  |         if (prelude_tokens.has_next_token()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |             ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |                 .rule_name = "@layer"_fly_string, | 
					
						
							|  |  |  |  |                 .prelude = prelude_tokens.dump_string(), | 
					
						
							|  |  |  |  |                 .description = "Trailing tokens after name in prelude."_string, | 
					
						
							|  |  |  |  |             }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |             return {}; | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         // Then the rules
 | 
					
						
							| 
									
										
										
										
											2025-04-14 16:02:29 +01:00
										 |  |  |  |         GC::RootVector<GC::Ref<CSSRule>> child_rules { realm().heap() }; | 
					
						
							| 
									
										
										
										
											2024-11-04 17:26:19 +00:00
										 |  |  |  |         for (auto const& child : rule.child_rules_and_lists_of_declarations) { | 
					
						
							|  |  |  |  |             child.visit( | 
					
						
							|  |  |  |  |                 [&](Rule const& rule) { | 
					
						
							|  |  |  |  |                     if (auto child_rule = convert_to_rule(rule, nested)) | 
					
						
							| 
									
										
										
										
											2025-04-14 16:02:29 +01:00
										 |  |  |  |                         child_rules.append(*child_rule); | 
					
						
							| 
									
										
										
										
											2024-11-04 17:26:19 +00:00
										 |  |  |  |                 }, | 
					
						
							|  |  |  |  |                 [&](Vector<Declaration> const& declarations) { | 
					
						
							| 
									
										
										
										
											2025-03-18 13:41:46 +00:00
										 |  |  |  |                     child_rules.append(CSSNestedDeclarations::create(realm(), *convert_to_style_declaration(declarations))); | 
					
						
							| 
									
										
										
										
											2024-11-04 17:26:19 +00:00
										 |  |  |  |                 }); | 
					
						
							|  |  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-02-05 12:08:27 +00:00
										 |  |  |  |         auto rule_list = CSSRuleList::create(realm(), child_rules); | 
					
						
							|  |  |  |  |         return CSSLayerBlockRule::create(realm(), layer_name, rule_list); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     // CSSLayerStatementRule
 | 
					
						
							|  |  |  |  |     // @layer <layer-name>#;
 | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |     auto prelude_tokens = TokenStream { rule.prelude }; | 
					
						
							|  |  |  |  |     prelude_tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |     Vector<FlyString> layer_names; | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |     while (prelude_tokens.has_next_token()) { | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         // Comma
 | 
					
						
							|  |  |  |  |         if (!layer_names.is_empty()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |             if (auto comma = prelude_tokens.consume_a_token(); !comma.is(Token::Type::Comma)) { | 
					
						
							|  |  |  |  |                 ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |                     .rule_name = "@layer"_fly_string, | 
					
						
							|  |  |  |  |                     .prelude = prelude_tokens.dump_string(), | 
					
						
							|  |  |  |  |                     .description = "Missing comma between layer names."_string, | 
					
						
							|  |  |  |  |                 }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |                 return {}; | 
					
						
							|  |  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |             prelude_tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         if (auto name = parse_layer_name(prelude_tokens, AllowBlankLayerName::No); name.has_value()) { | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |             layer_names.append(name.release_value()); | 
					
						
							|  |  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |             ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |                 .rule_name = "@layer"_fly_string, | 
					
						
							|  |  |  |  |                 .prelude = prelude_tokens.dump_string(), | 
					
						
							|  |  |  |  |                 .description = "Contains invalid layer name."_string, | 
					
						
							|  |  |  |  |             }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |             return {}; | 
					
						
							|  |  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         prelude_tokens.discard_whitespace(); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     if (layer_names.is_empty()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@layer"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = prelude_tokens.dump_string(), | 
					
						
							|  |  |  |  |             .description = "No layer names provided."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-05 12:08:27 +00:00
										 |  |  |  |     return CSSLayerStatementRule::create(realm(), move(layer_names)); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-15 04:01:23 +13:00
										 |  |  |  | GC::Ptr<CSSKeyframesRule> Parser::convert_to_keyframes_rule(AtRule const& rule) | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | { | 
					
						
							|  |  |  |  |     // https://drafts.csswg.org/css-animations/#keyframes
 | 
					
						
							|  |  |  |  |     // @keyframes = @keyframes <keyframes-name> { <qualified-rule-list> }
 | 
					
						
							|  |  |  |  |     // <keyframes-name> = <custom-ident> | <string>
 | 
					
						
							|  |  |  |  |     // <keyframe-block> = <keyframe-selector># { <declaration-list> }
 | 
					
						
							|  |  |  |  |     // <keyframe-selector> = from | to | <percentage [0,100]>
 | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |     auto prelude_stream = TokenStream { rule.prelude }; | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  |     if (!rule.is_block_rule) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@keyframes"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Must be a block, not a statement."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  |         return nullptr; | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     if (rule.prelude.is_empty()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@keyframes"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Empty prelude."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     prelude_stream.discard_whitespace(); | 
					
						
							|  |  |  |  |     auto& token = prelude_stream.consume_a_token(); | 
					
						
							|  |  |  |  |     if (!token.is_token()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@keyframes"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Name must be a <string> or <ident>."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     auto name_token = token.token(); | 
					
						
							|  |  |  |  |     prelude_stream.discard_whitespace(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     if (prelude_stream.has_next_token()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@keyframes"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Trailing tokens after name in prelude."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-14 08:17:15 +01:00
										 |  |  |  |     if (name_token.is(Token::Type::Ident) && (is_css_wide_keyword(name_token.ident()) || name_token.ident().is_one_of_ignoring_ascii_case("none"sv, "default"sv))) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@keyframes"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Invalid name."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     if (!name_token.is(Token::Type::String) && !name_token.is(Token::Type::Ident)) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@keyframes"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Name must be a <string> or <ident>."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     auto name = name_token.to_string(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-14 16:02:29 +01:00
										 |  |  |  |     GC::RootVector<GC::Ref<CSSRule>> keyframes(realm().heap()); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |     rule.for_each_as_qualified_rule_list([&](auto& qualified_rule) { | 
					
						
							|  |  |  |  |         if (!qualified_rule.child_rules.is_empty()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:27:53 +01:00
										 |  |  |  |             for (auto const& child_rule : qualified_rule.child_rules) { | 
					
						
							|  |  |  |  |                 ErrorReporter::the().report(InvalidRuleLocationError { | 
					
						
							|  |  |  |  |                     .outer_rule_name = "@keyframes"_fly_string, | 
					
						
							|  |  |  |  |                     .inner_rule_name = child_rule.visit( | 
					
						
							|  |  |  |  |                         [](Rule const& rule) { | 
					
						
							|  |  |  |  |                             return rule.visit( | 
					
						
							|  |  |  |  |                                 [](AtRule const& at_rule) { return MUST(String::formatted("@{}", at_rule.name)); }, | 
					
						
							|  |  |  |  |                                 [](QualifiedRule const&) { return "qualified-rule"_string; }); | 
					
						
							|  |  |  |  |                         }, | 
					
						
							|  |  |  |  |                         [](auto&) { | 
					
						
							|  |  |  |  |                             return "list-of-declarations"_string; | 
					
						
							|  |  |  |  |                         }), | 
					
						
							|  |  |  |  |                 }); | 
					
						
							|  |  |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         auto selectors = Vector<CSS::Percentage> {}; | 
					
						
							|  |  |  |  |         TokenStream child_tokens { qualified_rule.prelude }; | 
					
						
							|  |  |  |  |         while (child_tokens.has_next_token()) { | 
					
						
							|  |  |  |  |             child_tokens.discard_whitespace(); | 
					
						
							|  |  |  |  |             if (!child_tokens.has_next_token()) | 
					
						
							|  |  |  |  |                 break; | 
					
						
							|  |  |  |  |             auto tok = child_tokens.consume_a_token(); | 
					
						
							|  |  |  |  |             if (!tok.is_token()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |                 ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |                     .rule_name = "keyframe"_fly_string, | 
					
						
							|  |  |  |  |                     .prelude = child_tokens.dump_string(), | 
					
						
							|  |  |  |  |                     .description = "Invalid selector."_string, | 
					
						
							|  |  |  |  |                 }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |                 child_tokens.reconsume_current_input_token(); | 
					
						
							|  |  |  |  |                 break; | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  |             auto token = tok.token(); | 
					
						
							|  |  |  |  |             auto read_a_selector = false; | 
					
						
							|  |  |  |  |             if (token.is(Token::Type::Ident)) { | 
					
						
							|  |  |  |  |                 if (token.ident().equals_ignoring_ascii_case("from"sv)) { | 
					
						
							|  |  |  |  |                     selectors.append(CSS::Percentage(0)); | 
					
						
							|  |  |  |  |                     read_a_selector = true; | 
					
						
							|  |  |  |  |                 } | 
					
						
							|  |  |  |  |                 if (token.ident().equals_ignoring_ascii_case("to"sv)) { | 
					
						
							|  |  |  |  |                     selectors.append(CSS::Percentage(100)); | 
					
						
							|  |  |  |  |                     read_a_selector = true; | 
					
						
							|  |  |  |  |                 } | 
					
						
							|  |  |  |  |             } else if (token.is(Token::Type::Percentage)) { | 
					
						
							|  |  |  |  |                 selectors.append(CSS::Percentage(token.percentage())); | 
					
						
							|  |  |  |  |                 read_a_selector = true; | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if (read_a_selector) { | 
					
						
							|  |  |  |  |                 child_tokens.discard_whitespace(); | 
					
						
							|  |  |  |  |                 if (child_tokens.consume_a_token().is(Token::Type::Comma)) | 
					
						
							|  |  |  |  |                     continue; | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             child_tokens.reconsume_current_input_token(); | 
					
						
							|  |  |  |  |             break; | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         PropertiesAndCustomProperties properties; | 
					
						
							| 
									
										
										
										
											2025-07-23 10:27:53 +01:00
										 |  |  |  |         qualified_rule.for_each_as_declaration_list("keyframe"_fly_string, [&](auto const& declaration) { | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |             extract_property(declaration, properties); | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
											  
											
												LibWeb/CSS: Merge style declaration subclasses into CSSStyleProperties
We previously had PropertyOwningCSSStyleDeclaration and
ResolvedCSSStyleDeclaration, representing the current style properties
and resolved style respectively. Both of these were the
CSSStyleDeclaration type in the CSSOM. (We also had
ElementInlineCSSStyleDeclaration but I removed that in a previous
commit.)
In the meantime, the spec has changed so that these should now be a new
CSSStyleProperties type in the CSSOM. Also, we need to subclass
CSSStyleDeclaration for things like CSSFontFaceRule's list of
descriptors, which means it wouldn't hold style properties.
So, this commit does the fairly messy work of combining these two types
into a new CSSStyleProperties class. A lot of what previously was done
as separate methods in the two classes, now follows the spec steps of
"if the readonly flag is set, do X" instead, which is hopefully easier
to follow too.
There is still some functionality in CSSStyleDeclaration that belongs in
CSSStyleProperties, but I'll do that next. To avoid a huge diff for
"CSSStyleDeclaration-all-supported-properties-and-default-values.txt"
both here and in the following commit, we don't apply the (currently
empty) CSSStyleProperties prototype yet.
											
										 
											2025-03-17 17:50:49 +00:00
										 |  |  |  |         auto style = CSSStyleProperties::create(realm(), move(properties.properties), move(properties.custom_properties)); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         for (auto& selector : selectors) { | 
					
						
							| 
									
										
										
										
											2025-02-05 12:08:27 +00:00
										 |  |  |  |             auto keyframe_rule = CSSKeyframeRule::create(realm(), selector, *style); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |             keyframes.append(keyframe_rule); | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  |     }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-14 16:02:29 +01:00
										 |  |  |  |     return CSSKeyframesRule::create(realm(), name, CSSRuleList::create(realm(), keyframes)); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-15 04:01:23 +13:00
										 |  |  |  | GC::Ptr<CSSNamespaceRule> Parser::convert_to_namespace_rule(AtRule const& rule) | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | { | 
					
						
							|  |  |  |  |     // https://drafts.csswg.org/css-namespaces/#syntax
 | 
					
						
							|  |  |  |  |     // @namespace <namespace-prefix>? [ <string> | <url> ] ;
 | 
					
						
							|  |  |  |  |     // <namespace-prefix> = <ident>
 | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |     auto tokens = TokenStream { rule.prelude }; | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  |     if (rule.is_block_rule) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@namespace"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = tokens.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Must be a statement, not a block."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  |     if (rule.prelude.is_empty()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@namespace"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = tokens.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Empty prelude."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     tokens.discard_whitespace(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     Optional<FlyString> prefix = {}; | 
					
						
							|  |  |  |  |     if (tokens.next_token().is(Token::Type::Ident)) { | 
					
						
							|  |  |  |  |         prefix = tokens.consume_a_token().token().ident(); | 
					
						
							|  |  |  |  |         tokens.discard_whitespace(); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     FlyString namespace_uri; | 
					
						
							|  |  |  |  |     if (auto url = parse_url_function(tokens); url.has_value()) { | 
					
						
							| 
									
										
										
										
											2025-04-08 14:33:01 +01:00
										 |  |  |  |         // "A URI string parsed from the URI syntax must be treated as a literal string: as with the STRING syntax, no
 | 
					
						
							|  |  |  |  |         // URI-specific normalization is applied."
 | 
					
						
							|  |  |  |  |         // https://drafts.csswg.org/css-namespaces/#syntax
 | 
					
						
							|  |  |  |  |         namespace_uri = url->url(); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |     } else if (auto& url_token = tokens.consume_a_token(); url_token.is(Token::Type::String)) { | 
					
						
							|  |  |  |  |         namespace_uri = url_token.token().string(); | 
					
						
							|  |  |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@namespace"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = tokens.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Unable to parse <url>."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     tokens.discard_whitespace(); | 
					
						
							|  |  |  |  |     if (tokens.has_next_token()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@namespace"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = tokens.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Trailing tokens after <url> in prelude."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-05 12:08:27 +00:00
										 |  |  |  |     return CSSNamespaceRule::create(realm(), prefix, namespace_uri); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-15 04:01:23 +13:00
										 |  |  |  | GC::Ptr<CSSSupportsRule> Parser::convert_to_supports_rule(AtRule const& rule, Nested nested) | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | { | 
					
						
							|  |  |  |  |     // https://drafts.csswg.org/css-conditional-3/#at-supports
 | 
					
						
							|  |  |  |  |     // @supports <supports-condition> {
 | 
					
						
							|  |  |  |  |     //   <rule-list>
 | 
					
						
							|  |  |  |  |     // }
 | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |     auto supports_tokens = TokenStream { rule.prelude }; | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  |     if (!rule.is_block_rule) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@supports"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = supports_tokens.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Must be a block, not a statement."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     if (rule.prelude.is_empty()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@supports"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = supports_tokens.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Empty prelude."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     auto supports = parse_a_supports(supports_tokens); | 
					
						
							|  |  |  |  |     if (!supports) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@supports"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = supports_tokens.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Supports clause invalid."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-14 16:02:29 +01:00
										 |  |  |  |     GC::RootVector<GC::Ref<CSSRule>> child_rules { realm().heap() }; | 
					
						
							| 
									
										
										
										
											2024-11-04 17:26:19 +00:00
										 |  |  |  |     for (auto const& child : rule.child_rules_and_lists_of_declarations) { | 
					
						
							|  |  |  |  |         child.visit( | 
					
						
							|  |  |  |  |             [&](Rule const& rule) { | 
					
						
							|  |  |  |  |                 if (auto child_rule = convert_to_rule(rule, nested)) | 
					
						
							| 
									
										
										
										
											2025-04-14 16:02:29 +01:00
										 |  |  |  |                     child_rules.append(*child_rule); | 
					
						
							| 
									
										
										
										
											2024-11-04 17:26:19 +00:00
										 |  |  |  |             }, | 
					
						
							|  |  |  |  |             [&](Vector<Declaration> const& declarations) { | 
					
						
							| 
									
										
										
										
											2025-03-18 13:41:46 +00:00
										 |  |  |  |                 child_rules.append(CSSNestedDeclarations::create(realm(), *convert_to_style_declaration(declarations))); | 
					
						
							| 
									
										
										
										
											2024-11-04 17:26:19 +00:00
										 |  |  |  |             }); | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-05 12:08:27 +00:00
										 |  |  |  |     auto rule_list = CSSRuleList::create(realm(), child_rules); | 
					
						
							|  |  |  |  |     return CSSSupportsRule::create(realm(), supports.release_nonnull(), rule_list); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-15 04:01:23 +13:00
										 |  |  |  | GC::Ptr<CSSPropertyRule> Parser::convert_to_property_rule(AtRule const& rule) | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | { | 
					
						
							|  |  |  |  |     // https://drafts.css-houdini.org/css-properties-values-api-1/#at-ruledef-property
 | 
					
						
							|  |  |  |  |     // @property <custom-property-name> {
 | 
					
						
							|  |  |  |  |     // <declaration-list>
 | 
					
						
							|  |  |  |  |     // }
 | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |     auto prelude_stream = TokenStream { rule.prelude }; | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  |     if (!rule.is_block_rule) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@property"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Must be a block, not a statement."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     if (rule.prelude.is_empty()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@property"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Empty prelude."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     prelude_stream.discard_whitespace(); | 
					
						
							|  |  |  |  |     auto const& token = prelude_stream.consume_a_token(); | 
					
						
							|  |  |  |  |     if (!token.is_token()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@property"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Name must be an ident."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     auto name_token = token.token(); | 
					
						
							|  |  |  |  |     prelude_stream.discard_whitespace(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     if (prelude_stream.has_next_token()) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@property"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Trailing tokens after name in prelude."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |     if (!name_token.is(Token::Type::Ident) || !is_a_custom_property_name_string(name_token.ident())) { | 
					
						
							|  |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@property"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Name must be an ident starting with '--'."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     auto const& name = name_token.ident(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     Optional<FlyString> syntax_maybe; | 
					
						
							|  |  |  |  |     Optional<bool> inherits_maybe; | 
					
						
							| 
									
										
										
										
											2025-08-08 10:11:51 +01:00
										 |  |  |  |     RefPtr<StyleValue const> initial_value_maybe; | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     rule.for_each_as_declaration_list([&](auto& declaration) { | 
					
						
							| 
									
										
										
										
											2025-04-07 11:34:53 +01:00
										 |  |  |  |         if (auto descriptor = convert_to_descriptor(AtRuleID::Property, declaration); descriptor.has_value()) { | 
					
						
							|  |  |  |  |             if (descriptor->descriptor_id == DescriptorID::Syntax) { | 
					
						
							|  |  |  |  |                 if (descriptor->value->is_string()) | 
					
						
							|  |  |  |  |                     syntax_maybe = descriptor->value->as_string().string_value(); | 
					
						
							|  |  |  |  |                 return; | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-04-07 11:34:53 +01:00
										 |  |  |  |             if (descriptor->descriptor_id == DescriptorID::Inherits) { | 
					
						
							|  |  |  |  |                 switch (descriptor->value->to_keyword()) { | 
					
						
							|  |  |  |  |                 case Keyword::True: | 
					
						
							|  |  |  |  |                     inherits_maybe = true; | 
					
						
							|  |  |  |  |                     break; | 
					
						
							|  |  |  |  |                 case Keyword::False: | 
					
						
							|  |  |  |  |                     inherits_maybe = false; | 
					
						
							|  |  |  |  |                     break; | 
					
						
							|  |  |  |  |                 default: | 
					
						
							|  |  |  |  |                     break; | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2025-04-07 11:34:53 +01:00
										 |  |  |  |                 return; | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-04-07 11:34:53 +01:00
										 |  |  |  |             if (descriptor->descriptor_id == DescriptorID::InitialValue) { | 
					
						
							| 
									
										
										
										
											2025-04-07 11:54:51 +01:00
										 |  |  |  |                 initial_value_maybe = *descriptor->value; | 
					
						
							| 
									
										
										
										
											2025-04-07 11:34:53 +01:00
										 |  |  |  |                 return; | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |             } | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  |     }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-23 12:20:01 +02:00
										 |  |  |  |     // @property rules require a syntax and inherits descriptor; if either are missing, the entire rule is invalid and must be ignored.
 | 
					
						
							|  |  |  |  |     if (!syntax_maybe.has_value() || syntax_maybe->is_empty() || !inherits_maybe.has_value()) { | 
					
						
							|  |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-05 10:22:47 +05:30
										 |  |  |  |     CSS::Parser::ParsingParams parsing_params; | 
					
						
							|  |  |  |  |     if (document()) | 
					
						
							|  |  |  |  |         parsing_params = CSS::Parser::ParsingParams { *document() }; | 
					
						
							|  |  |  |  |     else | 
					
						
							|  |  |  |  |         parsing_params = CSS::Parser::ParsingParams { realm() }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-23 12:20:01 +02:00
										 |  |  |  |     auto syntax_component_values = parse_component_values_list(parsing_params, syntax_maybe.value()); | 
					
						
							|  |  |  |  |     auto maybe_syntax = parse_as_syntax(syntax_component_values); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     // If the provided string is not a valid syntax string (if it returns failure when consume
 | 
					
						
							|  |  |  |  |     // a syntax definition is called on it), the descriptor is invalid and must be ignored.
 | 
					
						
							|  |  |  |  |     if (!maybe_syntax) { | 
					
						
							|  |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |     // The initial-value descriptor is optional only if the syntax is the universal syntax definition,
 | 
					
						
							|  |  |  |  |     // otherwise the descriptor is required; if it’s missing, the entire rule is invalid and must be ignored.
 | 
					
						
							|  |  |  |  |     if (!initial_value_maybe && maybe_syntax->type() != CSS::Parser::SyntaxNode::NodeType::Universal) { | 
					
						
							|  |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     if (initial_value_maybe) { | 
					
						
							|  |  |  |  |         initial_value_maybe = Web::CSS::Parser::parse_with_a_syntax(parsing_params, initial_value_maybe->tokenize(), *maybe_syntax); | 
					
						
							|  |  |  |  |         // Otherwise, if the value of the syntax descriptor is not the universal syntax definition,
 | 
					
						
							|  |  |  |  |         // the following conditions must be met for the @property rule to be valid:
 | 
					
						
							|  |  |  |  |         //  - The initial-value descriptor must be present.
 | 
					
						
							|  |  |  |  |         //  - The initial-value descriptor’s value must parse successfully according to the grammar specified by the syntax definition.
 | 
					
						
							|  |  |  |  |         //  - FIXME: The initial-value must be computationally independent.
 | 
					
						
							| 
									
										
										
										
											2025-04-07 11:54:51 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-23 12:20:01 +02:00
										 |  |  |  |         if (!initial_value_maybe || initial_value_maybe->is_guaranteed_invalid()) { | 
					
						
							|  |  |  |  |             return {}; | 
					
						
							|  |  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-23 12:20:01 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     return CSSPropertyRule::create(realm(), name, syntax_maybe.value(), inherits_maybe.value(), move(initial_value_maybe)); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-15 04:01:23 +13:00
										 |  |  |  | GC::Ptr<CSSFontFaceRule> Parser::convert_to_font_face_rule(AtRule const& rule) | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | { | 
					
						
							|  |  |  |  |     // https://drafts.csswg.org/css-fonts/#font-face-rule
 | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |     TokenStream prelude_stream { rule.prelude }; | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  |     if (!rule.is_block_rule) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@font-face"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Must be a block, not a statement."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  |         return nullptr; | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-23 10:51:23 +01:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     prelude_stream.discard_whitespace(); | 
					
						
							|  |  |  |  |     if (prelude_stream.has_next_token()) { | 
					
						
							|  |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@font-face"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Prelude is not allowed."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							|  |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-13 13:54:43 +01:00
										 |  |  |  |     DescriptorList descriptors { AtRuleID::FontFace }; | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |     rule.for_each_as_declaration_list([&](auto& declaration) { | 
					
						
							| 
									
										
										
										
											2025-04-03 12:05:49 +01:00
										 |  |  |  |         if (auto descriptor = convert_to_descriptor(AtRuleID::FontFace, declaration); descriptor.has_value()) { | 
					
						
							|  |  |  |  |             descriptors.append(descriptor.release_value()); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  |         } | 
					
						
							|  |  |  |  |     }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-13 13:54:43 +01:00
										 |  |  |  |     return CSSFontFaceRule::create(realm(), CSSFontFaceDescriptors::create(realm(), descriptors.release_descriptors())); | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:48:56 +01:00
										 |  |  |  | GC::Ptr<CSSPageRule> Parser::convert_to_page_rule(AtRule const& page_rule) | 
					
						
							| 
									
										
										
										
											2025-05-13 13:05:08 +01:00
										 |  |  |  | { | 
					
						
							|  |  |  |  |     // https://drafts.csswg.org/css-page-3/#syntax-page-selector
 | 
					
						
							|  |  |  |  |     // @page = @page <page-selector-list>? { <declaration-rule-list> }
 | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |     TokenStream tokens { page_rule.prelude }; | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  |     if (!page_rule.is_block_rule) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = "@page"_fly_string, | 
					
						
							|  |  |  |  |             .prelude = tokens.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Must be a block, not a statement."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  |         return nullptr; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-16 11:37:53 +01:00
										 |  |  |  |     auto page_selectors = parse_a_page_selector_list(tokens); | 
					
						
							|  |  |  |  |     if (page_selectors.is_error()) | 
					
						
							| 
									
										
										
										
											2025-05-13 13:05:08 +01:00
										 |  |  |  |         return nullptr; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-13 12:17:41 +01:00
										 |  |  |  |     GC::RootVector<GC::Ref<CSSRule>> child_rules { realm().heap() }; | 
					
						
							| 
									
										
										
										
											2025-05-13 13:54:43 +01:00
										 |  |  |  |     DescriptorList descriptors { AtRuleID::Page }; | 
					
						
							| 
									
										
										
										
											2025-05-15 11:48:56 +01:00
										 |  |  |  |     page_rule.for_each_as_declaration_rule_list( | 
					
						
							| 
									
										
										
										
											2025-05-13 12:17:41 +01:00
										 |  |  |  |         [&](auto& at_rule) { | 
					
						
							| 
									
										
										
										
											2025-05-15 11:48:56 +01:00
										 |  |  |  |             if (auto converted_rule = convert_to_rule(at_rule, Nested::No)) { | 
					
						
							|  |  |  |  |                 if (is<CSSMarginRule>(*converted_rule)) { | 
					
						
							|  |  |  |  |                     child_rules.append(*converted_rule); | 
					
						
							|  |  |  |  |                 } else { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:27:53 +01:00
										 |  |  |  |                     ErrorReporter::the().report(InvalidRuleLocationError { | 
					
						
							|  |  |  |  |                         .outer_rule_name = "@page"_fly_string, | 
					
						
							|  |  |  |  |                         .inner_rule_name = MUST(FlyString::from_utf8(converted_rule->class_name())), | 
					
						
							|  |  |  |  |                     }); | 
					
						
							| 
									
										
										
										
											2025-05-15 11:48:56 +01:00
										 |  |  |  |                 } | 
					
						
							|  |  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-05-13 12:17:41 +01:00
										 |  |  |  |         }, | 
					
						
							|  |  |  |  |         [&](auto& declaration) { | 
					
						
							|  |  |  |  |             if (auto descriptor = convert_to_descriptor(AtRuleID::Page, declaration); descriptor.has_value()) { | 
					
						
							|  |  |  |  |                 descriptors.append(descriptor.release_value()); | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  |         }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     auto rule_list = CSSRuleList::create(realm(), child_rules); | 
					
						
							| 
									
										
										
										
											2025-05-13 13:54:43 +01:00
										 |  |  |  |     return CSSPageRule::create(realm(), page_selectors.release_value(), CSSPageDescriptors::create(realm(), descriptors.release_descriptors()), rule_list); | 
					
						
							| 
									
										
										
										
											2025-05-13 12:17:41 +01:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:48:56 +01:00
										 |  |  |  | GC::Ptr<CSSMarginRule> Parser::convert_to_margin_rule(AtRule const& rule) | 
					
						
							|  |  |  |  | { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |     TokenStream prelude_stream { rule.prelude }; | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  |     if (!rule.is_block_rule) { | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = MUST(String::formatted("@{}", rule.name)), | 
					
						
							|  |  |  |  |             .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Must be a block, not a statement."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2025-06-24 19:47:49 +01:00
										 |  |  |  |         return nullptr; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-23 10:51:23 +01:00
										 |  |  |  |     prelude_stream.discard_whitespace(); | 
					
						
							|  |  |  |  |     if (prelude_stream.has_next_token()) { | 
					
						
							|  |  |  |  |         ErrorReporter::the().report(CSS::Parser::InvalidRuleError { | 
					
						
							|  |  |  |  |             .rule_name = MUST(String::formatted("@{}", rule.name)), | 
					
						
							|  |  |  |  |             .prelude = prelude_stream.dump_string(), | 
					
						
							|  |  |  |  |             .description = "Prelude is not allowed."_string, | 
					
						
							|  |  |  |  |         }); | 
					
						
							|  |  |  |  |         return {}; | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-23 10:34:28 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 11:48:56 +01:00
										 |  |  |  |     // https://drafts.csswg.org/css-page-3/#syntax-page-selector
 | 
					
						
							|  |  |  |  |     // There are lots of these, but they're all in the format:
 | 
					
						
							|  |  |  |  |     // @foo = @foo { <declaration-list> };
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     // FIXME: The declaration list should be a CSSMarginDescriptors, but that has no spec definition:
 | 
					
						
							|  |  |  |  |     //        https://github.com/w3c/csswg-drafts/issues/10106
 | 
					
						
							|  |  |  |  |     //        So, we just parse a CSSStyleProperties instead for now.
 | 
					
						
							|  |  |  |  |     PropertiesAndCustomProperties properties; | 
					
						
							|  |  |  |  |     rule.for_each_as_declaration_list([&](auto const& declaration) { | 
					
						
							|  |  |  |  |         extract_property(declaration, properties); | 
					
						
							|  |  |  |  |     }); | 
					
						
							|  |  |  |  |     auto style = CSSStyleProperties::create(realm(), move(properties.properties), move(properties.custom_properties)); | 
					
						
							|  |  |  |  |     return CSSMarginRule::create(realm(), rule.name, style); | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-31 16:03:06 +00:00
										 |  |  |  | } |