This commit is contained in:
Sam Atkins 2025-04-04 08:16:43 +00:00 committed by GitHub
commit 2085ea887a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 218 additions and 51 deletions

View file

@ -861,11 +861,17 @@ WebIDL::ExceptionOr<void> KeyframeEffect::set_keyframes(Optional<GC::Root<JS::Ob
auto key = static_cast<u64>(keyframe.computed_offset.value() * 100 * AnimationKeyFrameKeyScaleFactor);
for (auto [property_id, property_value] : keyframe.parsed_properties()) {
if (property_value->is_unresolved() && target)
if (property_value->is_unresolved() && target) {
property_value = CSS::Parser::Parser::resolve_unresolved_style_value(CSS::Parser::ParsingParams { target->document() }, *target, pseudo_element_type(), property_id, property_value->as_unresolved());
CSS::StyleComputer::for_each_property_expanding_shorthands(property_id, property_value, CSS::StyleComputer::AllowUnresolved::Yes, [&](CSS::PropertyID shorthand_id, CSS::CSSStyleValue const& shorthand_value) {
m_target_properties.set(shorthand_id);
resolved_keyframe.properties.set(shorthand_id, NonnullRefPtr<CSS::CSSStyleValue const> { shorthand_value });
dbgln("Resolving unresolved {} in set_keyframes(); got `{}`", CSS::string_from_property_id(property_id), property_value->to_string(CSS::CSSStyleValue::SerializationMode::Normal));
}
CSS::StyleComputer::for_each_property_expanding_shorthands(property_id, property_value, CSS::StyleComputer::AllowUnresolved::Yes, [&](CSS::PropertyID longhand_id, CSS::CSSStyleValue const& longhand_value) {
dbgln("expanding shorthands in set_keyframes");
VERIFY(!longhand_value.is_guaranteed_invalid());
VERIFY(!longhand_value.is_pending_substitution());
VERIFY(!longhand_value.is_unresolved());
m_target_properties.set(longhand_id);
resolved_keyframe.properties.set(longhand_id, NonnullRefPtr<CSS::CSSStyleValue const> { longhand_value });
});
}

View file

@ -38,6 +38,7 @@
#include <LibWeb/CSS/StyleValues/GridTemplateAreaStyleValue.h>
#include <LibWeb/CSS/StyleValues/GridTrackPlacementStyleValue.h>
#include <LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.h>
#include <LibWeb/CSS/StyleValues/GuaranteedInvalidStyleValue.h>
#include <LibWeb/CSS/StyleValues/ImageStyleValue.h>
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
@ -45,6 +46,7 @@
#include <LibWeb/CSS/StyleValues/MathDepthStyleValue.h>
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
#include <LibWeb/CSS/StyleValues/OpenTypeTaggedStyleValue.h>
#include <LibWeb/CSS/StyleValues/PendingSubstitutionStyleValue.h>
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
#include <LibWeb/CSS/StyleValues/RadialGradientStyleValue.h>
@ -226,6 +228,12 @@ GridTrackSizeListStyleValue const& CSSStyleValue::as_grid_track_size_list() cons
return static_cast<GridTrackSizeListStyleValue const&>(*this);
}
GuaranteedInvalidStyleValue const& CSSStyleValue::as_guaranteed_invalid() const
{
VERIFY(is_guaranteed_invalid());
return static_cast<GuaranteedInvalidStyleValue const&>(*this);
}
CSSKeywordValue const& CSSStyleValue::as_keyword() const
{
VERIFY(is_keyword());
@ -274,6 +282,12 @@ OpenTypeTaggedStyleValue const& CSSStyleValue::as_open_type_tagged() const
return static_cast<OpenTypeTaggedStyleValue const&>(*this);
}
PendingSubstitutionStyleValue const& CSSStyleValue::as_pending_substitution() const
{
VERIFY(is_pending_substitution());
return static_cast<PendingSubstitutionStyleValue const&>(*this);
}
PercentageStyleValue const& CSSStyleValue::as_percentage() const
{
VERIFY(is_percentage());

View file

@ -106,12 +106,12 @@ public:
FilterValueList,
FitContent,
Flex,
FontVariant,
Frequency,
GridAutoFlow,
GridTemplateArea,
GridTrackPlacement,
GridTrackSizeList,
GuaranteedInvalid,
Image,
Integer,
Keyword,
@ -120,6 +120,7 @@ public:
MathDepth,
Number,
OpenTypeTagged,
PendingSubstitution,
Percentage,
Position,
RadialGradient,
@ -248,6 +249,10 @@ public:
GridTrackSizeListStyleValue const& as_grid_track_size_list() const;
GridTrackSizeListStyleValue& as_grid_track_size_list() { return const_cast<GridTrackSizeListStyleValue&>(const_cast<CSSStyleValue const&>(*this).as_grid_track_size_list()); }
bool is_guaranteed_invalid() const { return type() == Type::GuaranteedInvalid; }
GuaranteedInvalidStyleValue const& as_guaranteed_invalid() const;
GuaranteedInvalidStyleValue& as_guaranteed_invalid() { return const_cast<GuaranteedInvalidStyleValue&>(const_cast<CSSStyleValue const&>(*this).as_guaranteed_invalid()); }
bool is_image() const { return type() == Type::Image; }
ImageStyleValue const& as_image() const;
ImageStyleValue& as_image() { return const_cast<ImageStyleValue&>(const_cast<CSSStyleValue const&>(*this).as_image()); }
@ -280,6 +285,10 @@ public:
OpenTypeTaggedStyleValue const& as_open_type_tagged() const;
OpenTypeTaggedStyleValue& as_open_type_tagged() { return const_cast<OpenTypeTaggedStyleValue&>(const_cast<CSSStyleValue const&>(*this).as_open_type_tagged()); }
bool is_pending_substitution() const { return type() == Type::PendingSubstitution; }
PendingSubstitutionStyleValue const& as_pending_substitution() const;
PendingSubstitutionStyleValue& as_pending_substitution() { return const_cast<PendingSubstitutionStyleValue&>(const_cast<CSSStyleValue const&>(*this).as_pending_substitution()); }
bool is_percentage() const { return type() == Type::Percentage; }
PercentageStyleValue const& as_percentage() const;
PercentageStyleValue& as_percentage() { return const_cast<PercentageStyleValue&>(const_cast<CSSStyleValue const&>(*this).as_percentage()); }

View file

@ -63,6 +63,7 @@ void CascadedProperties::resolve_unresolved_properties(GC::Ref<DOM::Element> ele
if (!entry.property.value->is_unresolved())
continue;
entry.property.value = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { element->document() }, element, pseudo_element, property_id, entry.property.value->as_unresolved());
dbgln("Resolving unresolved {} in resolve_unresolved_properties(); got `{}`", string_from_property_id(property_id), entry.property.value->to_string(CSSStyleValue::SerializationMode::Normal));
}
}
}

View file

@ -1200,6 +1200,7 @@ Optional<FlyString> ComputedProperties::font_language_override() const
Optional<Gfx::FontVariantAlternates> ComputedProperties::font_variant_alternates() const
{
auto const& value = property(PropertyID::FontVariantAlternates);
dbgln("font-variant-alternates is: {}", value.to_string(CSSStyleValue::SerializationMode::Normal));
switch (keyword_to_font_variant_alternates(value.to_keyword()).value()) {
case FontVariantAlternates::Normal:
return {};

View file

@ -43,14 +43,27 @@ static T interpolate_raw(T from, T to, float delta)
static NonnullRefPtr<CSSStyleValue const> with_keyword_values_resolved(DOM::Element& element, PropertyID property_id, CSSStyleValue const& value)
{
if (value.is_guaranteed_invalid()) {
// At the moment, we're only dealing with "real" properties, so this behaves the same as `unset`.
// https://drafts.csswg.org/css-values-5/#invalid-at-computed-value-time
return property_initial_value(property_id);
}
if (value.is_pending_substitution()) {
dbgln("OH NO got pensubval in with_keyword_values_resolved(). Property {}", string_from_property_id(property_id));
}
if (value.is_unresolved()) {
dbgln("OH NO got unresolved in with_keyword_values_resolved(). Property {}", string_from_property_id(property_id));
}
if (!value.is_keyword())
return value;
switch (value.as_keyword().keyword()) {
case CSS::Keyword::Initial:
case CSS::Keyword::Unset:
case Keyword::Initial:
case Keyword::Unset:
return property_initial_value(property_id);
case CSS::Keyword::Inherit:
return CSS::StyleComputer::get_inherit_value(property_id, &element);
case Keyword::Inherit:
return StyleComputer::get_inherit_value(property_id, &element);
default:
break;
}

View file

@ -42,6 +42,7 @@
#include <LibWeb/CSS/StyleValues/FrequencyStyleValue.h>
#include <LibWeb/CSS/StyleValues/GridTrackPlacementStyleValue.h>
#include <LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.h>
#include <LibWeb/CSS/StyleValues/GuaranteedInvalidStyleValue.h>
#include <LibWeb/CSS/StyleValues/ImageStyleValue.h>
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
@ -3637,8 +3638,6 @@ NonnullRefPtr<CSSStyleValue> Parser::resolve_unresolved_style_value(ParsingParam
// to produce a different CSSStyleValue from it.
VERIFY(unresolved.contains_var_or_attr());
// If the value is invalid, we fall back to `unset`: https://www.w3.org/TR/css-variables-1/#invalid-at-computed-value-time
auto parser = Parser::create(context, ""sv);
return parser.resolve_unresolved_style_value(element, pseudo_element, property_id, unresolved);
}
@ -3701,18 +3700,18 @@ NonnullRefPtr<CSSStyleValue> Parser::resolve_unresolved_style_value(DOM::Element
}
};
if (!expand_variables(element, pseudo_element, string_from_property_id(property_id), dependencies, unresolved_values_without_variables_expanded, values_with_variables_expanded))
return CSSKeywordValue::create(Keyword::Unset);
return GuaranteedInvalidStyleValue::create();
TokenStream unresolved_values_with_variables_expanded { values_with_variables_expanded };
Vector<ComponentValue> expanded_values;
if (!expand_unresolved_values(element, string_from_property_id(property_id), unresolved_values_with_variables_expanded, expanded_values))
return CSSKeywordValue::create(Keyword::Unset);
return GuaranteedInvalidStyleValue::create();
auto expanded_value_tokens = TokenStream { expanded_values };
if (auto parsed_value = parse_css_value(property_id, expanded_value_tokens); !parsed_value.is_error())
return parsed_value.release_value();
return CSSKeywordValue::create(Keyword::Unset);
return GuaranteedInvalidStyleValue::create();
}
static RefPtr<CSSStyleValue const> get_custom_property(DOM::Element const& element, Optional<CSS::PseudoElement> pseudo_element, FlyString const& custom_property_name)

View file

@ -60,6 +60,7 @@
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
#include <LibWeb/CSS/StyleValues/MathDepthStyleValue.h>
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
#include <LibWeb/CSS/StyleValues/PendingSubstitutionStyleValue.h>
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
#include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
@ -707,6 +708,24 @@ void StyleComputer::for_each_property_expanding_shorthands(PropertyID property_i
return;
}
if (property_is_shorthand(property_id) && (value.is_unresolved() || value.is_pending_substitution())) {
// If a shorthand property contains an arbitrary substitution function in its value, the longhand properties
// its associated with must instead be filled in with a special, unobservable-to-authors pending-substitution
// value that indicates the shorthand contains an arbitrary substitution function, and thus the longhands
// value cant be determined until after substituted.
// https://drafts.csswg.org/css-values-5/#pending-substitution-value
dbgln("Assigning pensubval for longhands of {}", string_from_property_id(property_id));
auto pending_substitution_value = PendingSubstitutionStyleValue::create();
for (auto longhand_id : longhands_for_shorthand(property_id)) {
for_each_property_expanding_shorthands(longhand_id, pending_substitution_value, allow_unresolved, set_longhand_property);
}
return;
}
if (value.is_guaranteed_invalid()) {
dbgln("{} is guaranteed-invalid! We should probably do something.", string_from_property_id(property_id));
}
auto assign_edge_values = [&](PropertyID top_property, PropertyID right_property, PropertyID bottom_property, PropertyID left_property, auto const& values) {
if (values.size() == 4) {
set_longhand_property(top_property, values[0]);
@ -948,12 +967,12 @@ void StyleComputer::for_each_property_expanding_shorthands(PropertyID property_i
}
if (property_is_shorthand(property_id)) {
// ShorthandStyleValue was handled already.
// That means if we got here, that `value` must be a CSS-wide keyword, which we should apply to our longhand properties.
// ShorthandStyleValue was handled already, as were unresolved shorthands.
// That means the only values we should see are the CSS-wide keywords, or the guaranteed-invalid value.
// Both should be applied to our longhand properties.
// We don't directly call `set_longhand_property()` because the longhands might have longhands of their own.
// (eg `grid` -> `grid-template` -> `grid-template-areas` & `grid-template-rows` & `grid-template-columns`)
// Forget this requirement if we're ignoring unresolved values and the value is unresolved.
VERIFY(value.is_css_wide_keyword() || (allow_unresolved == AllowUnresolved::Yes && value.is_unresolved()));
VERIFY(value.is_css_wide_keyword() || value.is_guaranteed_invalid());
for (auto longhand : longhands_for_shorthand(property_id))
for_each_property_expanding_shorthands(longhand, value, allow_unresolved, set_longhand_property);
return;
@ -1007,12 +1026,11 @@ void StyleComputer::set_all_properties(
}
NonnullRefPtr<CSSStyleValue> property_value = value;
if (property_value->is_unresolved())
if (property_value->is_unresolved()) {
property_value = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { document }, element, pseudo_element, property_id, property_value->as_unresolved());
if (!property_value->is_unresolved())
set_property_expanding_shorthands(cascaded_properties, property_id, property_value, declaration, cascade_origin, important, layer_name);
set_property_expanding_shorthands(cascaded_properties, property_id, value, declaration, cascade_origin, important, layer_name);
dbgln("Resolving unresolved {} in set_all_properties(); got `{}`", string_from_property_id(property_id), property_value->to_string(CSSStyleValue::SerializationMode::Normal));
}
set_property_expanding_shorthands(cascaded_properties, property_id, property_value, declaration, cascade_origin, important, layer_name);
}
}
@ -1025,44 +1043,60 @@ void StyleComputer::cascade_declarations(
Important important,
Optional<FlyString> layer_name) const
{
for (auto const& match : matching_rules) {
for (auto const& property : match->declaration().properties()) {
auto cascade_style_declaration = [&](CSSStyleProperties const& declaration) {
for (auto const& property : declaration.properties()) {
dbgln("Cascading {}", string_from_property_id(property.property_id));
dbgln("-> value: `{}`, psv? {}, giv? {}", property.value->to_string(CSSStyleValue::SerializationMode::Normal), property.value->is_pending_substitution(), property.value->is_guaranteed_invalid());
if (important != property.important)
continue;
if (pseudo_element.has_value() && !pseudo_element_supports_property(*pseudo_element, property.property_id))
continue;
if (property.property_id == CSS::PropertyID::All) {
set_all_properties(cascaded_properties, element, pseudo_element, property.value, m_document, &match->declaration(), cascade_origin, important, layer_name);
auto property_value = property.value;
if (property_value->is_unresolved()) {
property_value = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { element.document() }, element, pseudo_element, property.property_id, property_value->as_unresolved());
dbgln("Resolving unresolved {} in cascade_declarations(); got `{}`", string_from_property_id(property.property_id), property_value->to_string(CSSStyleValue::SerializationMode::Normal));
}
if (property_value->is_guaranteed_invalid()) {
dbgln("Resolving guaranteed-invalid {} in cascade_declarations()", string_from_property_id(property.property_id));
// https://drafts.csswg.org/css-values-5/#invalid-at-computed-value-time
// When substitution results in a propertys value containing the guaranteed-invalid value, this makes the
// declaration invalid at computed-value time. When this happens, the computed value is one of the
// following depending on the propertys type:
// -> The property is a non-registered custom property
// -> The property is a registered custom property with universal syntax
// FIXME: Process custom properties here?
if (false) {
// The computed value is the guaranteed-invalid value.
}
// -> Otherwise
else {
// Either the propertys inherited value or its initial value depending on whether the property is
// inherited or not, respectively, as if the propertys value had been specified as the unset keyword.
property_value = CSSKeywordValue::create(Keyword::Unset);
}
}
if (property.property_id == PropertyID::All) {
set_all_properties(cascaded_properties, element, pseudo_element, property_value, m_document, &declaration, cascade_origin, important, layer_name);
continue;
}
auto property_value = property.value;
if (property.value->is_unresolved())
property_value = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { document() }, element, pseudo_element, property.property_id, property.value->as_unresolved());
if (!property_value->is_unresolved())
set_property_expanding_shorthands(cascaded_properties, property.property_id, property_value, &match->declaration(), cascade_origin, important, layer_name);
set_property_expanding_shorthands(cascaded_properties, property.property_id, property_value, &declaration, cascade_origin, important, layer_name);
}
};
for (auto const& match : matching_rules) {
cascade_style_declaration(match->declaration());
}
if (cascade_origin == CascadeOrigin::Author && !pseudo_element.has_value()) {
if (auto const inline_style = element.inline_style()) {
for (auto const& property : inline_style->properties()) {
if (important != property.important)
continue;
if (property.property_id == CSS::PropertyID::All) {
set_all_properties(cascaded_properties, element, pseudo_element, property.value, m_document, inline_style, cascade_origin, important, layer_name);
continue;
}
auto property_value = property.value;
if (property.value->is_unresolved())
property_value = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { document() }, element, pseudo_element, property.property_id, property.value->as_unresolved());
if (!property_value->is_unresolved())
set_property_expanding_shorthands(cascaded_properties, property.property_id, property_value, inline_style, cascade_origin, important, layer_name);
}
cascade_style_declaration(*inline_style);
}
}
}
@ -1147,6 +1181,24 @@ void StyleComputer::collect_animation_into(DOM::Element& element, Optional<CSS::
dbgln("Animation {} contains {} properties to interpolate, progress = {}%", animation->id(), valid_properties, progress_in_keyframe * 100);
}
// FIXME: I think I need to expand out any unresolved shorthands here somehow.
// See: https://drafts.csswg.org/web-animations-1/#ref-for-computed-keyframes
for (auto const& [property_id, value] : keyframe_values.properties) {
value.visit(
[&](RefPtr<CSSStyleValue const> value) -> RefPtr<CSSStyleValue const> {
if (value->is_revert() || value->is_revert_layer())
return computed_properties.property(property_id);
if (value->is_unresolved()) {
auto property_value = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { element.document() }, element, pseudo_element, property_id, value->as_unresolved());
dbgln("Resolving unresolved {} in collect_animation_into(); got `{}`", string_from_property_id(property_id), property_value->to_string(CSSStyleValue::SerializationMode::Normal));
return property_value;
}
return value;
},
[](auto const&) {});
}
for (auto const& it : keyframe_values.properties) {
auto resolve_property = [&](auto& property) {
return property.visit(
@ -1158,8 +1210,12 @@ void StyleComputer::collect_animation_into(DOM::Element& element, Optional<CSS::
[&](RefPtr<CSSStyleValue const> value) -> RefPtr<CSSStyleValue const> {
if (value->is_revert() || value->is_revert_layer())
return computed_properties.property(it.key);
if (value->is_unresolved())
return Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { element.document() }, element, pseudo_element, it.key, value->as_unresolved());
if (value->is_unresolved()) {
auto property_value = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { element.document() }, element, pseudo_element, it.key, value->as_unresolved());
dbgln("Resolving unresolved {} in collect_animation_into(); got `{}`", string_from_property_id(it.key), property_value->to_string(CSSStyleValue::SerializationMode::Normal));
return property_value;
}
return value;
});
};
@ -1680,8 +1736,6 @@ NonnullRefPtr<CSSStyleValue const> StyleComputer::get_inherit_value(CSS::Propert
void StyleComputer::compute_defaulted_property_value(ComputedProperties& style, DOM::Element const* element, CSS::PropertyID property_id, Optional<CSS::PseudoElement> pseudo_element) const
{
// FIXME: If we don't know the correct initial value for a property, we fall back to `initial`.
auto& value_slot = style.m_property_values[to_underlying(property_id)];
if (!value_slot) {
if (is_inherited_property(property_id)) {

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/CSS/CSSStyleValue.h>
namespace Web::CSS {
// https://drafts.csswg.org/css-variables/#guaranteed-invalid-value
class GuaranteedInvalidStyleValue final : public StyleValueWithDefaultOperators<GuaranteedInvalidStyleValue> {
public:
static ValueComparingNonnullRefPtr<GuaranteedInvalidStyleValue> create()
{
static ValueComparingNonnullRefPtr<GuaranteedInvalidStyleValue> instance = adopt_ref(*new (nothrow) GuaranteedInvalidStyleValue());
return instance;
}
virtual ~GuaranteedInvalidStyleValue() override = default;
virtual String to_string(SerializationMode) const override { return {}; }
bool properties_equal(GuaranteedInvalidStyleValue const&) const { return true; }
private:
GuaranteedInvalidStyleValue()
: StyleValueWithDefaultOperators(Type::GuaranteedInvalid)
{
}
};
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/CSS/CSSStyleValue.h>
namespace Web::CSS {
// https://drafts.csswg.org/css-values-5/#pending-substitution-value
class PendingSubstitutionStyleValue final : public StyleValueWithDefaultOperators<PendingSubstitutionStyleValue> {
public:
static ValueComparingNonnullRefPtr<PendingSubstitutionStyleValue> create()
{
static ValueComparingNonnullRefPtr<PendingSubstitutionStyleValue> instance = adopt_ref(*new (nothrow) PendingSubstitutionStyleValue());
return instance;
}
virtual ~PendingSubstitutionStyleValue() override = default;
virtual String to_string(SerializationMode) const override { return {}; }
// We shouldn't need to compare these, but in case we do: The nature of them is that their value is unknown, so
// consider them all to be unique.
bool properties_equal(PendingSubstitutionStyleValue const&) const { return false; }
private:
PendingSubstitutionStyleValue()
: StyleValueWithDefaultOperators(Type::PendingSubstitution)
{
}
};
}

View file

@ -212,6 +212,7 @@ class GridTrackPlacement;
class GridTrackPlacementStyleValue;
class GridTrackSizeList;
class GridTrackSizeListStyleValue;
class GuaranteedInvalidStyleValue;
class ImageStyleValue;
class IntegerOrCalculated;
class IntegerStyleValue;
@ -232,6 +233,7 @@ class NumberOrCalculated;
class NumberStyleValue;
class OpenTypeTaggedStyleValue;
class ParsedFontFace;
class PendingSubstitutionStyleValue;
class Percentage;
class PercentageOrCalculated;
class PercentageStyleValue;