LibWeb: Clamp and censor top-level calc results

We now clamp the values returned from calc into the allowed range (where
we know it) and censor any `NaN`s to `0` both when we resolve and when
we serialize.

Gains us 76 WPT passes.
This commit is contained in:
Callum Law 2025-08-06 16:29:12 +12:00 committed by Sam Atkins
commit 778da0175e
Notes: github-actions[bot] 2025-08-11 16:11:10 +00:00
15 changed files with 361 additions and 74 deletions

View file

@ -130,6 +130,66 @@ static NonnullRefPtr<CalculationNode const> simplify_2_children(T const& origina
return original;
}
static CalculationNode::NumericValue clamp_and_censor_numeric_value(NumericCalculationNode const& node, CalculationContext const& context)
{
auto value = node.value();
Optional<AcceptedTypeRange> accepted_range = value.visit(
[&](Number const&) { return context.resolve_numbers_as_integers ? context.accepted_type_ranges.get(ValueType::Integer) : context.accepted_type_ranges.get(ValueType::Number); },
[&](Angle const&) { return context.accepted_type_ranges.get(ValueType::Angle); },
[&](Flex const&) { return context.accepted_type_ranges.get(ValueType::Flex); },
[&](Frequency const&) { return context.accepted_type_ranges.get(ValueType::Frequency); },
[&](Length const&) { return context.accepted_type_ranges.get(ValueType::Length); },
[&](Percentage const&) { return context.accepted_type_ranges.get(ValueType::Percentage); },
[&](Resolution const&) { return context.accepted_type_ranges.get(ValueType::Resolution); },
[&](Time const&) { return context.accepted_type_ranges.get(ValueType::Time); });
if (!accepted_range.has_value()) {
dbgln_if(LIBWEB_CSS_DEBUG, "FIXME: Calculation context missing accepted type range {}", node.numeric_type());
// FIXME: Min and max values for Integer should be based on i32 rather than float
accepted_range = { AK::NumericLimits<float>::lowest(), AK::NumericLimits<float>::max() };
}
auto clamp_and_censor = [&](double value, double min, double max) {
// https://drafts.csswg.org/css-values/#calc-ieee
// NaN does not escape a top-level calculation; its censored into a zero value.
if (isnan(value))
value = 0;
// https://drafts.csswg.org/css-values/#calc-range
// the value resulting from a top-level calculation must be clamped to the range allowed in the target context.
// Clamping is performed on computed values to the extent possible, and also on used values if computation was
// unable to sufficiently simplify the expression to allow range-checking.
return clamp(value, min, max);
};
return value.visit(
[&](Number const& value) -> CalculationNode::NumericValue {
return Number { value.type(), clamp_and_censor(context.resolve_numbers_as_integers ? value.integer_value() : value.value(), accepted_range->min, accepted_range->max) };
},
[&](Angle const& value) -> CalculationNode::NumericValue {
return Angle { clamp_and_censor(value.raw_value(), accepted_range->min, accepted_range->max), value.type() };
},
[&](Flex const& value) -> CalculationNode::NumericValue {
return Flex { clamp_and_censor(value.raw_value(), accepted_range->min, accepted_range->max), value.type() };
},
[&](Frequency const& value) -> CalculationNode::NumericValue {
return Frequency { clamp_and_censor(value.raw_value(), accepted_range->min, accepted_range->max), value.type() };
},
[&](Length const& value) -> CalculationNode::NumericValue {
return Length { clamp_and_censor(value.raw_value(), accepted_range->min, accepted_range->max), value.type() };
},
[&](Percentage const& value) -> CalculationNode::NumericValue {
return Percentage { clamp_and_censor(value.value(), accepted_range->min, accepted_range->max) };
},
[&](Resolution const& value) -> CalculationNode::NumericValue {
return Resolution { clamp_and_censor(value.raw_value(), accepted_range->min, accepted_range->max), value.type() };
},
[&](Time const& value) -> CalculationNode::NumericValue {
return Time { clamp_and_censor(value.raw_value(), accepted_range->min, accepted_range->max), value.type() };
});
}
static String serialize_a_calculation_tree(CalculationNode const&, CalculationContext const&, SerializationMode);
// https://drafts.csswg.org/css-values-4/#serialize-a-math-function
@ -141,8 +201,9 @@ static String serialize_a_math_function(CalculationNode const& fn, CalculationCo
// the serialization being produced is of a computed value or later, then clamp the value to the range allowed
// for its context (if necessary), then serialize the value as normal and return the result.
if (fn.type() == CalculationNode::Type::Numeric && serialization_mode == SerializationMode::ResolvedValue) {
// FIXME: Clamp the value. Note that we might have an infinite/nan value here.
return static_cast<NumericCalculationNode const&>(fn).value_to_string();
auto clamped_value = clamp_and_censor_numeric_value(static_cast<NumericCalculationNode const&>(fn), context);
return clamped_value.visit([&](auto const& value) { return value.to_string(serialization_mode); });
}
// 2. If fn represents an infinite or NaN value:
@ -2757,12 +2818,40 @@ Optional<CalculatedStyleValue::ResolvedValue> CalculatedStyleValue::resolve_valu
auto raw_value = value->value();
// https://drafts.csswg.org/css-values/#calc-ieee
// FIXME: NaN does not escape a top-level calculation; its censored into a zero value.
// NaN does not escape a top-level calculation; its censored into a zero value.
if (isnan(raw_value))
raw_value = 0;
// https://drafts.csswg.org/css-values/#calc-range
// FIXME: the value resulting from a top-level calculation must be clamped to the range allowed in the target
// context. Clamping is performed on computed values to the extent possible, and also on used values if
// computation was unable to sufficiently simplify the expression to allow range-checking.
// the value resulting from a top-level calculation must be clamped to the range allowed in the target context.
// Clamping is performed on computed values to the extent possible, and also on used values if computation was
// unable to sufficiently simplify the expression to allow range-checking.
Optional<AcceptedTypeRange> accepted_range;
if (value->type()->matches_number(m_context.percentages_resolve_as))
accepted_range = m_context.resolve_numbers_as_integers ? m_context.accepted_type_ranges.get(ValueType::Integer) : m_context.accepted_type_ranges.get(ValueType::Number);
else if (value->type()->matches_angle_percentage(m_context.percentages_resolve_as))
accepted_range = m_context.accepted_type_ranges.get(ValueType::Angle);
else if (value->type()->matches_flex(m_context.percentages_resolve_as))
accepted_range = m_context.accepted_type_ranges.get(ValueType::Flex);
else if (value->type()->matches_frequency_percentage(m_context.percentages_resolve_as))
accepted_range = m_context.accepted_type_ranges.get(ValueType::Frequency);
else if (value->type()->matches_length_percentage(m_context.percentages_resolve_as))
accepted_range = m_context.accepted_type_ranges.get(ValueType::Length);
else if (value->type()->matches_percentage())
accepted_range = m_context.accepted_type_ranges.get(ValueType::Percentage);
else if (value->type()->matches_resolution(m_context.percentages_resolve_as))
accepted_range = m_context.accepted_type_ranges.get(ValueType::Resolution);
else if (value->type()->matches_time_percentage(m_context.percentages_resolve_as))
accepted_range = m_context.accepted_type_ranges.get(ValueType::Time);
if (!accepted_range.has_value()) {
dbgln_if(LIBWEB_CSS_DEBUG, "FIXME: Calculation context missing accepted type range {}", value->type());
// FIXME: Infinity for integers should be i32 max rather than float max
accepted_range = { AK::NumericLimits<float>::lowest(), AK::NumericLimits<float>::max() };
}
raw_value = clamp(raw_value, accepted_range->min, accepted_range->max);
return ResolvedValue { raw_value, value->type() };
}
@ -2912,15 +3001,8 @@ Optional<double> CalculatedStyleValue::resolve_number(CalculationResolutionConte
{
auto result = resolve_value(context);
if (result.has_value() && result->type.has_value() && result->type->matches_number(m_context.percentages_resolve_as)) {
auto value = result->value;
// FIXME: This can be removed once it is upstreamed to `resolve_value`
if (isnan(value))
return 0.;
if (result.has_value() && result->type.has_value() && result->type->matches_number(m_context.percentages_resolve_as))
return result->value;
}
return {};
}

View file

@ -2,8 +2,7 @@ Harness status: OK
Found 14 tests
13 Pass
1 Fail
14 Pass
Pass Property border-radius value '1px'
Pass Property border-radius value '1px 2% 3px 4%'
Pass Property border-radius value '5em / 1px 2% 3px 4%'
@ -13,7 +12,7 @@ Pass Property border-radius value '1px 1px 1px 1px / 1px 1px 2% 1px'
Pass Property border-radius value '1px 1px 2% 2%'
Pass Property border-radius value '1px 2% 1px 1px'
Pass Property border-radius value '1px 2% 2% 2% / 1px 2% 3px 2%'
Fail Property border-top-left-radius value 'calc(-0.5em + 10px)'
Pass Property border-top-left-radius value 'calc(-0.5em + 10px)'
Pass Property border-top-right-radius value '20%'
Pass Property border-bottom-right-radius value 'calc(0.5em + 10px) 40%'
Pass Property border-bottom-left-radius value '50% 60px'

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 12 tests
10 Pass
2 Fail
11 Pass
1 Fail
Pass Property flex-basis value '1px'
Pass Property flex-basis value '400%'
Pass Property flex-basis value 'auto'
@ -12,7 +12,7 @@ Pass Property flex-basis value 'fit-content'
Pass Property flex-basis value 'min-content'
Pass Property flex-basis value 'max-content'
Pass Property flex-basis value 'calc(10px + 0.5em)'
Fail Property flex-basis value 'calc(10px - 0.5em)'
Pass Property flex-basis value 'calc(10px - 0.5em)'
Pass Property flex-basis value 'calc(10%)'
Pass Property flex-basis value 'calc(0% + 10px)'
Fail Property flex-basis value 'calc(10% + 0px)'

View file

@ -2,8 +2,7 @@ Harness status: OK
Found 8 tests
7 Pass
1 Fail
8 Pass
Pass Property flex value 'none'
Pass Property flex value '1'
Pass Property flex value '2 3'
@ -11,4 +10,4 @@ Pass Property flex value '4 5 6px'
Pass Property flex value '7% 8'
Pass Property flex value '8 auto'
Pass Property flex value 'calc(10px + 0.5em)'
Fail Property flex value 'calc(10px - 0.5em)'
Pass Property flex value 'calc(10px - 0.5em)'

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 30 tests
8 Pass
22 Fail
10 Pass
20 Fail
Fail Property font-width value 'ultra-condensed'
Fail Property font-width value 'extra-condensed'
Fail Property font-width value 'condensed'
@ -16,7 +16,7 @@ Fail Property font-width value 'ultra-expanded'
Pass Property font-width value '234.5%'
Pass Property font-width value 'calc(100%)'
Pass Property font-width value 'calc(0%)'
Fail Property font-width value 'calc(-100%)'
Pass Property font-width value 'calc(-100%)'
Pass Property font-width value 'calc(100% + 100%)'
Fail Property font-width value 'calc(100% + (sign(20cqw - 10px) * 5%))'
Fail Property font-stretch value 'ultra-condensed'
@ -31,6 +31,6 @@ Fail Property font-stretch value 'ultra-expanded'
Pass Property font-stretch value '234.5%'
Pass Property font-stretch value 'calc(100%)'
Pass Property font-stretch value 'calc(0%)'
Fail Property font-stretch value 'calc(-100%)'
Pass Property font-stretch value 'calc(-100%)'
Pass Property font-stretch value 'calc(100% + 100%)'
Fail Property font-stretch value 'calc(100% + (sign(20cqw - 10px) * 5%))'

View file

@ -2,15 +2,15 @@ Harness status: OK
Found 12 tests
10 Pass
2 Fail
11 Pass
1 Fail
Pass Property max-height value 'none'
Pass Property max-height value 'min-content'
Pass Property max-height value 'max-content'
Pass Property max-height value '10px'
Pass Property max-height value '20%'
Pass Property max-height value 'calc(10% + 40px)'
Fail Property max-height value 'calc(10px - 0.5em)'
Pass Property max-height value 'calc(10px - 0.5em)'
Pass Property max-height value 'calc(10px + 0.5em)'
Pass Property max-height value 'fit-content(10px)'
Pass Property max-height value 'fit-content(20%)'

View file

@ -2,15 +2,15 @@ Harness status: OK
Found 12 tests
10 Pass
2 Fail
11 Pass
1 Fail
Pass Property max-width value 'none'
Pass Property max-width value 'min-content'
Pass Property max-width value 'max-content'
Pass Property max-width value '10px'
Pass Property max-width value '20%'
Pass Property max-width value 'calc(10% + 40px)'
Fail Property max-width value 'calc(10px - 0.5em)'
Pass Property max-width value 'calc(10px - 0.5em)'
Pass Property max-width value 'calc(10px + 0.5em)'
Pass Property max-width value 'fit-content(10px)'
Pass Property max-width value 'fit-content(20%)'

View file

@ -2,14 +2,14 @@ Harness status: OK
Found 11 tests
9 Pass
2 Fail
10 Pass
1 Fail
Pass Property min-height value 'min-content'
Pass Property min-height value 'max-content'
Pass Property min-height value '10px'
Pass Property min-height value '20%'
Pass Property min-height value 'calc(10% + 40px)'
Fail Property min-height value 'calc(10px - 0.5em)'
Pass Property min-height value 'calc(10px - 0.5em)'
Pass Property min-height value 'calc(10px + 0.5em)'
Pass Property min-height value 'fit-content(10px)'
Pass Property min-height value 'fit-content(20%)'

View file

@ -2,14 +2,14 @@ Harness status: OK
Found 11 tests
9 Pass
2 Fail
10 Pass
1 Fail
Pass Property min-width value 'min-content'
Pass Property min-width value 'max-content'
Pass Property min-width value '10px'
Pass Property min-width value '20%'
Pass Property min-width value 'calc(10% + 40px)'
Fail Property min-width value 'calc(10px - 0.5em)'
Pass Property min-width value 'calc(10px - 0.5em)'
Pass Property min-width value 'calc(10px + 0.5em)'
Pass Property min-width value 'fit-content(10px)'
Pass Property min-width value 'fit-content(20%)'

View file

@ -2,36 +2,36 @@ Harness status: OK
Found 140 tests
70 Pass
70 Fail
Fail CSS Transitions: property <left> from [0px] to [calc(infinity * 1px)] at (-0.25) should be [NaNpx]
84 Pass
56 Fail
Fail CSS Transitions: property <left> from [0px] to [calc(infinity * 1px)] at (-0.25) should be [-8.50705e+37px]
Fail CSS Transitions: property <left> from [0px] to [calc(infinity * 1px)] at (0) should be [0px]
Fail CSS Transitions: property <left> from [0px] to [calc(infinity * 1px)] at (0.25) should be [NaNpx]
Fail CSS Transitions: property <left> from [0px] to [calc(infinity * 1px)] at (0.5) should be [NaNpx]
Fail CSS Transitions: property <left> from [0px] to [calc(infinity * 1px)] at (0.75) should be [NaNpx]
Fail CSS Transitions: property <left> from [0px] to [calc(infinity * 1px)] at (1) should be [NaNpx]
Fail CSS Transitions: property <left> from [0px] to [calc(infinity * 1px)] at (1.25) should be [NaNpx]
Fail CSS Transitions with transition: all: property <left> from [0px] to [calc(infinity * 1px)] at (-0.25) should be [NaNpx]
Pass CSS Transitions: property <left> from [0px] to [calc(infinity * 1px)] at (0.25) should be [8.50705e+37px]
Pass CSS Transitions: property <left> from [0px] to [calc(infinity * 1px)] at (0.5) should be [1.70141e+38px]
Pass CSS Transitions: property <left> from [0px] to [calc(infinity * 1px)] at (0.75) should be [2.552115e+38px]
Pass CSS Transitions: property <left> from [0px] to [calc(infinity * 1px)] at (1) should be [3.40282e+38px]
Pass CSS Transitions: property <left> from [0px] to [calc(infinity * 1px)] at (1.25) should be [4.2535250000000006e+38px]
Fail CSS Transitions with transition: all: property <left> from [0px] to [calc(infinity * 1px)] at (-0.25) should be [-8.50705e+37px]
Fail CSS Transitions with transition: all: property <left> from [0px] to [calc(infinity * 1px)] at (0) should be [0px]
Fail CSS Transitions with transition: all: property <left> from [0px] to [calc(infinity * 1px)] at (0.25) should be [NaNpx]
Fail CSS Transitions with transition: all: property <left> from [0px] to [calc(infinity * 1px)] at (0.5) should be [NaNpx]
Fail CSS Transitions with transition: all: property <left> from [0px] to [calc(infinity * 1px)] at (0.75) should be [NaNpx]
Fail CSS Transitions with transition: all: property <left> from [0px] to [calc(infinity * 1px)] at (1) should be [NaNpx]
Fail CSS Transitions with transition: all: property <left> from [0px] to [calc(infinity * 1px)] at (1.25) should be [NaNpx]
Pass CSS Animations: property <left> from [0px] to [calc(infinity * 1px)] at (-0.25) should be [NaNpx]
Pass CSS Transitions with transition: all: property <left> from [0px] to [calc(infinity * 1px)] at (0.25) should be [8.50705e+37px]
Pass CSS Transitions with transition: all: property <left> from [0px] to [calc(infinity * 1px)] at (0.5) should be [1.70141e+38px]
Pass CSS Transitions with transition: all: property <left> from [0px] to [calc(infinity * 1px)] at (0.75) should be [2.552115e+38px]
Pass CSS Transitions with transition: all: property <left> from [0px] to [calc(infinity * 1px)] at (1) should be [3.40282e+38px]
Pass CSS Transitions with transition: all: property <left> from [0px] to [calc(infinity * 1px)] at (1.25) should be [4.2535250000000006e+38px]
Fail CSS Animations: property <left> from [0px] to [calc(infinity * 1px)] at (-0.25) should be [-3.40282e+38px]
Pass CSS Animations: property <left> from [0px] to [calc(infinity * 1px)] at (0) should be [0px]
Pass CSS Animations: property <left> from [0px] to [calc(infinity * 1px)] at (0.25) should be [NaNpx]
Fail CSS Animations: property <left> from [0px] to [calc(infinity * 1px)] at (0.5) should be [NaNpx]
Fail CSS Animations: property <left> from [0px] to [calc(infinity * 1px)] at (0.75) should be [NaNpx]
Fail CSS Animations: property <left> from [0px] to [calc(infinity * 1px)] at (1) should be [NaNpx]
Fail CSS Animations: property <left> from [0px] to [calc(infinity * 1px)] at (1.25) should be [NaNpx]
Pass Web Animations: property <left> from [0px] to [calc(infinity * 1px)] at (-0.25) should be [NaNpx]
Fail CSS Animations: property <left> from [0px] to [calc(infinity * 1px)] at (0.25) should be [3.40282e+38px]
Pass CSS Animations: property <left> from [0px] to [calc(infinity * 1px)] at (0.5) should be [3.40282e+38px]
Pass CSS Animations: property <left> from [0px] to [calc(infinity * 1px)] at (0.75) should be [3.40282e+38px]
Pass CSS Animations: property <left> from [0px] to [calc(infinity * 1px)] at (1) should be [3.40282e+38px]
Pass CSS Animations: property <left> from [0px] to [calc(infinity * 1px)] at (1.25) should be [3.40282e+38px]
Fail Web Animations: property <left> from [0px] to [calc(infinity * 1px)] at (-0.25) should be [-3.40282e+38px]
Pass Web Animations: property <left> from [0px] to [calc(infinity * 1px)] at (0) should be [0px]
Pass Web Animations: property <left> from [0px] to [calc(infinity * 1px)] at (0.25) should be [NaNpx]
Fail Web Animations: property <left> from [0px] to [calc(infinity * 1px)] at (0.5) should be [NaNpx]
Fail Web Animations: property <left> from [0px] to [calc(infinity * 1px)] at (0.75) should be [NaNpx]
Fail Web Animations: property <left> from [0px] to [calc(infinity * 1px)] at (1) should be [NaNpx]
Fail Web Animations: property <left> from [0px] to [calc(infinity * 1px)] at (1.25) should be [NaNpx]
Fail Web Animations: property <left> from [0px] to [calc(infinity * 1px)] at (0.25) should be [3.40282e+38px]
Pass Web Animations: property <left> from [0px] to [calc(infinity * 1px)] at (0.5) should be [3.40282e+38px]
Pass Web Animations: property <left> from [0px] to [calc(infinity * 1px)] at (0.75) should be [3.40282e+38px]
Pass Web Animations: property <left> from [0px] to [calc(infinity * 1px)] at (1) should be [3.40282e+38px]
Pass Web Animations: property <left> from [0px] to [calc(infinity * 1px)] at (1.25) should be [3.40282e+38px]
Fail CSS Transitions: property <left> from [calc(50% - 25px)] to [calc(100% - 10px)] at (-0.25) should be [-10px]
Fail CSS Transitions: property <left> from [calc(50% - 25px)] to [calc(100% - 10px)] at (0) should be [0px]
Fail CSS Transitions: property <left> from [calc(50% - 25px)] to [calc(100% - 10px)] at (0.25) should be [10px]

View file

@ -2,8 +2,8 @@ Harness status: OK
Found 45 tests
26 Pass
19 Fail
33 Pass
12 Fail
Pass Property width value 'calc(NaN * 1px)'
Pass Property width value 'calc(NaN * 1%)'
Pass Property width value 'calc(infinity * 1px)'
@ -30,13 +30,13 @@ Pass Property margin-left value 'calc(-infinity * 1%)'
Pass Property margin-left value 'calc(max(10000px, 0px) + min(-infinity * 1px, infinity * 1px))'
Pass Property margin-left value 'calc(-infinity * 1px - infinity * 1px)'
Pass Property margin-left value 'calc(min(-infinity * 1px, 10px))'
Fail Property animation-duration value 'calc(NaN * 1s)'
Fail Property animation-duration value 'calc(infinity * 1s)'
Fail Property animation-duration value 'calc(1 / 0 * 1s)'
Fail Property animation-duration value 'calc(max(infinity * 1s, 10s)'
Fail Property transition-delay value 'calc(-infinity* 1s)'
Fail Property transition-delay value 'calc(max(10000s, 0s) + min(-infinity * 1s, infinity * 1s))'
Fail Property transition-delay value 'calc(min(-infinity * 1s, 10s))'
Pass Property animation-duration value 'calc(NaN * 1s)'
Pass Property animation-duration value 'calc(infinity * 1s)'
Pass Property animation-duration value 'calc(1 / 0 * 1s)'
Pass Property animation-duration value 'calc(max(infinity * 1s, 10s)'
Pass Property transition-delay value 'calc(-infinity* 1s)'
Pass Property transition-delay value 'calc(max(10000s, 0s) + min(-infinity * 1s, infinity * 1s))'
Pass Property transition-delay value 'calc(min(-infinity * 1s, 10s))'
Fail Property rotate(calc(infinity * 1deg)) value expected same with rotate(0deg) in +/-0.0001
Fail Property rotate(calc(-infinity * 1deg)) value expected same with rotate(0deg) in +/-0.0001
Fail Property rotate(calc(NaN * 1deg)) value expected same with rotate(0deg) in +/-0.0001

View file

@ -0,0 +1,18 @@
Harness status: OK
Found 12 tests
11 Pass
1 Fail
Pass testing tab-size: calc(2 * 3)
Pass testing tab-size: calc(2 * -4)
Pass testing opacity: calc(2 / 4)
Pass testing tab-size: calc(2 / 4)
Pass testing opacity: calc(2 / 4) * 1px
Fail testing opacity: calc(90%)
Pass testing tab-size: calc(1 + 1px)
Pass testing tab-size: calc(1 + 100%)
Pass testing tab-size: calc(100%)
Pass testing tab-size: calc(10px) bla
Pass testing tab-size: calc(bla) 10px
Pass testing tab-size: calc(10px)

View file

@ -0,0 +1,31 @@
Harness status: OK
Found 25 tests
19 Pass
6 Fail
Pass min(1em, 110px / 10px * 1px) should be used-value-equivalent to 10px
Pass max(10px, 110px / 10px * 1px) should be used-value-equivalent to 11px
Pass max(1em + 2px, 110px / 10px * 1px) should be used-value-equivalent to 12px
Pass max(1em + 2%, 110px / 10px * 1px) should be used-value-equivalent to 12px
Pass clamp(110px / 10px * 1px, 1em + 200%, 200% * 1% / 1em) should be used-value-equivalent to 20px
Pass calc(3 + sign(10px / 1rem - sign(1em + 1px))) should be used-value-equivalent to 3
Pass calc(10em / 1em) should be used-value-equivalent to 10
Pass calc(10em / 1rem) should be used-value-equivalent to 10
Pass calc(10em / 1px) should be used-value-equivalent to 100
Pass calc(1px / 10em * NaN) should be used-value-equivalent to 0
Pass Property width value 'calc(1px * 10em / 0em)'
Pass Property width value 'calc(1px / 1px * 10em * infinity)'
Fail Property margin-left value 'calc(1px * 10em / -0em)'
Pass Property z-index value 'calc(10em / 0em)'
Pass sign(-0em / 1px) should be used-value-equivalent to 0
Fail clamp(-1, 1 / sign(-0em / 1px), 1) should be used-value-equivalent to -1
Fail sign( 0cqi / 1px) should be used-value-equivalent to 0
Fail clamp(-1, 1 / sign( 0cqi / 1px), 1) should be used-value-equivalent to 1
Pass sign(atan2(-0cap / 1px, 0em / 1px)) should be used-value-equivalent to 0
Fail clamp(-1, 1 / sign(atan2(-0cap / 1px, 0em / 1px)), 1) should be used-value-equivalent to -1
Pass sign(exp(-1vh / 0px)) should be used-value-equivalent to 0
Pass clamp(-1, 1 / sign(exp(-1vh / 0px)), 1) should be used-value-equivalent to 1
Fail calc(20cqw / 1rem) should be used-value-equivalent to 2
Pass Property animation-duration value 'calc(2s / (10s - 10s) * 1s)'
Pass subtraction of angle unit: deg minus turn

View file

@ -0,0 +1,100 @@
<!DOCTYPE html>
<meta charset="UTF-8">
<title>CSS Values and Units Test: computed value of 'tab-size' and 'opacity' when specified with calc() function</title>
<!--
Original test is:
https://chromium.googlesource.com/chromium/src/+/c825d655f6aaf73484f9d56e9012793f5b9668cc/third_party/WebKit/LayoutTests/css3/calc/calc-numbers.html
-->
<link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/">
<link rel="help" href="https://www.w3.org/TR/css-color-3/#transparency">
<link rel="help" href="https://www.w3.org/TR/css-text-3/#tab-size-property">
<link rel="help" href="https://www.w3.org/TR/css3-values/#calc-computed-value">
<link rel="help" href="https://www.w3.org/TR/css3-values/#calc-range">
<meta name="flags" content="invalid">
<meta content="This test verifies how 11 calc() functions are computed for 'opacity' and 'tab-size'." name="assert">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<div id="target"></div>
<script>
function startTesting()
{
function verifyComputedStyle(property_name, initial_value, specified_value, expected_value, description)
{
var elemTarget = document.getElementById("target");
test(function()
{
elemTarget.style.setProperty(property_name, initial_value);
/*
In exactly 6 out of the 11 sub-tests, the initial_value will
act as a fallback value because the specified value generates
an invalid value. Since we are running 11 consecutive tests
on the same element, then it is necessary to 'reset' its
property to an initial value.
*/
elemTarget.style.setProperty(property_name, specified_value);
assert_equals(getComputedStyle(elemTarget)[property_name], expected_value, specified_value + ' should compute to ' + expected_value);
}, description);
}
/* verifyComputedStyle(property_name, initial_value, specified_value, expected_value, description) */
verifyComputedStyle("tab-size", "initial", "calc(2 * 3)", "6", "testing tab-size: calc(2 * 3)");
verifyComputedStyle("tab-size", "12345", "calc(2 * -4)", "0", "testing tab-size: calc(2 * -4)");
/*
an out-of-range value inside a calc() does not cause
the declaration to become invalid. The value resulting
from an expression must be clamped to the range
allowed in the target context.
https://www.w3.org/TR/css-values-3/#calc-range
*/
verifyComputedStyle("opacity", "initial", "calc(2 / 4)", "0.5", "testing opacity: calc(2 / 4)");
verifyComputedStyle("tab-size", "12345", "calc(2 / 4)", "0.5", "testing tab-size: calc(2 / 4)");
/*
'tab-size' accepts <number> values.
*/
verifyComputedStyle("opacity", "0.9", "calc(2 / 4) * 1px", "0.9", "testing opacity: calc(2 / 4) * 1px");
verifyComputedStyle("opacity", "0.9", "calc(90%)", "0.9", "testing opacity: calc(90%)");
verifyComputedStyle("tab-size", "12345", "calc(1 + 1px)", "12345", "testing tab-size: calc(1 + 1px)");
verifyComputedStyle("tab-size", "12345", "calc(1 + 100%)", "12345", "testing tab-size: calc(1 + 100%)");
verifyComputedStyle("tab-size", "12345", "calc(100%)", "12345", "testing tab-size: calc(100%)");
verifyComputedStyle("tab-size", "12345", "calc(10px) bla", "12345", "testing tab-size: calc(10px) bla");
verifyComputedStyle("tab-size", "12345", "calc(bla) 10px", "12345", "testing tab-size: calc(bla) 10px");
verifyComputedStyle("tab-size", "initial", "calc(10px)", "10px", "testing tab-size: calc(10px)");
/* verifyComputedStyle(property_name, initial_value, specified_value, expected_value, description) */
}
startTesting();
</script>

View file

@ -0,0 +1,58 @@
<!DOCTYPE html>
<title>CSS Values: typed arithmetic tests</title>
<link rel="help" href="https://www.w3.org/TR/css-values-4/#calc-type-checking">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../css/support/numeric-testcommon.js"></script>
<script src="../../css/support/computed-testcommon.js"></script>
<style>
:root {
font-size: 10px;
}
#target {
font-size: 10px;
}
</style>
<div style="width: 100px; container: my-container / inline-size;">
<div id="target"></div>
</div>
<script>
function test_zero(expression, { is_negative, type = "integer" }) {
test_math_used(`sign(${expression})`, '0', { type: type });
// to test zero sign, make it to negative infinity and clamp it between -1 and 1
test_math_used(
`clamp(-1, 1 / sign(${expression}), 1)`,
(is_negative)? '-1' : '1',
{ type: type },
);
}
const REALLY_LARGE = 1e6;
const REALLY_LARGE_NEGATIVE = -REALLY_LARGE;
test_math_used("min(1em, 110px / 10px * 1px)", "10px");
test_math_used("max(10px, 110px / 10px * 1px)", "11px");
test_math_used("max(1em + 2px, 110px / 10px * 1px)", "12px");
test_math_used("max(1em + 2%, 110px / 10px * 1px)", "12px", {"prop": "width"});
test_math_used("clamp(110px / 10px * 1px, 1em + 200%, 200% * 1% / 1em)", "20px");
test_math_used("calc(3 + sign(10px / 1rem - sign(1em + 1px)))", "3", {"prop": "z-index"});
test_math_used("calc(10em / 1em)", "10", {"prop": "z-index"});
test_math_used("calc(10em / 1rem)", "10", {"prop": "z-index"});
test_math_used("calc(10em / 1px)", "100", {"prop": "z-index"});
test_math_used("calc(1px / 10em * NaN)", "0", {"prop": "z-index"});
testComputedValueGreaterOrLowerThan("width", "calc(1px * 10em / 0em)", REALLY_LARGE);
testComputedValueGreaterOrLowerThan("width", "calc(1px / 1px * 10em * infinity)", REALLY_LARGE);
testComputedValueGreaterOrLowerThan("margin-left", "calc(1px * 10em / -0em)", REALLY_LARGE_NEGATIVE);
testComputedValueGreaterOrLowerThan("z-index", "calc(10em / 0em)", REALLY_LARGE);
test_zero("-0em / 1px", { is_negative: true });
test_zero(" 0cqi / 1px", { is_negative: false });
test_zero('atan2(-0cap / 1px, 0em / 1px)', { is_negative: true });
test_zero('exp(-1vh / 0px)', { is_negative: false });
test_math_used("calc(20cqw / 1rem)", "2", {"prop": "z-index"});
testComputedValueGreaterOrLowerThan("animation-duration", "calc(2s / (10s - 10s) * 1s)", REALLY_LARGE);
testTransformValuesCloseTo("rotate(calc((40em - 40px) / 1px * 1deg - 0.5turn))", 0.0001, "rotate(180deg)", "subtraction of angle unit: deg minus turn");
</script>