LibWeb/CSS: Add flags for element-backed & tree-abiding pseudo-elements

Generate a couple of functions for checking if a pseudo-element fits
these categories.
This commit is contained in:
Sam Atkins
2026-03-31 16:48:28 +01:00
parent b23aa38546
commit 492cfc58d9
Notes: github-actions[bot] 2026-04-08 09:38:21 +00:00
3 changed files with 80 additions and 7 deletions

View File

@@ -262,7 +262,9 @@ Each entry has the following properties:
| `alias-for` | No | Nothing | Use to specify that this should be treated as an alias for the named pseudo-element. |
| `function-syntax` | No | Nothing | Syntax for the function arguments if this is a function-type pseudo-element. Copied directly from the spec. |
| `is-allowed-in-has` | No | `false` | Whether this is a [`:has`-allowed pseudo-element](https://drafts.csswg.org/selectors/#has-allowed-pseudo-element). |
| `is-element-backed` | No | `false` | Whether this is an [element-backed pseudo-element](https://drafts.csswg.org/css-pseudo-4/#element-backed). |
| `is-pseudo-root` | No | `false` | Whether this is a [pseudo-element root](https://drafts.csswg.org/css-view-transitions/#pseudo-element-root). |
| `is-tree-abiding` | No | `false` | Whether this is a [tree-abiding pseudo-element](https://drafts.csswg.org/css-pseudo-4/#tree-abiding). |
| `property-whitelist` | No | Nothing | Some pseudo-elements only permit certain properties. If so, name them in an array here. Some special values are allowed here for categories of properties - see below. |
| `spec` | No | Nothing | Link to the spec definition, for reference. Not used in generated code. |
| `type` | No | `"identifier"` | What type of pseudo-element is this. Either "identifier", "function", or "both". |
@@ -273,6 +275,8 @@ The generated code provides:
- `Optional<PseudoElement> aliased_pseudo_element_from_string(StringView)` is similar, but returns the `PseudoElement` this name is an alias for
- `StringView pseudo_element_name(PseudoElement)` to convert a `PseudoElement` back into a string
- `bool is_has_allowed_pseudo_element(PseudoElement)` returns whether the pseudo-element is valid inside `:has()`
- `bool is_element_backed_pseudo_element(PseudoElement)` returns whether the pseudo-element is element-backed
- `bool is_tree_abiding_pseudo_element(PseudoElement)` returns whether the pseudo-element is tree-abiding
- `bool is_pseudo_element_root(PseudoElement)` returns whether the pseudo-element is a [pseudo-element root](https://drafts.csswg.org/css-view-transitions/#pseudo-element-root)
- `bool pseudo_element_supports_property(PseudoElement, PropertyID)` returns whether the property can be applied to this pseudo-element

View File

@@ -36,19 +36,23 @@
"alias-for": "slider-thumb"
},
"after": {
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-after"
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-after",
"is-tree-abiding": true
},
"backdrop": {
"spec": "https://drafts.csswg.org/css-position-4/#selectordef-backdrop"
},
"before": {
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-before"
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-before",
"is-tree-abiding": true
},
"details-content": {
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-details-content"
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-details-content",
"is-element-backed": true
},
"file-selector-button": {
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-file-selector-button"
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-file-selector-button",
"is-element-backed": true
},
"first-letter": {
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-first-letter",
@@ -82,15 +86,18 @@
]
},
"marker": {
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-marker"
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-marker",
"is-tree-abiding": true
},
"part": {
"spec": "https://drafts.csswg.org/css-shadow-1/#selectordef-part",
"type": "function",
"function-syntax": "<ident>+"
"function-syntax": "<ident>+",
"is-element-backed": true
},
"placeholder": {
"spec": "https://drafts.csswg.org/css-pseudo-4/#selectordef-placeholder",
"is-tree-abiding": true,
"property-whitelist": [
"#font-properties",
"color",
@@ -127,7 +134,8 @@
"slotted": {
"spec": "https://drafts.csswg.org/css-shadow-1/#slotted-pseudo",
"type": "function",
"function-syntax": "<compound-selector>"
"function-syntax": "<compound-selector>",
"is-element-backed": true
},
"view-transition": {
"spec": "https://drafts.csswg.org/css-view-transitions-1/#selectordef-view-transition",

View File

@@ -87,6 +87,8 @@ Optional<PseudoElement> aliased_pseudo_element_from_string(StringView);
WEB_API StringView pseudo_element_name(PseudoElement);
bool is_has_allowed_pseudo_element(PseudoElement);
bool is_tree_abiding_pseudo_element(PseudoElement);
bool is_element_backed_pseudo_element(PseudoElement);
bool is_pseudo_element_root(PseudoElement);
bool pseudo_element_supports_property(PseudoElement, PropertyID);
@@ -222,6 +224,65 @@ bool is_has_allowed_pseudo_element(PseudoElement pseudo_element)
}
}
bool is_tree_abiding_pseudo_element(PseudoElement pseudo_element)
{
// Element-backed pseudo-elements are always tree-abiding.
// https://drafts.csswg.org/css-pseudo-4/#element-backed
if (is_element_backed_pseudo_element(pseudo_element))
return true;
switch (pseudo_element) {
)~~~");
pseudo_elements_data.for_each_member([&](auto& name, JsonValue const& value) {
auto& pseudo_element = value.as_object();
if (pseudo_element.has("alias-for"sv))
return;
if (!pseudo_element.get_bool("is-tree-abiding"sv).value_or(false))
return;
auto member_generator = generator.fork();
member_generator.set("name:titlecase", title_casify(name));
member_generator.append(R"~~~(
case PseudoElement::@name:titlecase@:
return true;
)~~~");
});
generator.append(R"~~~(
default:
return false;
}
}
bool is_element_backed_pseudo_element(PseudoElement pseudo_element)
{
switch (pseudo_element) {
)~~~");
pseudo_elements_data.for_each_member([&](auto& name, JsonValue const& value) {
auto& pseudo_element = value.as_object();
if (pseudo_element.has("alias-for"sv))
return;
if (!pseudo_element.get_bool("is-element-backed"sv).value_or(false))
return;
auto member_generator = generator.fork();
member_generator.set("name:titlecase", title_casify(name));
member_generator.append(R"~~~(
case PseudoElement::@name:titlecase@:
return true;
)~~~");
});
generator.append(R"~~~(
default:
return false;
}
}
bool is_pseudo_element_root(PseudoElement pseudo_element)
{
switch (pseudo_element) {