mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-26 12:17:52 +00:00
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:
parent
bc2ca96f50
commit
778da0175e
Notes:
github-actions[bot]
2025-08-11 16:11:10 +00:00
Author: https://github.com/Calme1709
Commit: 778da0175e
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5734
Reviewed-by: https://github.com/AtkinsSJ ✅
Reviewed-by: https://github.com/Hendiadyoin1
Reviewed-by: https://github.com/gmta
Reviewed-by: https://github.com/tcl3
15 changed files with 361 additions and 74 deletions
|
@ -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; it’s 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; it’s censored into a zero value.
|
||||
// NaN does not escape a top-level calculation; it’s 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 {};
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)'
|
|
@ -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)'
|
|
@ -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%))'
|
|
@ -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%)'
|
||||
|
|
|
@ -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%)'
|
||||
|
|
|
@ -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%)'
|
||||
|
|
|
@ -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%)'
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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>
|
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue