LibWeb: Add basic implementation of @page

This doesn't support selectors, and the only descriptors for now are for
margins.
This commit is contained in:
Sam Atkins 2025-05-13 12:17:41 +01:00
commit aaf07ae30d
Notes: github-actions[bot] 2025-05-15 08:54:53 +00:00
27 changed files with 471 additions and 20 deletions

View file

@ -1375,6 +1375,8 @@ Vector<Descriptor> Parser::parse_as_descriptor_declaration_block(AtRuleID at_rul
switch (at_rule_id) {
case AtRuleID::FontFace:
return RuleContext::AtFontFace;
case AtRuleID::Page:
return RuleContext::AtPage;
case AtRuleID::Property:
return RuleContext::AtProperty;
}
@ -1434,8 +1436,9 @@ bool Parser::is_valid_in_the_current_context(Declaration const&) const
return m_rule_context.contains_slow(RuleContext::Style);
case RuleContext::AtFontFace:
case RuleContext::AtPage:
case RuleContext::AtProperty:
// @font-face and @property have descriptor declarations
// @font-face, @page, and @property have descriptor declarations
return true;
case RuleContext::AtKeyframes:
@ -1482,8 +1485,10 @@ bool Parser::is_valid_in_the_current_context(AtRule const& at_rule) const
case RuleContext::AtFontFace:
case RuleContext::AtKeyframes:
case RuleContext::Keyframe:
case RuleContext::AtPage:
case RuleContext::AtProperty:
// 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;
}
@ -1522,6 +1527,7 @@ bool Parser::is_valid_in_the_current_context(QualifiedRule const&) const
return false;
case RuleContext::AtFontFace:
case RuleContext::AtPage:
case RuleContext::AtProperty:
case RuleContext::Keyframe:
// These can't contain qualified rules

View file

@ -245,8 +245,9 @@ private:
GC::Ptr<CSSRule> convert_to_layer_rule(AtRule const&, Nested);
GC::Ptr<CSSMediaRule> convert_to_media_rule(AtRule const&, Nested);
GC::Ptr<CSSNamespaceRule> convert_to_namespace_rule(AtRule const&);
GC::Ptr<CSSSupportsRule> convert_to_supports_rule(AtRule const&, Nested);
GC::Ptr<CSSPageRule> convert_to_page_rule(AtRule const& rule);
GC::Ptr<CSSPropertyRule> convert_to_property_rule(AtRule const& rule);
GC::Ptr<CSSSupportsRule> convert_to_supports_rule(AtRule const&, Nested);
GC::Ref<CSSStyleProperties> convert_to_style_declaration(Vector<Declaration> const&);
Optional<StyleProperty> convert_to_style_property(Declaration const&);

View file

@ -27,6 +27,8 @@ RuleContext rule_context_type_for_rule(CSSRule::Type rule_type)
return RuleContext::AtLayer;
case CSSRule::Type::NestedDeclarations:
return RuleContext::Style;
case CSSRule::Type::Page:
return RuleContext::AtPage;
case CSSRule::Type::Property:
return RuleContext::AtProperty;
// Other types shouldn't be trying to create a context.
@ -52,6 +54,8 @@ RuleContext rule_context_type_for_at_rule(FlyString const& name)
return RuleContext::AtLayer;
if (name == "property")
return RuleContext::AtProperty;
if (name == "page")
return RuleContext::AtPage;
return RuleContext::Unknown;
}

View file

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

View file

@ -21,6 +21,7 @@
#include <LibWeb/CSS/CSSMediaRule.h>
#include <LibWeb/CSS/CSSNamespaceRule.h>
#include <LibWeb/CSS/CSSNestedDeclarations.h>
#include <LibWeb/CSS/CSSPageRule.h>
#include <LibWeb/CSS/CSSPropertyRule.h>
#include <LibWeb/CSS/CSSStyleProperties.h>
#include <LibWeb/CSS/CSSStyleRule.h>
@ -64,12 +65,15 @@ GC::Ptr<CSSRule> Parser::convert_to_rule(Rule const& rule, Nested nested)
if (at_rule.name.equals_ignoring_ascii_case("namespace"sv))
return convert_to_namespace_rule(at_rule);
if (at_rule.name.equals_ignoring_ascii_case("supports"sv))
return convert_to_supports_rule(at_rule, nested);
if (at_rule.name.equals_ignoring_ascii_case("page"sv))
return convert_to_page_rule(at_rule);
if (at_rule.name.equals_ignoring_ascii_case("property"sv))
return convert_to_property_rule(at_rule);
if (at_rule.name.equals_ignoring_ascii_case("supports"sv))
return convert_to_supports_rule(at_rule, nested);
// FIXME: More at rules!
dbgln_if(CSS_PARSER_DEBUG, "Unrecognized CSS at-rule: @{}", at_rule.name);
return {};
@ -596,4 +600,43 @@ GC::Ptr<CSSFontFaceRule> Parser::convert_to_font_face_rule(AtRule const& rule)
return CSSFontFaceRule::create(realm(), CSSFontFaceDescriptors::create(realm(), move(descriptors)));
}
GC::Ptr<CSSPageRule> Parser::convert_to_page_rule(AtRule const& rule)
{
// https://drafts.csswg.org/css-page-3/#syntax-page-selector
// @page = @page <page-selector-list>? { <declaration-rule-list> }
// <page-selector-list> = <page-selector>#
// <page-selector> = [ <ident-token>? <pseudo-page>* ]!
// <pseudo-page> = : [ left | right | first | blank ]
SelectorList page_selectors;
// FIXME: Parse page selectors
if (rule.prelude.find_first_index_if([](ComponentValue const& it) { return !it.is(Token::Type::Whitespace); }).has_value()) {
dbgln("@page prelude wasn't empty!");
return nullptr;
}
GC::RootVector<GC::Ref<CSSRule>> child_rules { realm().heap() };
Vector<Descriptor> descriptors;
HashTable<DescriptorID> seen_descriptor_ids;
rule.for_each_as_declaration_rule_list(
[&](auto& at_rule) {
// FIXME: Parse margin rules here.
(void)at_rule;
},
[&](auto& declaration) {
if (auto descriptor = convert_to_descriptor(AtRuleID::Page, declaration); descriptor.has_value()) {
if (seen_descriptor_ids.contains(descriptor->descriptor_id)) {
descriptors.remove_first_matching([&descriptor](Descriptor const& existing) {
return existing.descriptor_id == descriptor->descriptor_id;
});
} else {
seen_descriptor_ids.set(descriptor->descriptor_id);
}
descriptors.append(descriptor.release_value());
}
});
auto rule_list = CSSRuleList::create(realm(), child_rules);
return CSSPageRule::create(realm(), move(page_selectors), CSSPageDescriptors::create(realm(), move(descriptors)), rule_list);
}
}