LibWeb: Support custom descriptors

Some at-rules (i.e. `@function`) require us to support custom
descriptors (e.g. `--foo`).

We do this by adding `DescriptorID::Custom` and using a new
`DescriptorNameAndID` class in a bunch of places where we previously
just used `DescriptorID`
This commit is contained in:
Callum Law 2026-03-05 00:03:57 +13:00 committed by Sam Atkins
parent fc3dc8ac8c
commit e7243f0090
16 changed files with 272 additions and 152 deletions

View file

@ -173,10 +173,11 @@ The generated code provides:
Each at-rule object has the following fields. Both are required.
| Field | Description |
|---------------|---------------------------------------------------------------------------------------------------|
| `spec` | String. URL to the spec that defines this at-rule. |
| `descriptors` | Object, with keys being descriptor names and values being objects of their properties. See below. |
| Field | Description |
|----------------------|----------------------------------------------------------------------------------------------------------------------|
| `spec` | String. URL to the spec that defines this at-rule. |
| `descriptors` | Object, with keys being descriptor names and values being objects of their properties. See below. |
| `custom-descriptors` | Object, the configuration for custom descriptors (i.e. --foo). Omission means that custom descriptors are disallowed |
### Descriptor fields
@ -189,6 +190,15 @@ Each descriptor object can have the following fields:
| `syntax` | Yes | Array of strings. Each string is one option, taken from the spec. |
| `FIXME` or `NOTE` | No | Strings, for when you want to leave a note. |
### Custom descriptor fields
Each custom descriptor object has the following fields
| Field | Required | Description |
|--------------------|----------|----------------------------------------------|
| `syntax` | Yes | Array of strings. Each string is one option. |
| `FIXME` or `NOTE` | No | Strings, for when you want to leave a note. |
## Keywords.json
This is a single JSON array of strings, each of which is a CSS keyword, for example `auto`, `none`, `medium`, or `currentcolor`.

View file

@ -137,7 +137,7 @@ void CSSCounterStyleRule::set_system(FlyString const& system)
{
// 1. parse the given value as the descriptor associated with the attribute.
Parser::ParsingParams parsing_params { realm() };
auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, CSS::DescriptorID::System, system);
auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, DescriptorNameAndID::from_id(CSS::DescriptorID::System), system);
// 2. If the result is invalid according to the given descriptors grammar, or would cause the @counter-style rule
// to not define a counter style, do nothing and abort these steps. (For example, some systems require the
@ -169,7 +169,7 @@ void CSSCounterStyleRule::set_negative(FlyString const& negative)
{
Parser::ParsingParams parsing_params { realm() };
if (auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, CSS::DescriptorID::Negative, negative))
if (auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, DescriptorNameAndID::from_id(CSS::DescriptorID::Negative), negative))
m_negative = value;
}
@ -185,7 +185,7 @@ void CSSCounterStyleRule::set_prefix(FlyString const& prefix)
{
Parser::ParsingParams parsing_params { realm() };
if (auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, CSS::DescriptorID::Prefix, prefix))
if (auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, DescriptorNameAndID::from_id(CSS::DescriptorID::Prefix), prefix))
m_prefix = value;
}
@ -201,7 +201,7 @@ void CSSCounterStyleRule::set_suffix(FlyString const& suffix)
{
Parser::ParsingParams parsing_params { realm() };
if (auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, CSS::DescriptorID::Suffix, suffix))
if (auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, DescriptorNameAndID::from_id(CSS::DescriptorID::Suffix), suffix))
m_suffix = value;
}
@ -217,7 +217,7 @@ void CSSCounterStyleRule::set_range(FlyString const& range)
{
Parser::ParsingParams parsing_params { realm() };
if (auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, CSS::DescriptorID::Range, range))
if (auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, DescriptorNameAndID::from_id(CSS::DescriptorID::Range), range))
m_range = value;
}
@ -233,7 +233,7 @@ void CSSCounterStyleRule::set_pad(FlyString const& pad)
{
Parser::ParsingParams parsing_params { realm() };
if (auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, CSS::DescriptorID::Pad, pad))
if (auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, DescriptorNameAndID::from_id(CSS::DescriptorID::Pad), pad))
m_pad = value;
}
@ -249,7 +249,7 @@ void CSSCounterStyleRule::set_fallback(FlyString const& fallback)
{
Parser::ParsingParams parsing_params { realm() };
if (auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, CSS::DescriptorID::Fallback, fallback))
if (auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, DescriptorNameAndID::from_id(CSS::DescriptorID::Fallback), fallback))
m_fallback = value;
}
@ -269,7 +269,7 @@ void CSSCounterStyleRule::set_symbols(FlyString const& symbols)
// 1. parse the given value as the descriptor associated with the attribute.
Parser::ParsingParams parsing_params { realm() };
auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, CSS::DescriptorID::Symbols, symbols);
auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, DescriptorNameAndID::from_id(CSS::DescriptorID::Symbols), symbols);
// 2. If the result is invalid according to the given descriptors grammar, or would cause the @counter-style rule
// to not define a counter style, do nothing and abort these steps. (For example, some systems require the
@ -300,7 +300,7 @@ void CSSCounterStyleRule::set_additive_symbols(FlyString const& additive_symbols
// 1. parse the given value as the descriptor associated with the attribute.
Parser::ParsingParams parsing_params { realm() };
auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, CSS::DescriptorID::AdditiveSymbols, additive_symbols);
auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, DescriptorNameAndID::from_id(CSS::DescriptorID::AdditiveSymbols), additive_symbols);
// 2. If the result is invalid according to the given descriptors grammar, or would cause the @counter-style rule
// to not define a counter style, do nothing and abort these steps. (For example, some systems require the
@ -327,7 +327,7 @@ void CSSCounterStyleRule::set_speak_as(FlyString const& speak_as)
{
Parser::ParsingParams parsing_params { realm() };
if (auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, CSS::DescriptorID::SpeakAs, speak_as))
if (auto value = parse_css_descriptor(parsing_params, CSS::AtRuleID::CounterStyle, DescriptorNameAndID::from_id(CSS::DescriptorID::SpeakAs), speak_as))
m_speak_as = value;
}

View file

@ -38,16 +38,16 @@ String CSSDescriptors::item(size_t index) const
if (index >= length())
return {};
return to_string(m_descriptors[index].descriptor_id).to_string();
return m_descriptors[index].descriptor_name_and_id.name().to_string();
}
// https://drafts.csswg.org/cssom/#set-a-css-declaration
bool CSSDescriptors::set_a_css_declaration(DescriptorID descriptor_id, NonnullRefPtr<StyleValue const> value, Important)
bool CSSDescriptors::set_a_css_declaration(DescriptorNameAndID const& descriptor_name_and_id, NonnullRefPtr<StyleValue const> value, Important)
{
VERIFY(!is_computed());
for (auto& descriptor : m_descriptors) {
if (descriptor.descriptor_id == descriptor_id) {
if (descriptor.descriptor_name_and_id == descriptor_name_and_id) {
if (*descriptor.value == *value)
return false;
descriptor.value = move(value);
@ -56,7 +56,7 @@ bool CSSDescriptors::set_a_css_declaration(DescriptorID descriptor_id, NonnullRe
}
m_descriptors.append(Descriptor {
.descriptor_id = descriptor_id,
.descriptor_name_and_id = descriptor_name_and_id,
.value = move(value),
});
return true;
@ -70,12 +70,12 @@ WebIDL::ExceptionOr<void> CSSDescriptors::set_property(FlyString const& property
return WebIDL::NoModificationAllowedError::create(realm(), "Cannot modify properties of readonly CSSStyleDeclaration"_utf16);
// 2. If property is not a custom property, follow these substeps:
Optional<DescriptorID> descriptor_id;
Optional<DescriptorNameAndID> descriptor_name_and_id;
{
// 1. Let property be property converted to ASCII lowercase.
// 2. If property is not a case-sensitive match for a supported CSS property, then return.
descriptor_id = descriptor_id_from_string(m_at_rule_id, property);
if (!descriptor_id.has_value())
descriptor_name_and_id = DescriptorNameAndID::from_name(m_at_rule_id, property);
if (!descriptor_name_and_id.has_value())
return {};
}
@ -90,7 +90,7 @@ WebIDL::ExceptionOr<void> CSSDescriptors::set_property(FlyString const& property
return {};
// 5. Let component value list be the result of parsing value for property property.
RefPtr<StyleValue const> component_value_list = parse_css_descriptor(Parser::ParsingParams {}, m_at_rule_id, *descriptor_id, value);
RefPtr<StyleValue const> component_value_list = parse_css_descriptor(Parser::ParsingParams {}, m_at_rule_id, *descriptor_name_and_id, value);
// 6. If component value list is null, then return.
if (!component_value_list)
@ -100,14 +100,14 @@ WebIDL::ExceptionOr<void> CSSDescriptors::set_property(FlyString const& property
auto updated = false;
// 8. If property is a shorthand property, then for each longhand property longhand that property maps to, in canonical order, follow these substeps:
if (is_shorthand(m_at_rule_id, *descriptor_id)) {
for_each_expanded_longhand(m_at_rule_id, *descriptor_id, component_value_list, [this, &updated, priority](DescriptorID longhand_id, auto longhand_value) {
if (is_shorthand(m_at_rule_id, *descriptor_name_and_id)) {
for_each_expanded_longhand(m_at_rule_id, *descriptor_name_and_id, component_value_list, [this, &updated, priority](DescriptorNameAndID const& longhand_name_and_id, auto longhand_value) {
VERIFY(longhand_value);
// 1. Let longhand result be the result of set the CSS declaration longhand with the appropriate value(s)
// from component value list, with the important flag set if priority is not the empty string, and unset
// otherwise, and with the list of declarations being the declarations.
auto longhand_result = set_a_css_declaration(longhand_id, longhand_value.release_nonnull(), priority.is_empty() ? Important::No : Important::Yes);
auto longhand_result = set_a_css_declaration(longhand_name_and_id, longhand_value.release_nonnull(), priority.is_empty() ? Important::No : Important::Yes);
// 2. If longhand result is true, let updated be true.
if (longhand_result)
@ -118,7 +118,7 @@ WebIDL::ExceptionOr<void> CSSDescriptors::set_property(FlyString const& property
// with the important flag set if priority is not the empty string, and unset otherwise, and with the list of
// declarations being the declarations.
else {
updated = set_a_css_declaration(*descriptor_id, *component_value_list, !priority.is_empty() ? Important::Yes : Important::No);
updated = set_a_css_declaration(*descriptor_name_and_id, *component_value_list, !priority.is_empty() ? Important::Yes : Important::No);
}
// 10. If updated is true, update style attribute for the CSS declaration block.
@ -143,22 +143,22 @@ WebIDL::ExceptionOr<String> CSSDescriptors::remove_property(FlyString const& pro
// 4. Let removed be false.
bool removed = false;
auto descriptor_id = descriptor_id_from_string(m_at_rule_id, property);
auto descriptor_name_and_id = DescriptorNameAndID::from_name(m_at_rule_id, property);
// 5. If property is a shorthand property, for each longhand property longhand that property maps to:
if (descriptor_id.has_value() && is_shorthand(m_at_rule_id, *descriptor_id)) {
for_each_expanded_longhand(m_at_rule_id, *descriptor_id, nullptr, [this, &removed](DescriptorID longhand_id, auto const&) {
if (descriptor_name_and_id.has_value() && is_shorthand(m_at_rule_id, *descriptor_name_and_id)) {
for_each_expanded_longhand(m_at_rule_id, *descriptor_name_and_id, nullptr, [this, &removed](DescriptorNameAndID const& longhand_name_and_id, auto const&) {
// 1. If longhand is not a property name of a CSS declaration in the declarations, continue.
// 2. Remove that CSS declaration and let removed be true.
if (m_descriptors.remove_first_matching([longhand_id](auto& entry) { return entry.descriptor_id == longhand_id; })) {
if (m_descriptors.remove_first_matching([longhand_name_and_id](Descriptor const& entry) { return entry.descriptor_name_and_id == longhand_name_and_id; })) {
removed = true;
}
});
}
// 6. Otherwise, if property is a case-sensitive match for a property name of a CSS declaration in the
// declarations, remove that CSS declaration and let removed be true.
else if (descriptor_id.has_value()) {
removed = m_descriptors.remove_first_matching([descriptor_id](auto& entry) { return entry.descriptor_id == *descriptor_id; });
else if (descriptor_name_and_id.has_value()) {
removed = m_descriptors.remove_first_matching([descriptor_name_and_id](Descriptor const& entry) { return entry.descriptor_name_and_id == descriptor_name_and_id; });
}
// 7. If removed is true, Update style attribute for the CSS declaration block.
@ -177,9 +177,9 @@ String CSSDescriptors::get_property_value(FlyString const& property) const
// 2. If property is a case-sensitive match for a property name of a CSS declaration in the declarations, then
// return the result of invoking serialize a CSS value of that declaration.
auto descriptor_id = descriptor_id_from_string(m_at_rule_id, property);
if (descriptor_id.has_value()) {
auto match = m_descriptors.first_matching([descriptor_id](auto& entry) { return entry.descriptor_id == *descriptor_id; });
auto descriptor_name_and_id = DescriptorNameAndID::from_name(m_at_rule_id, property);
if (descriptor_name_and_id.has_value()) {
auto match = m_descriptors.first_matching([descriptor_name_and_id](Descriptor const& entry) { return entry.descriptor_name_and_id == descriptor_name_and_id; });
if (match.has_value())
return match->value->to_string(SerializationMode::Normal);
}
@ -208,7 +208,7 @@ String CSSDescriptors::serialized() const
// 3. Declaration loop: For each CSS declaration declaration in declaration blocks declarations, follow these substeps:
for (auto const& descriptor : m_descriptors) {
// 1. Let property be declarations property name.
auto property = to_string(descriptor.descriptor_id);
auto property = descriptor.descriptor_name_and_id.name();
// 2. If property is in already serialized, continue with the steps labeled declaration loop.
// AD-HOC: Not needed as we don't have shorthands.
@ -264,49 +264,49 @@ void CSSDescriptors::visit_edges(Visitor& visitor)
}
}
RefPtr<StyleValue const> CSSDescriptors::descriptor(DescriptorID descriptor_id) const
RefPtr<StyleValue const> CSSDescriptors::descriptor(DescriptorNameAndID const& descriptor_name_and_id) const
{
auto match = m_descriptors.first_matching([descriptor_id](Descriptor const& descriptor) {
return descriptor.descriptor_id == descriptor_id;
auto match = m_descriptors.first_matching([descriptor_name_and_id](Descriptor const& descriptor) {
return descriptor.descriptor_name_and_id == descriptor_name_and_id;
});
if (match.has_value())
return match->value;
return nullptr;
}
RefPtr<StyleValue const> CSSDescriptors::descriptor_or_initial_value(DescriptorID descriptor_id) const
RefPtr<StyleValue const> CSSDescriptors::descriptor_or_initial_value(DescriptorNameAndID const& descriptor_name_and_id) const
{
if (auto value = descriptor(descriptor_id))
if (auto value = descriptor(descriptor_name_and_id))
return value.release_nonnull();
return descriptor_initial_value(m_at_rule_id, descriptor_id);
return descriptor_initial_value(m_at_rule_id, descriptor_name_and_id.id());
}
bool is_shorthand(AtRuleID at_rule, DescriptorID descriptor)
bool is_shorthand(AtRuleID at_rule, DescriptorNameAndID const& descriptor)
{
if (at_rule == AtRuleID::Page && descriptor == DescriptorID::Margin)
if (at_rule == AtRuleID::Page && descriptor.id() == DescriptorID::Margin)
return true;
return false;
}
void for_each_expanded_longhand(AtRuleID at_rule, DescriptorID descriptor, RefPtr<StyleValue const> value, Function<void(DescriptorID, RefPtr<StyleValue const>)> callback)
void for_each_expanded_longhand(AtRuleID at_rule, DescriptorNameAndID const& descriptor, RefPtr<StyleValue const> value, Function<void(DescriptorNameAndID const&, RefPtr<StyleValue const>)> callback)
{
if (at_rule == AtRuleID::Page && descriptor == DescriptorID::Margin) {
if (at_rule == AtRuleID::Page && descriptor.id() == DescriptorID::Margin) {
if (!value) {
callback(DescriptorID::MarginTop, nullptr);
callback(DescriptorID::MarginRight, nullptr);
callback(DescriptorID::MarginBottom, nullptr);
callback(DescriptorID::MarginLeft, nullptr);
callback(DescriptorNameAndID::from_id(DescriptorID::MarginTop), nullptr);
callback(DescriptorNameAndID::from_id(DescriptorID::MarginRight), nullptr);
callback(DescriptorNameAndID::from_id(DescriptorID::MarginBottom), nullptr);
callback(DescriptorNameAndID::from_id(DescriptorID::MarginLeft), nullptr);
return;
}
auto const& shorthand_value = value->as_shorthand();
callback(DescriptorID::MarginTop, shorthand_value.longhand(PropertyID::MarginTop));
callback(DescriptorID::MarginRight, shorthand_value.longhand(PropertyID::MarginRight));
callback(DescriptorID::MarginBottom, shorthand_value.longhand(PropertyID::MarginBottom));
callback(DescriptorID::MarginLeft, shorthand_value.longhand(PropertyID::MarginLeft));
callback(DescriptorNameAndID::from_id(DescriptorID::MarginTop), shorthand_value.longhand(PropertyID::MarginTop));
callback(DescriptorNameAndID::from_id(DescriptorID::MarginRight), shorthand_value.longhand(PropertyID::MarginRight));
callback(DescriptorNameAndID::from_id(DescriptorID::MarginBottom), shorthand_value.longhand(PropertyID::MarginBottom));
callback(DescriptorNameAndID::from_id(DescriptorID::MarginLeft), shorthand_value.longhand(PropertyID::MarginLeft));
}
}

View file

@ -27,8 +27,8 @@ public:
virtual StringView get_property_priority(FlyString const& property) const override;
Vector<Descriptor> const& descriptors() const { return m_descriptors; }
RefPtr<StyleValue const> descriptor(DescriptorID) const;
RefPtr<StyleValue const> descriptor_or_initial_value(DescriptorID) const;
RefPtr<StyleValue const> descriptor(DescriptorNameAndID const&) const;
RefPtr<StyleValue const> descriptor_or_initial_value(DescriptorNameAndID const&) const;
virtual String serialized() const override;
virtual WebIDL::ExceptionOr<void> set_css_text(StringView) override;
@ -37,7 +37,7 @@ protected:
CSSDescriptors(JS::Realm&, AtRuleID, Vector<Descriptor>);
private:
bool set_a_css_declaration(DescriptorID, NonnullRefPtr<StyleValue const>, Important);
bool set_a_css_declaration(DescriptorNameAndID const&, NonnullRefPtr<StyleValue const>, Important);
virtual void visit_edges(Visitor&) override;
@ -45,7 +45,7 @@ private:
Vector<Descriptor> m_descriptors;
};
bool is_shorthand(AtRuleID, DescriptorID);
void for_each_expanded_longhand(AtRuleID, DescriptorID, RefPtr<StyleValue const>, Function<void(DescriptorID, RefPtr<StyleValue const>)>);
bool is_shorthand(AtRuleID, DescriptorNameAndID const&);
void for_each_expanded_longhand(AtRuleID, DescriptorNameAndID const&, RefPtr<StyleValue const>, Function<void(DescriptorNameAndID const&, RefPtr<StyleValue const>)>);
}

View file

@ -45,8 +45,8 @@ bool CSSFontFaceRule::is_valid() const
// @font-face rules require a font-family and src descriptor; if either of these are missing, the @font-face rule
// must not be considered when performing the font matching algorithm.
// https://drafts.csswg.org/css-fonts-4/#font-face-rule
return !m_style->descriptor(DescriptorID::FontFamily).is_null()
&& !m_style->descriptor(DescriptorID::Src).is_null();
return !m_style->descriptor(DescriptorNameAndID::from_id(DescriptorID::FontFamily)).is_null()
&& !m_style->descriptor(DescriptorNameAndID::from_id(DescriptorID::Src)).is_null();
}
ParsedFontFace CSSFontFaceRule::font_face() const
@ -69,21 +69,21 @@ String CSSFontFaceRule::serialized() const
// AD-HOC: We don't necessary always have a font-family descriptor as the spec assumes,
// see https://github.com/w3c/csswg-drafts/issues/13323
if (auto font_family = descriptors.descriptor(DescriptorID::FontFamily); !font_family.is_null()) {
if (auto font_family = descriptors.descriptor(DescriptorNameAndID::from_id(DescriptorID::FontFamily)); !font_family.is_null()) {
builder.append(' ');
// 2. The string "font-family:", followed by a single SPACE (U+0020).
builder.append("font-family: "sv);
// 3. The result of performing serialize a string on the rules font family name.
descriptors.descriptor(DescriptorID::FontFamily)->serialize(builder, SerializationMode::Normal);
descriptors.descriptor(DescriptorNameAndID::from_id(DescriptorID::FontFamily))->serialize(builder, SerializationMode::Normal);
// 4. The string ";", i.e., SEMICOLON (U+003B).
builder.append(';');
}
// 5. If the rules associated source list is not empty, follow these substeps:
if (auto sources = descriptors.descriptor(DescriptorID::Src)) {
if (auto sources = descriptors.descriptor(DescriptorNameAndID::from_id(DescriptorID::Src))) {
// 1. A single SPACE (U+0020), followed by the string "src:", followed by a single SPACE (U+0020).
builder.append(" src: "sv);
@ -95,7 +95,7 @@ String CSSFontFaceRule::serialized() const
}
// 6. If rules associated unicode-range descriptor is present, a single SPACE (U+0020), followed by the string "unicode-range:", followed by a single SPACE (U+0020), followed by the result of performing serialize a <'unicode-range'>, followed by the string ";", i.e., SEMICOLON (U+003B).
if (auto unicode_range = descriptors.descriptor(DescriptorID::UnicodeRange)) {
if (auto unicode_range = descriptors.descriptor(DescriptorNameAndID::from_id(DescriptorID::UnicodeRange))) {
builder.append(" unicode-range: "sv);
unicode_range->serialize(builder, SerializationMode::Normal);
builder.append(';');
@ -110,7 +110,7 @@ String CSSFontFaceRule::serialized() const
// followed by the string "font-feature-settings:", followed by a single SPACE (U+0020),
// followed by the result of performing serialize a <'font-feature-settings'>,
// followed by the string ";", i.e., SEMICOLON (U+003B).
if (auto font_feature_settings = descriptors.descriptor(DescriptorID::FontFeatureSettings)) {
if (auto font_feature_settings = descriptors.descriptor(DescriptorNameAndID::from_id(DescriptorID::FontFeatureSettings))) {
builder.append(" font-feature-settings: "sv);
font_feature_settings->serialize(builder, SerializationMode::Normal);
builder.append(";"sv);
@ -121,7 +121,7 @@ String CSSFontFaceRule::serialized() const
// followed by the result of performing serialize a <'font-stretch'>,
// followed by the string ";", i.e., SEMICOLON (U+003B).
// NOTE: font-stretch is now an alias for font-width, so we use that instead.
if (auto font_width = descriptors.descriptor(DescriptorID::FontWidth)) {
if (auto font_width = descriptors.descriptor(DescriptorNameAndID::from_id(DescriptorID::FontWidth))) {
builder.append(" font-stretch: "sv);
font_width->serialize(builder, SerializationMode::Normal);
builder.append(";"sv);
@ -131,7 +131,7 @@ String CSSFontFaceRule::serialized() const
// followed by the string "font-weight:", followed by a single SPACE (U+0020),
// followed by the result of performing serialize a <'font-weight'>,
// followed by the string ";", i.e., SEMICOLON (U+003B).
if (auto font_weight = descriptors.descriptor(DescriptorID::FontWeight)) {
if (auto font_weight = descriptors.descriptor(DescriptorNameAndID::from_id(DescriptorID::FontWeight))) {
builder.append(" font-weight: "sv);
font_weight->serialize(builder, SerializationMode::Normal);
builder.append(";"sv);
@ -141,7 +141,7 @@ String CSSFontFaceRule::serialized() const
// followed by the string "font-style:", followed by a single SPACE (U+0020),
// followed by the result of performing serialize a <'font-style'>,
// followed by the string ";", i.e., SEMICOLON (U+003B).
if (auto font_style = descriptors.descriptor(DescriptorID::FontStyle)) {
if (auto font_style = descriptors.descriptor(DescriptorNameAndID::from_id(DescriptorID::FontStyle))) {
builder.append(" font-style: "sv);
font_style->serialize(builder, SerializationMode::Normal);
builder.append(";"sv);

View file

@ -7,7 +7,7 @@
#pragma once
#include <AK/NonnullRefPtr.h>
#include <LibWeb/CSS/DescriptorID.h>
#include <LibWeb/CSS/DescriptorNameAndID.h>
#include <LibWeb/Forward.h>
namespace Web::CSS {
@ -15,7 +15,7 @@ namespace Web::CSS {
struct Descriptor {
~Descriptor();
DescriptorID descriptor_id;
DescriptorNameAndID descriptor_name_and_id;
NonnullRefPtr<StyleValue const> value;
};

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2026, Callum Law <callumlaw1709@outlook.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/CSS/DescriptorID.h>
#include <LibWeb/CSS/PropertyName.h>
#include <LibWeb/CSS/Serialize.h>
namespace Web::CSS {
class DescriptorNameAndID {
public:
static Optional<DescriptorNameAndID> from_name(AtRuleID at_rule_id, FlyString name)
{
if (is_a_custom_property_name_string(name))
return DescriptorNameAndID(move(name), DescriptorID::Custom);
if (auto descriptor_id = descriptor_id_from_string(at_rule_id, name); descriptor_id.has_value()) {
// NB: We use the serialized ID as the name here instead of the input name to ensure that legacy alias
// mapping is reflected
return DescriptorNameAndID(CSS::to_string(descriptor_id.value()), descriptor_id.value());
}
return {};
}
static DescriptorNameAndID from_id(DescriptorID descriptor_id)
{
VERIFY(descriptor_id != DescriptorID::Custom);
return DescriptorNameAndID(CSS::to_string(descriptor_id), descriptor_id);
}
DescriptorID id() const { return m_id; }
FlyString const& name() const
{
return m_name;
}
bool operator==(DescriptorNameAndID const& other) const
{
return m_id == other.m_id && m_name == other.m_name;
}
private:
DescriptorNameAndID(FlyString name, DescriptorID id)
: m_name(move(name))
, m_id(id)
{
}
FlyString m_name;
DescriptorID m_id;
};
}
namespace AK {
template<>
struct Traits<Web::CSS::DescriptorNameAndID> : public DefaultTraits<Web::CSS::DescriptorNameAndID> {
static unsigned hash(Web::CSS::DescriptorNameAndID const& descriptor_name_and_id)
{
return pair_int_hash(to_underlying(descriptor_name_and_id.id()), descriptor_name_and_id.name().hash());
}
};
}

View file

@ -96,7 +96,7 @@ GC::Ref<FontFace> FontFace::construct_impl(JS::Realm& realm, String family, Font
Parser::ParsingParams parsing_params { realm };
auto try_parse_descriptor = [&parsing_params, &font_face, &realm](DescriptorID descriptor_id, String const& string) -> String {
auto result = parse_css_descriptor(parsing_params, AtRuleID::FontFace, descriptor_id, string);
auto result = parse_css_descriptor(parsing_params, AtRuleID::FontFace, DescriptorNameAndID::from_id(descriptor_id), string);
if (!result) {
font_face->reject_status_promise(WebIDL::SyntaxError::create(realm, Utf16String::formatted("FontFace constructor: Invalid {}", to_string(descriptor_id))));
return {};
@ -120,7 +120,7 @@ GC::Ref<FontFace> FontFace::construct_impl(JS::Realm& realm, String family, Font
font_face->m_line_gap_override = try_parse_descriptor(DescriptorID::LineGapOverride, descriptors.line_gap_override);
RefPtr<StyleValue const> parsed_source;
if (auto* source_string = source.get_pointer<String>()) {
parsed_source = parse_css_descriptor(parsing_params, AtRuleID::FontFace, DescriptorID::Src, *source_string);
parsed_source = parse_css_descriptor(parsing_params, AtRuleID::FontFace, DescriptorNameAndID::from_id(DescriptorID::Src), *source_string);
if (!parsed_source) {
font_face->reject_status_promise(WebIDL::SyntaxError::create(realm, Utf16String::formatted("FontFace constructor: Invalid {}", to_string(DescriptorID::Src))));
}
@ -238,7 +238,7 @@ GC::Ref<FontFace> FontFace::create_css_connected(JS::Realm& realm, CSSFontFaceRu
font_face->m_css_font_face_rule = &rule;
font_face->reparse_connected_css_font_face_rule_descriptors();
if (auto src_value = rule.descriptors()->descriptor(DescriptorID::Src)) {
if (auto src_value = rule.descriptors()->descriptor(DescriptorNameAndID::from_id(DescriptorID::Src))) {
font_face->m_urls = ParsedFontFace::sources_from_style_value(*src_value);
font_face->m_urls.remove_all_matching(is_unsupported_source);
}
@ -252,17 +252,17 @@ void FontFace::reparse_connected_css_font_face_rule_descriptors()
{
auto const& descriptors = m_css_font_face_rule->descriptors();
set_family_impl(*descriptors->descriptor(DescriptorID::FontFamily));
set_style_impl(*descriptors->descriptor_or_initial_value(DescriptorID::FontStyle));
set_weight_impl(*descriptors->descriptor_or_initial_value(DescriptorID::FontWeight));
set_stretch_impl(*descriptors->descriptor_or_initial_value(DescriptorID::FontWidth));
set_unicode_range_impl(*descriptors->descriptor_or_initial_value(DescriptorID::UnicodeRange));
set_feature_settings_impl(*descriptors->descriptor_or_initial_value(DescriptorID::FontFeatureSettings));
set_variation_settings_impl(*descriptors->descriptor_or_initial_value(DescriptorID::FontVariationSettings));
set_display_impl(*descriptors->descriptor_or_initial_value(DescriptorID::FontDisplay));
set_ascent_override_impl(*descriptors->descriptor_or_initial_value(DescriptorID::AscentOverride));
set_descent_override_impl(*descriptors->descriptor_or_initial_value(DescriptorID::DescentOverride));
set_line_gap_override_impl(*descriptors->descriptor_or_initial_value(DescriptorID::LineGapOverride));
set_family_impl(*descriptors->descriptor(DescriptorNameAndID::from_id(DescriptorID::FontFamily)));
set_style_impl(*descriptors->descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::FontStyle)));
set_weight_impl(*descriptors->descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::FontWeight)));
set_stretch_impl(*descriptors->descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::FontWidth)));
set_unicode_range_impl(*descriptors->descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::UnicodeRange)));
set_feature_settings_impl(*descriptors->descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::FontFeatureSettings)));
set_variation_settings_impl(*descriptors->descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::FontVariationSettings)));
set_display_impl(*descriptors->descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::FontDisplay)));
set_ascent_override_impl(*descriptors->descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::AscentOverride)));
set_descent_override_impl(*descriptors->descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::DescentOverride)));
set_line_gap_override_impl(*descriptors->descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::LineGapOverride)));
}
ParsedFontFace FontFace::parsed_font_face() const
@ -342,7 +342,7 @@ WebIDL::ExceptionOr<void> FontFace::set_family(String const& string)
// If it does not match the grammar, throw a SyntaxError; otherwise, set the attribute to the serialization of the
// parsed value.
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorID::FontFamily, string);
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorNameAndID::from_id(DescriptorID::FontFamily), string);
if (!property)
return WebIDL::SyntaxError::create(realm(), "FontFace.family setter: Invalid descriptor value"_utf16);
@ -366,7 +366,7 @@ WebIDL::ExceptionOr<void> FontFace::set_style(String const& string)
// If it does not match the grammar, throw a SyntaxError; otherwise, set the attribute to the serialization of the
// parsed value.
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorID::FontStyle, string);
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorNameAndID::from_id(DescriptorID::FontStyle), string);
if (!property)
return WebIDL::SyntaxError::create(realm(), "FontFace.style setter: Invalid descriptor value"_utf16);
@ -390,7 +390,7 @@ WebIDL::ExceptionOr<void> FontFace::set_weight(String const& string)
// If it does not match the grammar, throw a SyntaxError; otherwise, set the attribute to the serialization of the
// parsed value.
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorID::FontWeight, string);
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorNameAndID::from_id(DescriptorID::FontWeight), string);
if (!property)
return WebIDL::SyntaxError::create(realm(), "FontFace.weight setter: Invalid descriptor value"_utf16);
@ -415,7 +415,7 @@ WebIDL::ExceptionOr<void> FontFace::set_stretch(String const& string)
// parsed value.
// NOTE: font-stretch is now an alias for font-width
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorID::FontWidth, string);
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorNameAndID::from_id(DescriptorID::FontWidth), string);
if (!property)
return WebIDL::SyntaxError::create(realm(), "FontFace.stretch setter: Invalid descriptor value"_utf16);
@ -439,7 +439,7 @@ WebIDL::ExceptionOr<void> FontFace::set_unicode_range(String const& string)
// If it does not match the grammar, throw a SyntaxError; otherwise, set the attribute to the serialization of the
// parsed value.
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorID::UnicodeRange, string);
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorNameAndID::from_id(DescriptorID::UnicodeRange), string);
if (!property)
return WebIDL::SyntaxError::create(realm(), "FontFace.unicodeRange setter: Invalid descriptor value"_utf16);
@ -463,7 +463,7 @@ WebIDL::ExceptionOr<void> FontFace::set_feature_settings(String const& string)
// If it does not match the grammar, throw a SyntaxError; otherwise, set the attribute to the serialization of the
// parsed value.
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorID::FontFeatureSettings, string);
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorNameAndID::from_id(DescriptorID::FontFeatureSettings), string);
if (!property)
return WebIDL::SyntaxError::create(realm(), "FontFace.featureSettings setter: Invalid descriptor value"_utf16);
@ -487,7 +487,7 @@ WebIDL::ExceptionOr<void> FontFace::set_variation_settings(String const& string)
// If it does not match the grammar, throw a SyntaxError; otherwise, set the attribute to the serialization of the
// parsed value.
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorID::FontVariationSettings, string);
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorNameAndID::from_id(DescriptorID::FontVariationSettings), string);
if (!property)
return WebIDL::SyntaxError::create(realm(), "FontFace.variationSettings setter: Invalid descriptor value"_utf16);
@ -511,7 +511,7 @@ WebIDL::ExceptionOr<void> FontFace::set_display(String const& string)
// If it does not match the grammar, throw a SyntaxError; otherwise, set the attribute to the serialization of the
// parsed value.
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorID::FontDisplay, string);
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorNameAndID::from_id(DescriptorID::FontDisplay), string);
if (!property)
return WebIDL::SyntaxError::create(realm(), "FontFace.display setter: Invalid descriptor value"_utf16);
@ -535,7 +535,7 @@ WebIDL::ExceptionOr<void> FontFace::set_ascent_override(String const& string)
// If it does not match the grammar, throw a SyntaxError; otherwise, set the attribute to the serialization of the
// parsed value.
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorID::AscentOverride, string);
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorNameAndID::from_id(DescriptorID::AscentOverride), string);
if (!property)
return WebIDL::SyntaxError::create(realm(), "FontFace.ascentOverride setter: Invalid descriptor value"_utf16);
@ -559,7 +559,7 @@ WebIDL::ExceptionOr<void> FontFace::set_descent_override(String const& string)
// If it does not match the grammar, throw a SyntaxError; otherwise, set the attribute to the serialization of the
// parsed value.
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorID::DescentOverride, string);
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorNameAndID::from_id(DescriptorID::DescentOverride), string);
if (!property)
return WebIDL::SyntaxError::create(realm(), "FontFace.descentOverride setter: Invalid descriptor value"_utf16);
@ -583,7 +583,7 @@ WebIDL::ExceptionOr<void> FontFace::set_line_gap_override(String const& string)
// If it does not match the grammar, throw a SyntaxError; otherwise, set the attribute to the serialization of the
// parsed value.
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorID::LineGapOverride, string);
auto property = parse_css_descriptor(Parser::ParsingParams(), AtRuleID::FontFace, DescriptorNameAndID::from_id(DescriptorID::LineGapOverride), string);
if (!property)
return WebIDL::SyntaxError::create(realm(), "FontFace.lineGapOverride setter: Invalid descriptor value"_utf16);

View file

@ -65,7 +65,7 @@ ParsedFontFace ParsedFontFace::from_descriptors(CSSFontFaceDescriptors const& de
};
FlyString font_family;
if (auto value = descriptors.descriptor_or_initial_value(DescriptorID::FontFamily))
if (auto value = descriptors.descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::FontFamily)))
font_family = string_from_style_value(*value);
ComputationContext computation_context {
@ -73,7 +73,7 @@ ParsedFontFace ParsedFontFace::from_descriptors(CSSFontFaceDescriptors const& de
};
Optional<FontWeightRange> weight;
if (auto value = descriptors.descriptor_or_initial_value(DescriptorID::FontWeight)) {
if (auto value = descriptors.descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::FontWeight))) {
// https://drafts.csswg.org/css-fonts-4/#font-prop-desc
// The auto values for these three descriptors have the following effects:
// - For font selection purposes, the font is selected as if the appropriate normal value (normal, normal or normal) is chosen
@ -102,7 +102,7 @@ ParsedFontFace ParsedFontFace::from_descriptors(CSSFontFaceDescriptors const& de
}
Optional<int> slope;
if (auto value = descriptors.descriptor_or_initial_value(DescriptorID::FontStyle)) {
if (auto value = descriptors.descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::FontStyle))) {
// https://drafts.csswg.org/css-fonts-4/#font-prop-desc
// The auto values for these three descriptors have the following effects:
// - For font selection purposes, the font is selected as if the appropriate normal value (normal, normal or normal) is chosen
@ -115,7 +115,7 @@ ParsedFontFace ParsedFontFace::from_descriptors(CSSFontFaceDescriptors const& de
}
Optional<int> width;
if (auto value = descriptors.descriptor_or_initial_value(DescriptorID::FontWidth)) {
if (auto value = descriptors.descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::FontWidth))) {
// https://drafts.csswg.org/css-fonts-4/#font-prop-desc
// The auto values for these three descriptors have the following effects:
// - For font selection purposes, the font is selected as if the appropriate normal value (normal, normal or normal) is chosen
@ -128,45 +128,45 @@ ParsedFontFace ParsedFontFace::from_descriptors(CSSFontFaceDescriptors const& de
}
Vector<Source> sources;
if (auto value = descriptors.descriptor_or_initial_value(DescriptorID::Src))
if (auto value = descriptors.descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::Src)))
sources = sources_from_style_value(*value);
Vector<Gfx::UnicodeRange> unicode_ranges;
if (auto value = descriptors.descriptor_or_initial_value(DescriptorID::UnicodeRange)) {
if (auto value = descriptors.descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::UnicodeRange))) {
for (auto const& range : value->as_value_list().values())
unicode_ranges.append(range->as_unicode_range().unicode_range());
}
Optional<Percentage> ascent_override;
if (auto value = descriptors.descriptor_or_initial_value(DescriptorID::AscentOverride))
if (auto value = descriptors.descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::AscentOverride)))
ascent_override = extract_percentage_or_normal(*value);
Optional<Percentage> descent_override;
if (auto value = descriptors.descriptor_or_initial_value(DescriptorID::DescentOverride))
if (auto value = descriptors.descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::DescentOverride)))
descent_override = extract_percentage_or_normal(*value);
Optional<Percentage> line_gap_override;
if (auto value = descriptors.descriptor_or_initial_value(DescriptorID::LineGapOverride))
if (auto value = descriptors.descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::LineGapOverride)))
line_gap_override = extract_percentage_or_normal(*value);
FontDisplay font_display;
if (auto value = descriptors.descriptor_or_initial_value(DescriptorID::FontDisplay))
if (auto value = descriptors.descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::FontDisplay)))
font_display = keyword_to_font_display(value->to_keyword()).value_or(FontDisplay::Auto);
Optional<FlyString> font_named_instance;
if (auto value = descriptors.descriptor_or_initial_value(DescriptorID::FontNamedInstance)) {
if (auto value = descriptors.descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::FontNamedInstance))) {
if (value->is_string())
font_named_instance = value->as_string().string_value();
}
Optional<FlyString> font_language_override;
if (auto value = descriptors.descriptor_or_initial_value(DescriptorID::FontLanguageOverride)) {
if (auto value = descriptors.descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::FontLanguageOverride))) {
if (value->is_string())
font_language_override = value->as_string().string_value();
}
Optional<OrderedHashMap<FlyString, i32>> font_feature_settings;
if (auto value = descriptors.descriptor_or_initial_value(DescriptorID::FontFeatureSettings)) {
if (auto value = descriptors.descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::FontFeatureSettings))) {
if (value->to_keyword() == Keyword::Normal) {
font_feature_settings.clear();
} else if (value->is_value_list()) {
@ -184,7 +184,7 @@ ParsedFontFace ParsedFontFace::from_descriptors(CSSFontFaceDescriptors const& de
}
Optional<OrderedHashMap<FlyString, double>> font_variation_settings;
if (auto value = descriptors.descriptor_or_initial_value(DescriptorID::FontVariationSettings)) {
if (auto value = descriptors.descriptor_or_initial_value(DescriptorNameAndID::from_id(DescriptorID::FontVariationSettings))) {
if (value->to_keyword() == Keyword::Normal) {
font_variation_settings.clear();
} else if (value->is_value_list()) {

View file

@ -22,17 +22,17 @@
namespace Web::CSS::Parser {
Parser::ParseErrorOr<NonnullRefPtr<StyleValue const>> Parser::parse_descriptor_value(AtRuleID at_rule_id, DescriptorID descriptor_id, TokenStream<ComponentValue>& unprocessed_tokens)
Parser::ParseErrorOr<NonnullRefPtr<StyleValue const>> Parser::parse_descriptor_value(AtRuleID at_rule_id, DescriptorNameAndID const& descriptor_name_and_id, TokenStream<ComponentValue>& unprocessed_tokens)
{
if (!at_rule_supports_descriptor(at_rule_id, descriptor_id)) {
if (!at_rule_supports_descriptor(at_rule_id, descriptor_name_and_id.id())) {
ErrorReporter::the().report(UnknownPropertyError {
.rule_name = to_string(at_rule_id),
.property_name = to_string(descriptor_id),
.property_name = descriptor_name_and_id.name(),
});
return ParseError::SyntaxError;
}
auto context_guard = push_temporary_value_parsing_context(DescriptorContext { at_rule_id, descriptor_id });
auto context_guard = push_temporary_value_parsing_context(DescriptorContext { at_rule_id, descriptor_name_and_id.id() });
Vector<ComponentValue> component_values;
while (unprocessed_tokens.has_next_token()) {
@ -48,7 +48,7 @@ Parser::ParseErrorOr<NonnullRefPtr<StyleValue const>> Parser::parse_descriptor_v
: Optional<ComputationContext> {};
TokenStream tokens { component_values };
auto metadata = get_descriptor_metadata(at_rule_id, descriptor_id);
auto metadata = get_descriptor_metadata(at_rule_id, descriptor_name_and_id.id());
for (auto const& option : metadata.syntax) {
auto transaction = tokens.begin_transaction();
auto parsed_style_value = option.visit(
@ -415,7 +415,7 @@ Parser::ParseErrorOr<NonnullRefPtr<StyleValue const>> Parser::parse_descriptor_v
ErrorReporter::the().report(InvalidPropertyError {
.rule_name = to_string(at_rule_id),
.property_name = to_string(descriptor_id),
.property_name = descriptor_name_and_id.name(),
.value_string = tokens.dump_string(),
.description = "Failed to parse."_string,
});
@ -425,16 +425,16 @@ Parser::ParseErrorOr<NonnullRefPtr<StyleValue const>> Parser::parse_descriptor_v
Optional<Descriptor> Parser::convert_to_descriptor(AtRuleID at_rule_id, Declaration const& declaration)
{
auto descriptor_id = descriptor_id_from_string(at_rule_id, declaration.name);
if (!descriptor_id.has_value())
auto descriptor_name_and_id = DescriptorNameAndID::from_name(at_rule_id, declaration.name);
if (!descriptor_name_and_id.has_value())
return {};
auto value_token_stream = TokenStream(declaration.value);
auto value = parse_descriptor_value(at_rule_id, descriptor_id.value(), value_token_stream);
auto value = parse_descriptor_value(at_rule_id, descriptor_name_and_id.value(), value_token_stream);
if (value.is_error())
return {};
return Descriptor { *descriptor_id, value.release_value() };
return Descriptor { descriptor_name_and_id.value(), value.release_value() };
}
}

View file

@ -89,11 +89,11 @@ RefPtr<CSS::StyleValue const> parse_css_type(CSS::Parser::ParsingParams const& c
return CSS::Parser::Parser::create(context, string).parse_as_type(value_type);
}
RefPtr<CSS::StyleValue const> parse_css_descriptor(CSS::Parser::ParsingParams const& parsing_params, CSS::AtRuleID at_rule_id, CSS::DescriptorID descriptor_id, StringView string)
RefPtr<CSS::StyleValue const> parse_css_descriptor(CSS::Parser::ParsingParams const& parsing_params, CSS::AtRuleID at_rule_id, CSS::DescriptorNameAndID const& descriptor_name_and_id, StringView string)
{
if (string.is_empty())
return nullptr;
return CSS::Parser::Parser::create(parsing_params, string).parse_as_descriptor_value(at_rule_id, descriptor_id);
return CSS::Parser::Parser::create(parsing_params, string).parse_as_descriptor_value(at_rule_id, descriptor_name_and_id);
}
CSS::CSSRule* parse_css_rule(CSS::Parser::ParsingParams const& context, StringView css_text)

View file

@ -1807,11 +1807,11 @@ RefPtr<StyleValue const> Parser::parse_as_css_value(PropertyID property_id)
return parsed_value.release_value();
}
RefPtr<StyleValue const> Parser::parse_as_descriptor_value(AtRuleID at_rule_id, DescriptorID descriptor_id)
RefPtr<StyleValue const> Parser::parse_as_descriptor_value(AtRuleID at_rule_id, DescriptorNameAndID const& descriptor_name_and_id)
{
auto component_values = parse_a_list_of_component_values(m_token_stream);
auto tokens = TokenStream(component_values);
auto parsed_value = parse_descriptor_value(at_rule_id, descriptor_id, tokens);
auto parsed_value = parse_descriptor_value(at_rule_id, descriptor_name_and_id, tokens);
if (parsed_value.is_error())
return nullptr;
return parsed_value.release_value();

View file

@ -160,7 +160,7 @@ public:
RefPtr<Supports> parse_as_supports();
RefPtr<StyleValue const> parse_as_css_value(PropertyID);
RefPtr<StyleValue const> parse_as_descriptor_value(AtRuleID, DescriptorID);
RefPtr<StyleValue const> parse_as_descriptor_value(AtRuleID, DescriptorNameAndID const&);
RefPtr<StyleValue const> parse_as_type(ValueType);
Optional<ComponentValue> parse_as_component_value();
@ -382,7 +382,7 @@ private:
RefPtr<RadialGradientStyleValue const> parse_radial_gradient_function(TokenStream<ComponentValue>&);
ParseErrorOr<NonnullRefPtr<StyleValue const>> parse_css_value(PropertyID, TokenStream<ComponentValue>&, Optional<String> original_source_text = {});
ParseErrorOr<NonnullRefPtr<StyleValue const>> parse_descriptor_value(AtRuleID, DescriptorID, TokenStream<ComponentValue>&);
ParseErrorOr<NonnullRefPtr<StyleValue const>> parse_descriptor_value(AtRuleID, DescriptorNameAndID const&, TokenStream<ComponentValue>&);
RefPtr<StyleValue const> parse_positional_value_list_shorthand(PropertyID, TokenStream<ComponentValue>&);
RefPtr<StyleValue const> parse_css_value_for_property(PropertyID, TokenStream<ComponentValue>&);
struct PropertyAndValue {
@ -645,7 +645,7 @@ CSS::Parser::Parser::PropertiesAndCustomProperties parse_css_property_declaratio
Vector<CSS::Descriptor> parse_css_descriptor_declaration_block(CSS::Parser::ParsingParams const&, CSS::AtRuleID, StringView);
RefPtr<CSS::StyleValue const> parse_css_value(CSS::Parser::ParsingParams const&, StringView, CSS::PropertyID);
RefPtr<CSS::StyleValue const> parse_css_type(CSS::Parser::ParsingParams const&, StringView, CSS::ValueType);
RefPtr<CSS::StyleValue const> parse_css_descriptor(CSS::Parser::ParsingParams const&, CSS::AtRuleID, CSS::DescriptorID, StringView);
RefPtr<CSS::StyleValue const> parse_css_descriptor(CSS::Parser::ParsingParams const&, CSS::AtRuleID, CSS::DescriptorNameAndID const&, StringView);
Optional<CSS::SelectorList> parse_selector(CSS::Parser::ParsingParams const&, StringView);
Optional<CSS::SelectorList> parse_selector_for_nested_style_rule(CSS::Parser::ParsingParams const&, StringView);
Optional<CSS::PageSelectorList> parse_page_selector_list(CSS::Parser::ParsingParams const&, StringView);

View file

@ -56,8 +56,8 @@ public:
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) {
if (is_shorthand(m_at_rule, descriptor.descriptor_name_and_id)) {
for_each_expanded_longhand(m_at_rule, descriptor.descriptor_name_and_id, descriptor.value, [this](auto longhand_id, auto longhand_value) {
append_internal(Descriptor { longhand_id, longhand_value.release_nonnull() });
});
return;
@ -74,19 +74,19 @@ public:
private:
void append_internal(Descriptor&& descriptor)
{
if (m_seen_descriptor_ids.contains(descriptor.descriptor_id)) {
if (m_seen_descriptor_ids.contains(descriptor.descriptor_name_and_id)) {
m_descriptors.remove_first_matching([&descriptor](Descriptor const& existing) {
return existing.descriptor_id == descriptor.descriptor_id;
return existing.descriptor_name_and_id == descriptor.descriptor_name_and_id;
});
} else {
m_seen_descriptor_ids.set(descriptor.descriptor_id);
m_seen_descriptor_ids.set(descriptor.descriptor_name_and_id);
}
m_descriptors.append(move(descriptor));
}
AtRuleID m_at_rule;
Vector<Descriptor> m_descriptors;
HashTable<DescriptorID> m_seen_descriptor_ids;
HashTable<DescriptorNameAndID> m_seen_descriptor_ids;
};
GC::Ptr<CSSRule> Parser::convert_to_rule(Rule const& rule, Nested nested)
@ -756,12 +756,12 @@ GC::Ptr<CSSPropertyRule> Parser::convert_to_property_rule(AtRule const& rule)
rule.for_each_as_declaration_list([&](auto& declaration) {
if (auto descriptor = convert_to_descriptor(AtRuleID::Property, declaration); descriptor.has_value()) {
if (descriptor->descriptor_id == DescriptorID::Syntax) {
if (descriptor->descriptor_name_and_id.id() == DescriptorID::Syntax) {
if (descriptor->value->is_string())
syntax_maybe = descriptor->value->as_string().string_value();
return;
}
if (descriptor->descriptor_id == DescriptorID::Inherits) {
if (descriptor->descriptor_name_and_id.id() == DescriptorID::Inherits) {
switch (descriptor->value->to_keyword()) {
case Keyword::True:
inherits_maybe = true;
@ -774,7 +774,7 @@ GC::Ptr<CSSPropertyRule> Parser::convert_to_property_rule(AtRule const& rule)
}
return;
}
if (descriptor->descriptor_id == DescriptorID::InitialValue) {
if (descriptor->descriptor_name_and_id.id() == DescriptorID::InitialValue) {
initial_value_maybe = *descriptor->value;
return;
}
@ -893,7 +893,7 @@ GC::Ptr<CSSCounterStyleRule> Parser::convert_to_counter_style_rule(AtRule const&
if (!descriptor.has_value())
return;
switch (descriptor->descriptor_id) {
switch (descriptor->descriptor_name_and_id.id()) {
case DescriptorID::System:
system = descriptor->value;
break;

View file

@ -685,7 +685,7 @@ void dump_descriptors(StringBuilder& builder, CSS::CSSDescriptors const& descrip
builder.appendff("Declarations ({}):\n", descriptors.length());
for (auto const& descriptor : descriptors.descriptors()) {
dump_indent(builder, indent_levels);
builder.appendff(" {}: '{}'", CSS::to_string(descriptor.descriptor_id), descriptor.value->to_string(CSS::SerializationMode::Normal));
builder.appendff(" {}: '{}'", descriptor.descriptor_name_and_id.name(), descriptor.value->to_string(CSS::SerializationMode::Normal));
builder.append('\n');
}
}

View file

@ -106,6 +106,7 @@ enum class DescriptorID : @descriptor_id_underlying_type@ {
member_generator.appendln(" @name:titlecase@,");
}
generator.append(R"~~~(
Custom,
};
Optional<DescriptorID> descriptor_id_from_string(AtRuleID, StringView);
@ -197,6 +198,13 @@ Optional<DescriptorID> descriptor_id_from_string(AtRuleID at_rule_id, StringView
case AtRuleID::@at_rule:titlecase@:
)~~~");
if (at_rule.has_object("custom-descriptors"sv)) {
at_rule_generator.append(R"~~~(
if (is_a_custom_property_name_string(string))
return DescriptorID::Custom;
)~~~");
}
auto const& descriptors = at_rule.get_object("descriptors"sv).value();
descriptors.for_each_member([&](auto const& descriptor_name, JsonValue const& descriptor_value) {
@ -242,6 +250,8 @@ FlyString to_string(DescriptorID descriptor_id)
}
generator.append(R"~~~(
case DescriptorID::Custom:
VERIFY_NOT_REACHED();
}
VERIFY_NOT_REACHED();
}
@ -271,6 +281,12 @@ bool at_rule_supports_descriptor(AtRuleID at_rule_id, DescriptorID descriptor_id
descriptor_generator.appendln(" case DescriptorID::@descriptor:titlecase@:");
});
if (at_rule.has_object("custom-descriptors"sv)) {
at_rule_generator.append(R"~~~(
case DescriptorID::Custom:
)~~~");
}
at_rule_generator.append(R"~~~(
return true;
default:
@ -323,7 +339,7 @@ RefPtr<StyleValue const> descriptor_initial_value(AtRuleID at_rule_id, Descripto
descriptor_generator.set("initial_value_string", initial_value.value());
descriptor_generator.append(R"~~~(
case DescriptorID::@descriptor:titlecase@: {
auto parsed_value = parse_css_descriptor(parsing_params, AtRuleID::@at_rule:titlecase@, DescriptorID::@descriptor:titlecase@, "@initial_value_string@"sv);
auto parsed_value = parse_css_descriptor(parsing_params, AtRuleID::@at_rule:titlecase@, DescriptorNameAndID::from_id(DescriptorID::@descriptor:titlecase@), "@initial_value_string@"sv);
VERIFY(!parsed_value.is_null());
auto initial_value = parsed_value.release_nonnull();
initial_values[to_underlying(at_rule_id)][to_underlying(descriptor_id)] = initial_value;
@ -365,19 +381,7 @@ DescriptorMetadata get_descriptor_metadata(AtRuleID at_rule_id, DescriptorID des
switch (descriptor_id) {
)~~~");
auto const& descriptors = at_rule.get_object("descriptors"sv).value();
descriptors.for_each_member([&](auto const& descriptor_name, JsonValue const& descriptor_value) {
auto const& descriptor = descriptor_value.as_object();
if (is_legacy_alias(descriptor))
return;
auto descriptor_generator = at_rule_generator.fork();
descriptor_generator.set("descriptor:titlecase", title_casify(descriptor_name));
descriptor_generator.append(R"~~~(
case DescriptorID::@descriptor:titlecase@: {
DescriptorMetadata metadata;
)~~~");
auto const& syntax = descriptor.get_array("syntax"sv).value();
auto const generate_syntax_list = [&](SourceGenerator& descriptor_generator, JsonArray const& syntax) {
for (auto const& entry : syntax.values()) {
auto option_generator = descriptor_generator.fork();
auto const& syntax_string = entry.as_string();
@ -446,12 +450,46 @@ DescriptorMetadata get_descriptor_metadata(AtRuleID at_rule_id, DescriptorID des
)~~~");
}
}
};
auto const& descriptors = at_rule.get_object("descriptors"sv).value();
descriptors.for_each_member([&](auto const& descriptor_name, JsonValue const& descriptor_value) {
auto const& descriptor = descriptor_value.as_object();
if (is_legacy_alias(descriptor))
return;
auto descriptor_generator = at_rule_generator.fork();
descriptor_generator.set("descriptor:titlecase", title_casify(descriptor_name));
descriptor_generator.append(R"~~~(
case DescriptorID::@descriptor:titlecase@: {
DescriptorMetadata metadata;
)~~~");
auto const& syntax = descriptor.get_array("syntax"sv).value();
generate_syntax_list(descriptor_generator, syntax);
descriptor_generator.append(R"~~~(
return metadata;
}
)~~~");
});
if (at_rule.has_object("custom-descriptors"sv)) {
auto const& custom_descriptors = at_rule.get_object("custom-descriptors"sv).value();
auto custom_descriptor_generator = at_rule_generator.fork();
custom_descriptor_generator.append(R"~~~(
case DescriptorID::Custom: {
DescriptorMetadata metadata;
)~~~");
auto const& syntax = custom_descriptors.get_array("syntax"sv).value();
generate_syntax_list(custom_descriptor_generator, syntax);
custom_descriptor_generator.append(R"~~~(
return metadata;
}
)~~~");
}
at_rule_generator.append(R"~~~(
default:
VERIFY_NOT_REACHED();