LibWeb: Set numeric type of asin, acos, atan calculation results

Previously we were omitting the numeric type which meant these functions
weren't valid in some cases e.g. within rotate() functions.
This commit is contained in:
Callum Law 2025-06-25 14:25:20 +12:00 committed by Tim Ledbetter
commit 536f8c395c
Notes: github-actions[bot] 2025-06-25 04:20:13 +00:00
5 changed files with 254 additions and 1 deletions

View file

@ -1702,7 +1702,7 @@ static Optional<CalculatedStyleValue::CalculationResult> run_asin_acos_or_atan_o
break;
}
return CalculatedStyleValue::CalculationResult { result, CSSNumericType {}.made_consistent_with(child.numeric_type().value()) };
return CalculatedStyleValue::CalculationResult { result, CSSNumericType { CSSNumericType::BaseType::Angle, 1 }.made_consistent_with(child.numeric_type().value()) };
}
// https://drafts.csswg.org/css-values-4/#funcdef-asin

View file

@ -0,0 +1,51 @@
Harness status: OK
Found 45 tests
34 Pass
11 Fail
Pass acos(1) should be used-value-equivalent to 0deg
Pass atan(0) should be used-value-equivalent to 0deg
Pass asin(0) should be used-value-equivalent to 0deg
Pass atan2(0,0) should be used-value-equivalent to 0deg
Pass calc(asin(sin(pi/2))) should be used-value-equivalent to 90deg
Pass calc(acos(cos(pi - 3.14159265358979323846))) should be used-value-equivalent to 0deg
Pass calc(atan(e - 2.7182818284590452354) ) should be used-value-equivalent to 0deg
Pass calc(asin(sin(30deg + 1.0471967rad ) )) should be used-value-equivalent to 90deg
Pass calc(acos(cos(30deg - 0.523599rad ) )) should be used-value-equivalent to 0deg
Pass calc(asin(sin(3.14159 / 2 + 1 - 1) )) should be used-value-equivalent to 90deg
Pass calc(asin(sin(100grad) )) should be used-value-equivalent to 90deg
Pass calc(acos(cos(0 / 2 + 1 - 1) )) should be used-value-equivalent to 0deg
Pass calc(atan(tan(30deg + 0.261799rad ) )) should be used-value-equivalent to 45deg
Pass calc(atan(tan(0.7853975rad ) )) should be used-value-equivalent to 45deg
Pass calc(atan(tan(3.14159 / 4 + 1 - 1) )) should be used-value-equivalent to 45deg
Pass calc(asin(sin(0.25turn)) ) should be used-value-equivalent to 90deg
Pass calc(atan2(0,1)) should be used-value-equivalent to 0deg
Pass calc(atan2(0,-1) / 4) should be used-value-equivalent to 45deg
Pass calc(atan2(1,-1)) should be used-value-equivalent to 135deg
Pass calc(atan2(-1,1)) should be used-value-equivalent to -45deg
Fail calc(asin(sin(180deg * sibling-index()))) should be used-value-equivalent to 0deg
Fail calc(acos(cos(180deg * sibling-index()))) should be used-value-equivalent to 180deg
Fail calc(atan(tan(180deg * sibling-index()))) should be used-value-equivalent to 0deg
Pass calc(cos(sin(acos(cos(pi))))) should be used-value-equivalent to 1
Pass atan2(1px, -1px) should be used-value-equivalent to 135deg
Pass atan2(1cm, -1cm) should be used-value-equivalent to 135deg
Pass atan2(1mm, -1mm) should be used-value-equivalent to 135deg
Pass atan2(1Q, -1Q) should be used-value-equivalent to 135deg
Pass atan2(1in, -1in) should be used-value-equivalent to 135deg
Pass atan2(1pc, -1pc) should be used-value-equivalent to 135deg
Pass atan2(1pt, -1pt) should be used-value-equivalent to 135deg
Fail atan2(1em, -1em) should be used-value-equivalent to 135deg
Fail atan2(1ex, -1ex) should be used-value-equivalent to 135deg
Fail atan2(1ch, -1ch) should be used-value-equivalent to 135deg
Fail atan2(1rem, -1rem) should be used-value-equivalent to 135deg
Fail atan2(1rem + 1px - 1px, -1rem) should be used-value-equivalent to 135deg
Fail atan2(1vh, -1vh) should be used-value-equivalent to 135deg
Fail atan2(1vh + 0px, -1vh + 0px) should be used-value-equivalent to 135deg
Fail atan2(1vw, -1vw) should be used-value-equivalent to 135deg
Pass atan2(1deg, -1deg) should be used-value-equivalent to 135deg
Pass atan2(1grad, -1grad) should be used-value-equivalent to 135deg
Pass atan2(1turn, -1turn) should be used-value-equivalent to 135deg
Pass atan2(1rad, -1rad) should be used-value-equivalent to 135deg
Pass atan2(1s, -1s) should be used-value-equivalent to 135deg
Pass atan2(1ms, -1ms) should be used-value-equivalent to 135deg

View file

@ -0,0 +1,68 @@
Harness status: OK
Found 62 tests
50 Pass
12 Fail
Pass 'rotate(acos(1))' as a specified value should serialize as 'rotate(calc(0deg))'.
Pass 'rotate(calc(acos(1)))' as a specified value should serialize as 'rotate(calc(0deg))'.
Pass 'rotate(acos(-1))' as a specified value should serialize as 'rotate(calc(180deg))'.
Pass 'rotate(calc(acos(-1)))' as a specified value should serialize as 'rotate(calc(180deg))'.
Pass 'rotate(acos(-1.5))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Pass 'rotate(calc(acos(-1.5)))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Pass 'rotate(acos(1.5))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Pass 'rotate(calc(acos(1.5)))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Pass 'rotate(acos(2))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Pass 'rotate(calc(acos(2)))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Fail 'rotate(acos(0.5))' as a specified value should serialize as 'rotate(calc(60deg))'.
Fail 'rotate(calc(acos(0.5)))' as a specified value should serialize as 'rotate(calc(60deg))'.
Fail 'rotate(acos(1 - 0.5))' as a specified value should serialize as 'rotate(calc(60deg))'.
Fail 'rotate(calc(acos(1 - 0.5)))' as a specified value should serialize as 'rotate(calc(60deg))'.
Pass 'rotate(acos(0))' as a specified value should serialize as 'rotate(calc(90deg))'.
Pass 'rotate(calc(acos(0)))' as a specified value should serialize as 'rotate(calc(90deg))'.
Pass 'rotate(asin(1))' as a specified value should serialize as 'rotate(calc(90deg))'.
Pass 'rotate(calc(asin(1)))' as a specified value should serialize as 'rotate(calc(90deg))'.
Pass 'rotate(asin(-1))' as a specified value should serialize as 'rotate(calc(-90deg))'.
Pass 'rotate(calc(asin(-1)))' as a specified value should serialize as 'rotate(calc(-90deg))'.
Pass 'rotate(asin(-1.5))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Pass 'rotate(calc(asin(-1.5)))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Pass 'rotate(asin(1.5))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Pass 'rotate(calc(asin(1.5)))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Pass 'rotate(asin(2))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Pass 'rotate(calc(asin(2)))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Fail 'rotate(asin(0.5))' as a specified value should serialize as 'rotate(calc(30deg))'.
Fail 'rotate(calc(asin(0.5)))' as a specified value should serialize as 'rotate(calc(30deg))'.
Fail 'rotate(asin(1 - 0.5))' as a specified value should serialize as 'rotate(calc(30deg))'.
Fail 'rotate(calc(asin(1 - 0.5)))' as a specified value should serialize as 'rotate(calc(30deg))'.
Pass 'rotate(asin(0))' as a specified value should serialize as 'rotate(calc(0deg))'.
Pass 'rotate(calc(asin(0)))' as a specified value should serialize as 'rotate(calc(0deg))'.
Pass 'rotate(acos(pi - pi))' as a specified value should serialize as 'rotate(calc(90deg))'.
Pass 'rotate(calc(acos(pi - pi)))' as a specified value should serialize as 'rotate(calc(90deg))'.
Pass 'rotate(asin(pi - pi + 1))' as a specified value should serialize as 'rotate(calc(90deg))'.
Pass 'rotate(calc(asin(pi - pi + 1)))' as a specified value should serialize as 'rotate(calc(90deg))'.
Pass 'rotate(atan(1))' as a specified value should serialize as 'rotate(calc(45deg))'.
Pass 'rotate(calc(atan(1)))' as a specified value should serialize as 'rotate(calc(45deg))'.
Fail 'rotate(atan(0.577350269))' as a specified value should serialize as 'rotate(calc(30deg))'.
Fail 'rotate(calc(atan(0.577350269)))' as a specified value should serialize as 'rotate(calc(30deg))'.
Pass 'rotate(atan(0))' as a specified value should serialize as 'rotate(calc(0deg))'.
Pass 'rotate(calc(atan(0)))' as a specified value should serialize as 'rotate(calc(0deg))'.
Pass 'rotate(atan(infinity))' as a specified value should serialize as 'rotate(calc(90deg))'.
Pass 'rotate(calc(atan(infinity)))' as a specified value should serialize as 'rotate(calc(90deg))'.
Fail 'rotate(atan2(37.320508075, 10))' as a specified value should serialize as 'rotate(calc(75deg))'.
Fail 'rotate(calc(atan2(37.320508075, 10)))' as a specified value should serialize as 'rotate(calc(75deg))'.
Pass 'rotate(atan2(1s, 1000ms))' as a specified value should serialize as 'rotate(calc(45deg))'.
Pass 'rotate(calc(atan2(1s, 1000ms)))' as a specified value should serialize as 'rotate(calc(45deg))'.
Pass 'rotate(atan2(infinity, infinity))' as a specified value should serialize as 'rotate(calc(45deg))'.
Pass 'rotate(calc(atan2(infinity, infinity)))' as a specified value should serialize as 'rotate(calc(45deg))'.
Pass 'rotate(atan2(-infinity, -infinity))' as a specified value should serialize as 'rotate(calc(-135deg))'.
Pass 'rotate(calc(atan2(-infinity, -infinity)))' as a specified value should serialize as 'rotate(calc(-135deg))'.
Pass 'rotate(atan2(infinity, 10))' as a specified value should serialize as 'rotate(calc(90deg))'.
Pass 'rotate(calc(atan2(infinity, 10)))' as a specified value should serialize as 'rotate(calc(90deg))'.
Pass 'rotate(atan2(10, infinity))' as a specified value should serialize as 'rotate(calc(0deg))'.
Pass 'rotate(calc(atan2(10, infinity)))' as a specified value should serialize as 'rotate(calc(0deg))'.
Pass 'rotate(atan2(NaN, 10))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Pass 'rotate(calc(atan2(NaN, 10)))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Pass 'rotate(atan2(10, NaN))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Pass 'rotate(calc(atan2(10, NaN)))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Pass 'rotate(atan2(NaN, NaN))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.
Pass 'rotate(calc(atan2(NaN, NaN)))' as a specified value should serialize as 'rotate(calc(NaN * 1deg))'.

View file

@ -0,0 +1,73 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-values-4/#trig-funcs">
<link rel="help" href="https://drafts.csswg.org/css-values-4/#numbers">
<link rel="help" href="https://drafts.csswg.org/css-values-4/#calc-type-checking">
<link rel="author" title="Apple Inc">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../support/numeric-testcommon.js"></script>
<div id="target"></div>
<script>
function test_angle_equals(value, expected) {
test_math_used(value, expected, {type: "angle"});
}
// Simple tests
test_angle_equals('acos(1)', '0deg');
test_angle_equals('atan(0)', '0deg');
test_angle_equals('asin(0)', '0deg');
test_angle_equals('atan2(0,0)', '0deg');
// Test pi
test_math_used('calc(asin(sin(pi/2)))', '90deg', {type:'angle', approx:0.1});
test_math_used('calc(acos(cos(pi - 3.14159265358979323846)))', '0deg', {type:'angle', approx:0.1});
// Test e
test_math_used('calc(atan(e - 2.7182818284590452354) )', '0deg', {type:'angle', approx:0.1});
// General calculations
test_math_used('calc(asin(sin(30deg + 1.0471967rad ) ))', '90deg', {type:'angle', approx:0.1});
test_math_used('calc(acos(cos(30deg - 0.523599rad ) ))', '0deg', {type:'angle', approx:0.1});
test_math_used('calc(asin(sin(3.14159 / 2 + 1 - 1) ))', '90deg', {type:'angle', approx:0.1});
test_math_used('calc(asin(sin(100grad) ))', '90deg', {type:'angle', approx:0.1});
test_math_used('calc(acos(cos(0 / 2 + 1 - 1) ))', '0deg', {type:'angle', approx:0.1});
test_math_used('calc(atan(tan(30deg + 0.261799rad ) ))', '45deg', {type:'angle', approx:0.1});
test_math_used('calc(atan(tan(0.7853975rad ) ))', '45deg', {type:'angle', approx:0.1});
test_math_used('calc(atan(tan(3.14159 / 4 + 1 - 1) ))', '45deg', {type:'angle', approx:0.1});
test_math_used('calc(asin(sin(0.25turn)) )', '90deg', {type:'angle', approx:0.1});
test_math_used('calc(atan2(0,1))', '0deg', {type:'angle', approx:0.1});
test_math_used('calc(atan2(0,-1) / 4)', '45deg', {type:'angle', approx:0.1}); // atan2(0,-1) equals 180deg, result is divided to avoid ambiguity with -180deg
test_math_used('calc(atan2(1,-1))', '135deg', {type:'angle', approx:0.1});
test_math_used('calc(atan2(-1,1))', '-45deg', {type:'angle', approx:0.1});
// Test unresolved at parse time
test_math_used('calc(asin(sin(180deg * sibling-index())))', '0deg', {type:'angle', approx:0.1});
test_math_used('calc(acos(cos(180deg * sibling-index())))', '180deg', {type:'angle', approx:0.1});
test_math_used('calc(atan(tan(180deg * sibling-index())))', '0deg', {type:'angle', approx:0.1});
// Test nesting
test_math_used('calc(cos(sin(acos(cos(pi)))))', '1', {type:'number', approx:0.1});
// Test types for atan2
test_math_used('atan2(1px, -1px)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1cm, -1cm)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1mm, -1mm)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1Q, -1Q)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1in, -1in)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1pc, -1pc)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1pt, -1pt)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1em, -1em)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1ex, -1ex)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1ch, -1ch)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1rem, -1rem)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1rem + 1px - 1px, -1rem)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1vh, -1vh)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1vh + 0px, -1vh + 0px)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1vw, -1vw)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1deg, -1deg)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1grad, -1grad)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1turn, -1turn)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1rad, -1rad)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1s, -1s)', '135deg', {type:'angle', approx:0.1});
test_math_used('atan2(1ms, -1ms)', '135deg', {type:'angle', approx:0.1});
</script>

View file

@ -0,0 +1,61 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-values-4/#trig-funcs">
<link rel="help" href="https://drafts.csswg.org/css-values-4/#angles">
<link rel="help" href="https://drafts.csswg.org/css-values-4/#calc-serialize">
<link rel="author" title="Apple Inc">
<link rel="author" title="Seokho Song" href="seokho@chromium.org">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../support/serialize-testcommon.js"></script>
<div id=target></div>
<script>
function test_serialization(specified, expected, {prop="transform"}={}) {
// We only test the specified serialization,
// and not the computed or used serialization,
// since we'd need to do that by retrieving the rotation matrix,
// and that isn't perfectly interoperable in corner cases.
// Plus the point of this test is to check the trig functions themselves.
test_specified_serialization(prop, `rotate(${specified})`, `rotate(${expected})`)
}
//TEST CASE | EXPECTED
var test_map = {
"acos(1)" :"calc(0deg)",
"acos(-1)" :"calc(180deg)",
"acos(-1.5)" :"calc(NaN * 1deg)",
"acos(1.5)" :"calc(NaN * 1deg)",
"acos(2)" :"calc(NaN * 1deg)",
"acos(0.5)" :"calc(60deg)",
"acos(1 - 0.5)" :"calc(60deg)",
"acos(0)" :"calc(90deg)",
"asin(1)" :"calc(90deg)",
"asin(-1)" :"calc(-90deg)",
"asin(-1.5)" :"calc(NaN * 1deg)",
"asin(1.5)" :"calc(NaN * 1deg)",
"asin(2)" :"calc(NaN * 1deg)",
"asin(0.5)" :"calc(30deg)",
"asin(1 - 0.5)" :"calc(30deg)",
"asin(0)" :"calc(0deg)",
"acos(pi - pi)" :"calc(90deg)",
"asin(pi - pi + 1)" :"calc(90deg)",
"atan(1)" :"calc(45deg)",
"atan(0.577350269)" :"calc(30deg)",
"atan(0)" :"calc(0deg)",
"atan(infinity)" :"calc(90deg)",
"atan2(37.320508075, 10)" :"calc(75deg)",
"atan2(1s, 1000ms)" :"calc(45deg)",
"atan2(infinity, infinity)" :"calc(45deg)",
"atan2(-infinity, -infinity)" :"calc(-135deg)",
"atan2(infinity, 10)" :"calc(90deg)",
"atan2(10, infinity)" :"calc(0deg)",
"atan2(NaN, 10)" :"calc(NaN * 1deg)",
"atan2(10, NaN)" :"calc(NaN * 1deg)",
"atan2(NaN, NaN)" :"calc(NaN * 1deg)",
};
for (var exp in test_map) {
test_serialization(exp, test_map[exp]);
test_serialization(`calc(${exp})`, test_map[exp]);
}
</script>