LibWeb: Absolutize StyleValues before computing font properties

We also avoid prematurely constructing CSSPixels when computing
font-size which gains us a couple of test passes
This commit is contained in:
Callum Law 2025-10-03 23:16:30 +13:00 committed by Tim Ledbetter
parent ca9d107a1a
commit 28451b16c9
Notes: github-actions[bot] 2025-10-20 15:14:19 +00:00
5 changed files with 53 additions and 45 deletions

View file

@ -18,7 +18,7 @@ struct CalculationResolutionContext {
using PercentageBasis = Variant<Empty, Angle, Frequency, Length, Time>; using PercentageBasis = Variant<Empty, Angle, Frequency, Length, Time>;
PercentageBasis percentage_basis {}; PercentageBasis percentage_basis {};
Optional<Length::ResolutionContext> length_resolution_context; Optional<Length::ResolutionContext> length_resolution_context {};
static CalculationResolutionContext from_computation_context(ComputationContext const& computation_context, PercentageBasis percentage_basis = {}) static CalculationResolutionContext from_computation_context(ComputationContext const& computation_context, PercentageBasis percentage_basis = {})
{ {

View file

@ -3381,29 +3381,31 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_font_size(NonnullRefPtr<S
// https://drafts.csswg.org/css-fonts/#font-size-prop // https://drafts.csswg.org/css-fonts/#font-size-prop
// an absolute length // an absolute length
auto const& absolutized_value = specified_value->absolutized(computation_context);
// <absolute-size> // <absolute-size>
if (auto absolute_size = keyword_to_absolute_size(specified_value->to_keyword()); absolute_size.has_value()) if (auto absolute_size = keyword_to_absolute_size(absolutized_value->to_keyword()); absolute_size.has_value())
return LengthStyleValue::create(Length::make_px(absolute_size_mapping(absolute_size.value(), default_user_font_size()))); return LengthStyleValue::create(Length::make_px(absolute_size_mapping(absolute_size.value(), default_user_font_size())));
// <relative-size> // <relative-size>
if (auto relative_size = keyword_to_relative_size(specified_value->to_keyword()); relative_size.has_value()) if (auto relative_size = keyword_to_relative_size(absolutized_value->to_keyword()); relative_size.has_value())
return LengthStyleValue::create(Length::make_px(relative_size_mapping(relative_size.value(), inherited_font_size))); return LengthStyleValue::create(Length::make_px(relative_size_mapping(relative_size.value(), inherited_font_size)));
// <length-percentage [0,∞]> // <length-percentage [0,∞]>
// A length value specifies an absolute font size (independent of the user agents font table). Negative lengths are invalid. // A length value specifies an absolute font size (independent of the user agents font table). Negative lengths are invalid.
if (specified_value->is_length()) if (absolutized_value->is_length())
return LengthStyleValue::create(Length::make_px(max(CSSPixels { 0 }, specified_value->as_length().length().to_px(computation_context.length_resolution_context)))); return absolutized_value;
// A percentage value specifies an absolute font size relative to the parent elements computed font-size. Negative percentages are invalid. // A percentage value specifies an absolute font size relative to the parent elements computed font-size. Negative percentages are invalid.
if (specified_value->is_percentage()) if (absolutized_value->is_percentage())
return LengthStyleValue::create(Length::make_px(inherited_font_size * specified_value->as_percentage().percentage().as_fraction())); return LengthStyleValue::create(Length::make_px(inherited_font_size * absolutized_value->as_percentage().percentage().as_fraction()));
if (specified_value->is_calculated()) if (absolutized_value->is_calculated())
return LengthStyleValue::create(specified_value->as_calculated().resolve_length(CalculationResolutionContext::from_computation_context(computation_context, Length(1, LengthUnit::Em))).value()); return LengthStyleValue::create(absolutized_value->as_calculated().resolve_length({ .percentage_basis = Length::make_px(inherited_font_size) }).value());
// math // math
// Special mathematical scaling rules must be applied when determining the computed value of the font-size property. // Special mathematical scaling rules must be applied when determining the computed value of the font-size property.
if (specified_value->to_keyword() == Keyword::Math) { if (absolutized_value->to_keyword() == Keyword::Math) {
auto math_scaling_factor = [&]() { auto math_scaling_factor = [&]() {
// https://w3c.github.io/mathml-core/#the-math-script-level-property // https://w3c.github.io/mathml-core/#the-math-script-level-property
// If the specified value font-size is math then the computed value of font-size is obtained by multiplying // If the specified value font-size is math then the computed value of font-size is obtained by multiplying
@ -3439,7 +3441,7 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_font_size(NonnullRefPtr<S
return 1.0 / scale; return 1.0 / scale;
}(); }();
return LengthStyleValue::create(Length::make_px(inherited_font_size.scale_by(math_scaling_factor))); return LengthStyleValue::create(Length::make_px(inherited_font_size.scaled(math_scaling_factor)));
} }
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
@ -3473,22 +3475,24 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_font_weight(NonnullRefPtr
// https://drafts.csswg.org/css-fonts-4/#font-weight-prop // https://drafts.csswg.org/css-fonts-4/#font-weight-prop
// a number, see below // a number, see below
auto const& absolutized_value = specified_value->absolutized(computation_context);
// <number [1,1000]> // <number [1,1000]>
if (specified_value->is_number()) if (absolutized_value->is_number())
return specified_value; return absolutized_value;
// AD-HOC: Anywhere we support a numbers we should also support calcs // AD-HOC: Anywhere we support a numbers we should also support calcs
if (specified_value->is_calculated()) if (absolutized_value->is_calculated())
return NumberStyleValue::create(specified_value->as_calculated().resolve_number(CalculationResolutionContext::from_computation_context(computation_context)).value()); return NumberStyleValue::create(absolutized_value->as_calculated().resolve_number({}).value());
// normal // normal
// Same as 400. // Same as 400.
if (specified_value->to_keyword() == Keyword::Normal) if (absolutized_value->to_keyword() == Keyword::Normal)
return NumberStyleValue::create(400); return NumberStyleValue::create(400);
// bold // bold
// Same as 700. // Same as 700.
if (specified_value->to_keyword() == Keyword::Bold) if (absolutized_value->to_keyword() == Keyword::Bold)
return NumberStyleValue::create(700); return NumberStyleValue::create(700);
// Specified values of bolder and lighter indicate weights relative to the weight of the parent element. The // Specified values of bolder and lighter indicate weights relative to the weight of the parent element. The
@ -3504,7 +3508,7 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_font_weight(NonnullRefPtr
// bolder // bolder
// Specifies a bolder weight than the inherited value. See §2.2.1 Relative Weights. // Specifies a bolder weight than the inherited value. See §2.2.1 Relative Weights.
if (specified_value->to_keyword() == Keyword::Bolder) { if (absolutized_value->to_keyword() == Keyword::Bolder) {
if (inherited_font_weight < 350) if (inherited_font_weight < 350)
return NumberStyleValue::create(400); return NumberStyleValue::create(400);
@ -3519,7 +3523,7 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_font_weight(NonnullRefPtr
// lighter // lighter
// Specifies a lighter weight than the inherited value. See §2.2.1 Relative Weights. // Specifies a lighter weight than the inherited value. See §2.2.1 Relative Weights.
if (specified_value->to_keyword() == Keyword::Lighter) { if (absolutized_value->to_keyword() == Keyword::Lighter) {
if (inherited_font_weight < 100) if (inherited_font_weight < 100)
return NumberStyleValue::create(inherited_font_weight); return NumberStyleValue::create(inherited_font_weight);
@ -3540,15 +3544,17 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_font_width(NonnullRefPtr<
// https://drafts.csswg.org/css-fonts-4/#font-width-prop // https://drafts.csswg.org/css-fonts-4/#font-width-prop
// a percentage, see below // a percentage, see below
auto absolutized_value = specified_value->absolutized(computation_context);
// <percentage [0,∞]> // <percentage [0,∞]>
if (specified_value->is_percentage()) if (absolutized_value->is_percentage())
return specified_value; return absolutized_value;
// AD-HOC: We support calculated percentages as well // AD-HOC: We support calculated percentages as well
if (specified_value->is_calculated()) if (absolutized_value->is_calculated())
return PercentageStyleValue::create(specified_value->as_calculated().resolve_percentage(CalculationResolutionContext::from_computation_context(computation_context)).value()); return PercentageStyleValue::create(absolutized_value->as_calculated().resolve_percentage({}).value());
switch (specified_value->to_keyword()) { switch (absolutized_value->to_keyword()) {
// ultra-condensed 50% // ultra-condensed 50%
case Keyword::UltraCondensed: case Keyword::UltraCondensed:
return PercentageStyleValue::create(Percentage(50)); return PercentageStyleValue::create(Percentage(50));
@ -3584,29 +3590,26 @@ NonnullRefPtr<StyleValue const> StyleComputer::compute_font_width(NonnullRefPtr<
NonnullRefPtr<StyleValue const> StyleComputer::compute_line_height(NonnullRefPtr<StyleValue const> const& specified_value, ComputationContext const& computation_context) NonnullRefPtr<StyleValue const> StyleComputer::compute_line_height(NonnullRefPtr<StyleValue const> const& specified_value, ComputationContext const& computation_context)
{ {
// https://drafts.csswg.org/css-inline-3/#line-height-property // https://drafts.csswg.org/css-inline-3/#line-height-property
// normal
if (specified_value->to_keyword() == Keyword::Normal)
return specified_value;
auto absolutized_value = specified_value->absolutized(computation_context);
// normal
// <length [0,∞]> // <length [0,∞]>
if (specified_value->is_length()) // <number [0,∞]>
return specified_value->absolutized(computation_context); if (absolutized_value->to_keyword() == Keyword::Normal || absolutized_value->is_length() || absolutized_value->is_number())
return absolutized_value;
// NOTE: We also support calc()'d lengths (percentages resolve to lengths so we don't have to handle them separately) // NOTE: We also support calc()'d lengths (percentages resolve to lengths so we don't have to handle them separately)
if (specified_value->is_calculated() && specified_value->as_calculated().resolves_to_length_percentage()) if (absolutized_value->is_calculated() && absolutized_value->as_calculated().resolves_to_length_percentage())
return LengthStyleValue::create(specified_value->as_calculated().resolve_length(CalculationResolutionContext::from_computation_context(computation_context, Length(1, LengthUnit::Em))).value()); return LengthStyleValue::create(absolutized_value->as_calculated().resolve_length({ .percentage_basis = Length::make_px(computation_context.length_resolution_context.font_metrics.font_size) }).value());
// <number [0,∞]>
if (specified_value->is_number())
return specified_value;
// NOTE: We also support calc()'d numbers // NOTE: We also support calc()'d numbers
if (specified_value->is_calculated() && specified_value->as_calculated().resolves_to_number()) if (absolutized_value->is_calculated() && absolutized_value->as_calculated().resolves_to_number())
return NumberStyleValue::create(specified_value->as_calculated().resolve_number(CalculationResolutionContext::from_computation_context(computation_context, Length(1, LengthUnit::Em))).value()); return NumberStyleValue::create(absolutized_value->as_calculated().resolve_number({ .percentage_basis = Length::make_px(computation_context.length_resolution_context.font_metrics.font_size) }).value());
// <percentage [0,∞]> // <percentage [0,∞]>
if (specified_value->is_percentage()) if (absolutized_value->is_percentage())
return LengthStyleValue::create(Length::make_px(computation_context.length_resolution_context.font_metrics.font_size * specified_value->as_percentage().percentage().as_fraction())); return LengthStyleValue::create(Length::make_px(computation_context.length_resolution_context.font_metrics.font_size * absolutized_value->as_percentage().percentage().as_fraction()));
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }

View file

@ -127,6 +127,10 @@ private:
double value; double value;
Optional<NumericType> type; Optional<NumericType> type;
}; };
// FIXME: Calculations should be simplified apart from percentages by the absolutized method prior to this method
// being called so we can take just the percentage_basis rather than a full CalculationResolutionContext.
// There are still some CalculatedStyleValues which we don't call absolutized for (i.e. sub-values of other
// StyleValue classes which lack their own absolutized method) which will need to be fixed beforehand.
Optional<ResolvedValue> resolve_value(CalculationResolutionContext const&) const; Optional<ResolvedValue> resolve_value(CalculationResolutionContext const&) const;
Optional<ValueType> percentage_resolved_type() const; Optional<ValueType> percentage_resolved_type() const;

View file

@ -112,6 +112,8 @@ public:
// NOTE: The initial value here is non-standard as the default font is "10px sans-serif" // NOTE: The initial value here is non-standard as the default font is "10px sans-serif"
auto inherited_font_size = CSSPixels { 10 }; auto inherited_font_size = CSSPixels { 10 };
auto inherited_font_weight = CSS::InitialValues::font_weight(); auto inherited_font_weight = CSS::InitialValues::font_weight();
// FIXME: Investigate whether this is the correct resolution context (i.e. whether we should instead use
// a font-size of 10px) for OffscreenCanvas
auto length_resolution_context = CSS::Length::ResolutionContext::for_window(*document->window()); auto length_resolution_context = CSS::Length::ResolutionContext::for_window(*document->window());
if constexpr (SameAs<CanvasType, HTML::HTMLCanvasElement>) { if constexpr (SameAs<CanvasType, HTML::HTMLCanvasElement>) {

View file

@ -2,8 +2,7 @@ Harness status: OK
Found 140 tests Found 140 tests
136 Pass 140 Pass
4 Fail
Pass CSS Transitions: property <font-size> from neutral to [20px] at (-2) should be [0px] Pass CSS Transitions: property <font-size> from neutral to [20px] at (-2) should be [0px]
Pass CSS Transitions: property <font-size> from neutral to [20px] at (-0.3) should be [7px] Pass CSS Transitions: property <font-size> from neutral to [20px] at (-0.3) should be [7px]
Pass CSS Transitions: property <font-size> from neutral to [20px] at (0) should be [10px] Pass CSS Transitions: property <font-size> from neutral to [20px] at (0) should be [10px]
@ -36,28 +35,28 @@ Pass CSS Transitions: property <font-size> from [initial] to [20px] at (-2) shou
Pass CSS Transitions: property <font-size> from [initial] to [20px] at (-0.3) should be [14.8px] Pass CSS Transitions: property <font-size> from [initial] to [20px] at (-0.3) should be [14.8px]
Pass CSS Transitions: property <font-size> from [initial] to [20px] at (0) should be [16px] Pass CSS Transitions: property <font-size> from [initial] to [20px] at (0) should be [16px]
Pass CSS Transitions: property <font-size> from [initial] to [20px] at (0.3) should be [17.2px] Pass CSS Transitions: property <font-size> from [initial] to [20px] at (0.3) should be [17.2px]
Fail CSS Transitions: property <font-size> from [initial] to [20px] at (0.6) should be [18.4px] Pass CSS Transitions: property <font-size> from [initial] to [20px] at (0.6) should be [18.4px]
Pass CSS Transitions: property <font-size> from [initial] to [20px] at (1) should be [20px] Pass CSS Transitions: property <font-size> from [initial] to [20px] at (1) should be [20px]
Pass CSS Transitions: property <font-size> from [initial] to [20px] at (1.5) should be [22px] Pass CSS Transitions: property <font-size> from [initial] to [20px] at (1.5) should be [22px]
Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (-2) should be [8px] Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (-2) should be [8px]
Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (-0.3) should be [14.8px] Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (-0.3) should be [14.8px]
Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (0) should be [16px] Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (0) should be [16px]
Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (0.3) should be [17.2px] Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (0.3) should be [17.2px]
Fail CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (0.6) should be [18.4px] Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (0.6) should be [18.4px]
Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (1) should be [20px] Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (1) should be [20px]
Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (1.5) should be [22px] Pass CSS Transitions with transition: all: property <font-size> from [initial] to [20px] at (1.5) should be [22px]
Pass CSS Animations: property <font-size> from [initial] to [20px] at (-2) should be [8px] Pass CSS Animations: property <font-size> from [initial] to [20px] at (-2) should be [8px]
Pass CSS Animations: property <font-size> from [initial] to [20px] at (-0.3) should be [14.8px] Pass CSS Animations: property <font-size> from [initial] to [20px] at (-0.3) should be [14.8px]
Pass CSS Animations: property <font-size> from [initial] to [20px] at (0) should be [16px] Pass CSS Animations: property <font-size> from [initial] to [20px] at (0) should be [16px]
Pass CSS Animations: property <font-size> from [initial] to [20px] at (0.3) should be [17.2px] Pass CSS Animations: property <font-size> from [initial] to [20px] at (0.3) should be [17.2px]
Fail CSS Animations: property <font-size> from [initial] to [20px] at (0.6) should be [18.4px] Pass CSS Animations: property <font-size> from [initial] to [20px] at (0.6) should be [18.4px]
Pass CSS Animations: property <font-size> from [initial] to [20px] at (1) should be [20px] Pass CSS Animations: property <font-size> from [initial] to [20px] at (1) should be [20px]
Pass CSS Animations: property <font-size> from [initial] to [20px] at (1.5) should be [22px] Pass CSS Animations: property <font-size> from [initial] to [20px] at (1.5) should be [22px]
Pass Web Animations: property <font-size> from [initial] to [20px] at (-2) should be [8px] Pass Web Animations: property <font-size> from [initial] to [20px] at (-2) should be [8px]
Pass Web Animations: property <font-size> from [initial] to [20px] at (-0.3) should be [14.8px] Pass Web Animations: property <font-size> from [initial] to [20px] at (-0.3) should be [14.8px]
Pass Web Animations: property <font-size> from [initial] to [20px] at (0) should be [16px] Pass Web Animations: property <font-size> from [initial] to [20px] at (0) should be [16px]
Pass Web Animations: property <font-size> from [initial] to [20px] at (0.3) should be [17.2px] Pass Web Animations: property <font-size> from [initial] to [20px] at (0.3) should be [17.2px]
Fail Web Animations: property <font-size> from [initial] to [20px] at (0.6) should be [18.4px] Pass Web Animations: property <font-size> from [initial] to [20px] at (0.6) should be [18.4px]
Pass Web Animations: property <font-size> from [initial] to [20px] at (1) should be [20px] Pass Web Animations: property <font-size> from [initial] to [20px] at (1) should be [20px]
Pass Web Animations: property <font-size> from [initial] to [20px] at (1.5) should be [22px] Pass Web Animations: property <font-size> from [initial] to [20px] at (1.5) should be [22px]
Pass CSS Transitions: property <font-size> from [inherit] to [20px] at (-2) should be [50px] Pass CSS Transitions: property <font-size> from [inherit] to [20px] at (-2) should be [50px]