LibWeb/CSS: Add basic implementation of CSSMarginRule

This is a bit under-specced, specifically there's no definition of
CSSMarginDescriptors so I've gone with CSSStyleProperties for now. Gets
us 17 WPT subtests.
This commit is contained in:
Sam Atkins 2025-05-15 11:48:56 +01:00
commit 870f24f181
Notes: github-actions[bot] 2025-05-16 10:02:36 +00:00
21 changed files with 233 additions and 32 deletions

View file

@ -14,6 +14,7 @@
#include <AK/Debug.h>
#include <LibURL/Parser.h>
#include <LibWeb/CSS/CSSMarginRule.h>
#include <LibWeb/CSS/CSSStyleDeclaration.h>
#include <LibWeb/CSS/CSSStyleProperties.h>
#include <LibWeb/CSS/CSSStyleSheet.h>
@ -1438,7 +1439,8 @@ bool Parser::is_valid_in_the_current_context(Declaration const&) const
case RuleContext::AtFontFace:
case RuleContext::AtPage:
case RuleContext::AtProperty:
// @font-face, @page, and @property have descriptor declarations
case RuleContext::Margin:
// These have descriptor declarations
return true;
case RuleContext::AtKeyframes:
@ -1455,9 +1457,9 @@ bool Parser::is_valid_in_the_current_context(Declaration const&) const
bool Parser::is_valid_in_the_current_context(AtRule const& at_rule) const
{
// All at-rules can appear at the top level
// All at-rules can appear at the top level, except margin rules
if (m_rule_context.is_empty())
return true;
return !is_margin_rule_name(at_rule.name);
// Only grouping rules can be nested within style rules
if (m_rule_context.contains_slow(RuleContext::Style))
@ -1482,13 +1484,16 @@ bool Parser::is_valid_in_the_current_context(AtRule const& at_rule) const
// @supports cannot check for at-rules
return false;
case RuleContext::AtPage:
// @page rules can contain margin rules
return is_margin_rule_name(at_rule.name);
case RuleContext::AtFontFace:
case RuleContext::AtKeyframes:
case RuleContext::Keyframe:
case RuleContext::AtPage:
case RuleContext::AtProperty:
case RuleContext::Margin:
// These can't contain any at-rules
// FIXME: Eventually @page can contain margin-box at-rules: https://drafts.csswg.org/css-page-3/#margin-at-rules
return false;
}
@ -1530,6 +1535,7 @@ bool Parser::is_valid_in_the_current_context(QualifiedRule const&) const
case RuleContext::AtPage:
case RuleContext::AtProperty:
case RuleContext::Keyframe:
case RuleContext::Margin:
// These can't contain qualified rules
return false;
}

View file

@ -243,6 +243,7 @@ private:
GC::Ptr<CSSKeyframesRule> convert_to_keyframes_rule(AtRule const&);
GC::Ptr<CSSImportRule> convert_to_import_rule(AtRule const&);
GC::Ptr<CSSRule> convert_to_layer_rule(AtRule const&, Nested);
GC::Ptr<CSSMarginRule> convert_to_margin_rule(AtRule const&);
GC::Ptr<CSSMediaRule> convert_to_media_rule(AtRule const&, Nested);
GC::Ptr<CSSNamespaceRule> convert_to_namespace_rule(AtRule const&);
GC::Ptr<CSSPageRule> convert_to_page_rule(AtRule const& rule);

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/CSS/CSSMarginRule.h>
#include <LibWeb/CSS/Parser/RuleContext.h>
namespace Web::CSS::Parser {
@ -25,6 +26,8 @@ RuleContext rule_context_type_for_rule(CSSRule::Type rule_type)
return RuleContext::AtSupports;
case CSSRule::Type::LayerBlock:
return RuleContext::AtLayer;
case CSSRule::Type::Margin:
return RuleContext::Margin;
case CSSRule::Type::NestedDeclarations:
return RuleContext::Style;
case CSSRule::Type::Page:
@ -56,6 +59,8 @@ RuleContext rule_context_type_for_at_rule(FlyString const& name)
return RuleContext::AtProperty;
if (name.equals_ignoring_ascii_case("page"sv))
return RuleContext::AtPage;
if (is_margin_rule_name(name))
return RuleContext::Margin;
return RuleContext::Unknown;
}

View file

@ -23,6 +23,7 @@ enum class RuleContext : u8 {
AtLayer,
AtProperty,
AtPage,
Margin,
};
RuleContext rule_context_type_for_rule(CSSRule::Type);
RuleContext rule_context_type_for_at_rule(FlyString const&);

View file

@ -18,6 +18,7 @@
#include <LibWeb/CSS/CSSKeyframesRule.h>
#include <LibWeb/CSS/CSSLayerBlockRule.h>
#include <LibWeb/CSS/CSSLayerStatementRule.h>
#include <LibWeb/CSS/CSSMarginRule.h>
#include <LibWeb/CSS/CSSMediaRule.h>
#include <LibWeb/CSS/CSSNamespaceRule.h>
#include <LibWeb/CSS/CSSNestedDeclarations.h>
@ -102,6 +103,9 @@ GC::Ptr<CSSRule> Parser::convert_to_rule(Rule const& rule, Nested nested)
if (at_rule.name.equals_ignoring_ascii_case("layer"sv))
return convert_to_layer_rule(at_rule, nested);
if (is_margin_rule_name(at_rule.name))
return convert_to_margin_rule(at_rule);
if (at_rule.name.equals_ignoring_ascii_case("media"sv))
return convert_to_media_rule(at_rule, nested);
@ -697,20 +701,25 @@ static Optional<PageSelectorList> parse_page_selector_list(Vector<ComponentValue
return selector_list;
}
GC::Ptr<CSSPageRule> Parser::convert_to_page_rule(AtRule const& rule)
GC::Ptr<CSSPageRule> Parser::convert_to_page_rule(AtRule const& page_rule)
{
// https://drafts.csswg.org/css-page-3/#syntax-page-selector
// @page = @page <page-selector-list>? { <declaration-rule-list> }
auto page_selectors = parse_page_selector_list(rule.prelude);
auto page_selectors = parse_page_selector_list(page_rule.prelude);
if (!page_selectors.has_value())
return nullptr;
GC::RootVector<GC::Ref<CSSRule>> child_rules { realm().heap() };
DescriptorList descriptors { AtRuleID::Page };
rule.for_each_as_declaration_rule_list(
page_rule.for_each_as_declaration_rule_list(
[&](auto& at_rule) {
// FIXME: Parse margin rules here.
(void)at_rule;
if (auto converted_rule = convert_to_rule(at_rule, Nested::No)) {
if (is<CSSMarginRule>(*converted_rule)) {
child_rules.append(*converted_rule);
} else {
dbgln_if(CSS_PARSER_DEBUG, "CSSParser: nested {} is not allowed inside @page rule; discarding.", converted_rule->class_name());
}
}
},
[&](auto& declaration) {
if (auto descriptor = convert_to_descriptor(AtRuleID::Page, declaration); descriptor.has_value()) {
@ -722,4 +731,21 @@ GC::Ptr<CSSPageRule> Parser::convert_to_page_rule(AtRule const& rule)
return CSSPageRule::create(realm(), page_selectors.release_value(), CSSPageDescriptors::create(realm(), descriptors.release_descriptors()), rule_list);
}
GC::Ptr<CSSMarginRule> Parser::convert_to_margin_rule(AtRule const& rule)
{
// 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);
}
}