LibWeb: Skip property computation in some cases

There are cases where we can skip the property value computation process
because we know that the computed value will be equal to the specified
value
This commit is contained in:
Callum Law 2026-01-26 23:20:46 +13:00 committed by Andreas Kling
parent 9195f26003
commit 63584321fe
Notes: github-actions[bot] 2026-02-13 20:56:04 +00:00
4 changed files with 422 additions and 29 deletions

View file

@ -19,24 +19,25 @@ Each property will have some set of these fields on it:
(Note that required fields are not required on properties with `legacy-alias-for` or `logical-alias-for` set.)
| Field | Required | Default | Description | Generated functions |
|-------------------------------------|----------|------------|-------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `affects-layout` | No | `true` | Boolean. Whether changing this property will invalidate the element's layout. | `bool property_affects_layout(PropertyID)` |
| `affects-stacking-context` | No | `false` | Boolean. Whether this property can cause a new stacking context for the element. | `bool property_affects_stacking_context(PropertyID)` |
| `animation-type` | Yes | | String. How the property should be animated. Defined by the spec. See below. | `AnimationType animation_type_from_longhand_property(PropertyID)` |
| `inherited` | Yes | | Boolean. Whether the property is inherited by its child elements. Only applicable to longhand properties. | `bool is_inherited_property(PropertyID)` |
| `initial` | Yes | | String. The property's initial value if it is not specified. | `NonnullRefPtr<StyleValue const> property_initial_value(PropertyID)` |
| `legacy-alias-for` | No | Nothing | String. The name of a property this is an alias for. See below. | |
| `logical-alias-for` | No | Nothing | An object. See below. | `bool property_is_logical_alias(PropertyID);`<br/>`PropertyID map_logical_alias_to_physical_property(PropertyID, LogicalAliasMappingContext const&)` |
| `longhands` | No | `[]` | Array of strings. If this is a shorthand, these are the property names that it expands out into. | `Vector<PropertyID> longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> expanded_longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> shorthands_for_longhand(PropertyID)` |
| `max-values` | No | `1` | Integer. How many values can be parsed for this property. eg, `margin` can have up to 4 values. | `size_t property_maximum_value_count(PropertyID)` |
| `multiplicity` | No | `"single"` | String. Category for whether this property is a single value or a list of values. See below. | `bool property_is_single_valued(PropertyID)`<br/>`bool property_is_list_valued(PropertyID)`<br/>`PropertyMultiplicity property_multiplicity(PropertyID)` |
| `percentages-resolve-to` | No | Nothing | String. What type percentages get resolved to. eg, for `width` percentages are resolved to `length` values. | `Optional<ValueType> property_resolves_percentages_relative_to(PropertyID)` |
| `positional-value-list-shorthand` | No | `false` | Boolean. Whether this property is a "positional value list shorthand". See below. | `bool property_is_positional_value_list_shorthand(PropertyID)` |
| `quirks` | No | `[]` | Array of strings. Some properties have special behavior in "quirks mode", which are listed here. See below. | `bool property_has_quirk(PropertyID, Quirk)` |
| `valid-identifiers` | No | `[]` | Array of strings. Which keywords the property accepts. See below. | `bool property_accepts_keyword(PropertyID, Keyword)`<br/>`Optional<Keyword> resolve_legacy_value_alias(PropertyID, Keyword)` |
| `valid-types` | No | `[]` | Array of strings. Which value types the property accepts. See below. | `bool property_accepts_type(PropertyID, ValueType)` |
| `needs-layout-for-getcomputedstyle` | No | `false` | Boolean. Whether this property requires up-to-date layout before it could be queried by getComputedStyle() | `bool property_needs_layout_for_getcomputedstyle(PropertyID)` |
| Field | Required | Default | Description | Generated functions |
|-------------------------------------|----------|------------|-------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `affects-layout` | No | `true` | Boolean. Whether changing this property will invalidate the element's layout. | `bool property_affects_layout(PropertyID)` |
| `affects-stacking-context` | No | `false` | Boolean. Whether this property can cause a new stacking context for the element. | `bool property_affects_stacking_context(PropertyID)` |
| `animation-type` | Yes | | String. How the property should be animated. Defined by the spec. See below. | `AnimationType animation_type_from_longhand_property(PropertyID)` |
| `inherited` | Yes | | Boolean. Whether the property is inherited by its child elements. Only applicable to longhand properties. | `bool is_inherited_property(PropertyID)` |
| `initial` | Yes | | String. The property's initial value if it is not specified. | `NonnullRefPtr<StyleValue const> property_initial_value(PropertyID)` |
| `legacy-alias-for` | No | Nothing | String. The name of a property this is an alias for. See below. | |
| `logical-alias-for` | No | Nothing | An object. See below. | `bool property_is_logical_alias(PropertyID);`<br/>`PropertyID map_logical_alias_to_physical_property(PropertyID, LogicalAliasMappingContext const&)` |
| `longhands` | No | `[]` | Array of strings. If this is a shorthand, these are the property names that it expands out into. | `Vector<PropertyID> longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> expanded_longhands_for_shorthand(PropertyID)`<br/>`Vector<PropertyID> shorthands_for_longhand(PropertyID)` |
| `max-values` | No | `1` | Integer. How many values can be parsed for this property. eg, `margin` can have up to 4 values. | `size_t property_maximum_value_count(PropertyID)` |
| `multiplicity` | No | `"single"` | String. Category for whether this property is a single value or a list of values. See below. | `bool property_is_single_valued(PropertyID)`<br/>`bool property_is_list_valued(PropertyID)`<br/>`PropertyMultiplicity property_multiplicity(PropertyID)` |
| `percentages-resolve-to` | No | Nothing | String. What type percentages get resolved to. eg, for `width` percentages are resolved to `length` values. | `Optional<ValueType> property_resolves_percentages_relative_to(PropertyID)` |
| `positional-value-list-shorthand` | No | `false` | Boolean. Whether this property is a "positional value list shorthand". See below. | `bool property_is_positional_value_list_shorthand(PropertyID)` |
| `quirks` | No | `[]` | Array of strings. Some properties have special behavior in "quirks mode", which are listed here. See below. | `bool property_has_quirk(PropertyID, Quirk)` |
| `requires-computation` | Yes | | String. When a property's value needs to be run through the computation process. See below. | `bool property_requires_computation_with_inherited_value(PropertyID)`<br/>`bool property_requires_computation_with_initial_value(PropertyID)`<br/>`bool property_requires_computation_with_cascaded_value(PropertyID)` |
| `valid-identifiers` | No | `[]` | Array of strings. Which keywords the property accepts. See below. | `bool property_accepts_keyword(PropertyID, Keyword)`<br/>`Optional<Keyword> resolve_legacy_value_alias(PropertyID, Keyword)` |
| `valid-types` | No | `[]` | Array of strings. Which value types the property accepts. See below. | `bool property_accepts_type(PropertyID, ValueType)` |
| `needs-layout-for-getcomputedstyle` | No | `false` | Boolean. Whether this property requires up-to-date layout before it could be queried by getComputedStyle() | `bool property_needs_layout_for_getcomputedstyle(PropertyID)` |
### `animation-type`
@ -96,6 +97,30 @@ The [Quirks spec](https://quirks.spec.whatwg.org/#css) defines these.
| The hashless hex color quirk | `hashless-hex-color` |
| The unitless length quirk | `unitless-length` |
### `requires-computation`
There are three ways the specified value of a property on an element can be determined:
- Inherited, e.g. it is an inherited property and no value has been specified, or the `inherit` keyword has been
specified
- Initial, e.g. it is _not_ an inherited property and no value has been specified, or the `initial` keyword has been
specified
- Cascaded, a value has been cascaded for this element based on the styles
We then turn that specified value into a computed value by running the computation (see
`StyleComputer::compute_value_of_property`).
Depending on the property, we know that for some of the above cases the computed value is guaranteed to be the same as
the specified value, by specifying the cases where it _isn't_ we can limit running the computation process to only when
required.
| Value | Behavior |
| --------------------- | ------------------------------------------------------------------------------------------------------------- |
| `always` | Always runs the computation process, regardless of how the specified value was determined |
| `non-inherited-value` | Only run the computation process if the specified value was determined based on the initial or cascaded value |
| `cascaded-value` | Only run the computation process if the specified value was determined based on a cascaded value |
| `never` | Always skip the computation process |
### `valid-identifiers`
A list of CSS keyword names, that the property accepts. Consider defining an enum instead and putting its name in the

File diff suppressed because it is too large Load diff

View file

@ -1965,10 +1965,12 @@ GC::Ref<ComputedProperties> StyleComputer::compute_properties(DOM::AbstractEleme
auto inherited = ComputedProperties::Inherited::No;
RefPtr<StyleValue const> value;
auto important = Important::No;
bool requires_computation;
if (auto cascaded_style_property = cascaded_properties.style_property(property_id); cascaded_style_property.has_value()) {
important = cascaded_style_property->important;
value = cascaded_style_property->value;
requires_computation = property_requires_computation_with_cascaded_value(property_id);
}
// NOTE: We've already handled font-size above.
@ -1990,19 +1992,23 @@ GC::Ref<ComputedProperties> StyleComputer::compute_properties(DOM::AbstractEleme
should_inherit |= property_id == PropertyID::Color && value && value->to_keyword() == Keyword::Currentcolor;
// FIXME: Logical properties should inherit from their parent's equivalent unmapped logical property.
if (should_inherit) {
if (should_inherit && abstract_element.element_to_inherit_style_from().has_value()) {
inherited = ComputedProperties::Inherited::Yes;
value = get_non_animated_inherit_value(property_id, abstract_element);
requires_computation = property_requires_computation_with_inherited_value(property_id);
// FIXME: Do we need to recompute animated inherited values?
if (auto animated_value = get_animated_inherit_value(property_id, abstract_element); animated_value.has_value())
computed_style->set_animated_property(property_id, animated_value->value, animated_value->is_result_of_transition, ComputedProperties::Inherited::Yes);
}
if (!value || value->is_initial() || value->is_unset())
if (!value || value->is_initial() || value->is_unset() || (should_inherit && !abstract_element.element_to_inherit_style_from().has_value())) {
value = property_initial_value(property_id);
requires_computation = property_requires_computation_with_initial_value(property_id);
}
computed_style->set_property(property_id, compute_property(property_id, value.release_nonnull()), inherited, important);
computed_style->set_property(property_id, requires_computation ? compute_property(property_id, value.release_nonnull()) : value.release_nonnull(), inherited, important);
}
if (is<HTML::HTMLHtmlElement>(abstract_element.element()))

View file

@ -292,6 +292,10 @@ Vector<PropertyID> const& shorthands_for_longhand(PropertyID);
Vector<PropertyID> const& property_computation_order();
bool property_is_positional_value_list_shorthand(PropertyID);
bool property_requires_computation_with_inherited_value(PropertyID);
bool property_requires_computation_with_initial_value(PropertyID);
bool property_requires_computation_with_cascaded_value(PropertyID);
size_t property_maximum_value_count(PropertyID);
bool property_affects_layout(PropertyID);
@ -1607,6 +1611,101 @@ bool property_is_positional_value_list_shorthand(PropertyID property_id)
return false;
}
}
)~~~");
Vector<StringView> properties_requiring_computation_with_inherited_value;
Vector<StringView> properties_requiring_computation_with_initial_value;
Vector<StringView> properties_requiring_computation_with_cascaded_value;
properties.for_each_member([&](auto& name, auto& value) {
VERIFY(value.is_object());
if (is_legacy_alias(value.as_object()))
return;
auto const& requires_computation = value.as_object().get_string("requires-computation"sv);
if (requires_computation.has_value() && value.as_object().has("longhands"sv)) {
dbgln("Property '{}' is a shorthand and cannot have 'requires-computation' set.", name);
VERIFY_NOT_REACHED();
}
if (value.as_object().has("longhands"sv))
return;
if (!requires_computation.has_value()) {
dbgln("Property '{}' is missing 'requires-computation' field.", name);
VERIFY_NOT_REACHED();
}
if (requires_computation.value() == "always"sv) {
properties_requiring_computation_with_inherited_value.append(name);
properties_requiring_computation_with_initial_value.append(name);
properties_requiring_computation_with_cascaded_value.append(name);
} else if (requires_computation.value() == "non-inherited-value"sv) {
properties_requiring_computation_with_initial_value.append(name);
properties_requiring_computation_with_cascaded_value.append(name);
} else if (requires_computation.value() == "cascaded-value"sv) {
properties_requiring_computation_with_cascaded_value.append(name);
} else if (requires_computation.value() != "never"sv) {
dbgln("Property '{}' has unrecognized 'requires-computation' value '{}'", name, requires_computation.value());
VERIFY_NOT_REACHED();
}
});
generator.append(R"~~~(
bool property_requires_computation_with_inherited_value(PropertyID property_id)
{
switch(property_id) {
)~~~");
for (auto const& property_name : properties_requiring_computation_with_inherited_value) {
auto property_generator = generator.fork();
property_generator.set("name:titlecase", title_casify(property_name));
property_generator.appendln(" case PropertyID::@name:titlecase@:");
}
generator.append(R"~~~(
return true;
default:
return false;
}
}
bool property_requires_computation_with_initial_value(PropertyID property_id)
{
switch(property_id) {
)~~~");
for (auto const& property_name : properties_requiring_computation_with_initial_value) {
auto property_generator = generator.fork();
property_generator.set("name:titlecase", title_casify(property_name));
property_generator.appendln(" case PropertyID::@name:titlecase@:");
}
generator.append(R"~~~(
return true;
default:
return false;
}
}
bool property_requires_computation_with_cascaded_value(PropertyID property_id)
{
switch(property_id) {
)~~~");
for (auto const& property_name : properties_requiring_computation_with_cascaded_value) {
auto property_generator = generator.fork();
property_generator.set("name:titlecase", title_casify(property_name));
property_generator.appendln(" case PropertyID::@name:titlecase@:");
}
generator.append(R"~~~(
return true;
default:
return false;
}
}
)~~~");
generator.append(R"~~~(