LibWeb: Don't crash on non-finite values in CSS clamp() function

Stops a WPT test from crashing, giving us 29 new subtest passes. :^)
This commit is contained in:
Andreas Kling 2025-04-24 12:08:58 +02:00 committed by Andreas Kling
commit 1772adb600
Notes: github-actions[bot] 2025-04-24 16:28:32 +00:00
4 changed files with 210 additions and 1 deletions

View file

@ -1217,7 +1217,8 @@ CalculatedStyleValue::CalculationResult ClampCalculationNode::resolve(Calculatio
if (chosen_value == max_value)
return max_node;
VERIFY_NOT_REACHED();
// NOTE: Non-finite values end up here.
return CalculatedStyleValue::CalculationResult { chosen_value, numeric_type() };
}
NonnullRefPtr<CalculationNode const> ClampCalculationNode::with_simplified_children(CalculationContext const& context, CalculationResolutionContext const& resolution_context) const

View file

@ -0,0 +1,47 @@
Harness status: OK
Found 41 tests
29 Pass
12 Fail
Pass 'calc(1px * NaN)' as a specified value should serialize as 'calc(NaN * 1px)'.
Pass 'calc(1% * NaN)' as a specified value should serialize as 'calc(NaN * 1%)'.
Pass 'calc(1in * NaN)' as a specified value should serialize as 'calc(NaN * 1px)'.
Pass 'calc(1cm * NaN)' as a specified value should serialize as 'calc(NaN * 1px)'.
Pass 'calc(1mm * NaN)' as a specified value should serialize as 'calc(NaN * 1px)'.
Pass 'calc(1q * NaN)' as a specified value should serialize as 'calc(NaN * 1px)'.
Pass 'calc(1pt * NaN)' as a specified value should serialize as 'calc(NaN * 1px)'.
Pass 'calc(1pc * NaN)' as a specified value should serialize as 'calc(NaN * 1px)'.
Pass 'calc(1px * nan)' as a specified value should serialize as 'calc(NaN * 1px)'.
Fail 'calc(1px * infinity / infinity)' as a specified value should serialize as 'calc(NaN * 1px)'.
Pass 'calc(1px * 0 * infinity)' as a specified value should serialize as 'calc(NaN * 1px)'.
Pass 'calc(1px * (infinity + -infinity))' as a specified value should serialize as 'calc(NaN * 1px)'.
Pass 'calc(1px * (-infinity + infinity))' as a specified value should serialize as 'calc(NaN * 1px)'.
Pass 'calc(1px * (infinity - infinity))' as a specified value should serialize as 'calc(NaN * 1px)'.
Pass 'calc(1px * infinity)' as a specified value should serialize as 'calc(infinity * 1px)'.
Pass 'calc(1px * -infinity)' as a specified value should serialize as 'calc(-infinity * 1px)'.
Pass 'calc(1% * infinity)' as a specified value should serialize as 'calc(infinity * 1%)'.
Pass 'calc(1% * -infinity)' as a specified value should serialize as 'calc(-infinity * 1%)'.
Pass 'calc(1px * iNFinIty)' as a specified value should serialize as 'calc(infinity * 1px)'.
Pass 'calc(1px * (infinity + infinity))' as a specified value should serialize as 'calc(infinity * 1px)'.
Pass 'calc(1px * (-infinity + -infinity))' as a specified value should serialize as 'calc(-infinity * 1px)'.
Pass 'calc(1px * 1/infinity)' as a specified value should serialize as 'calc(0px)'.
Pass 'calc(1px * infinity * infinity)' as a specified value should serialize as 'calc(infinity * 1px)'.
Pass 'calc(1px * -infinity * -infinity)' as a specified value should serialize as 'calc(infinity * 1px)'.
Fail 'calc(1 * max(INFinity*3px, 0px))' as a specified value should serialize as 'calc(infinity * 1px)'.
Pass 'calc(1 * min(inFInity*4px, 0px))' as a specified value should serialize as 'calc(0px)'.
Fail 'calc(1 * max(nAn*2px, 0px))' as a specified value should serialize as 'calc(NaN * 1px)'.
Fail 'calc(1 * min(nan*3px, 0px))' as a specified value should serialize as 'calc(NaN * 1px)'.
Pass 'calc(1 * clamp(-INFINITY*20px, 0px, infiniTY*10px))' as a specified value should serialize as 'calc(0px)'.
Pass 'calc(1px * max(NaN, min(0,10)))' as a specified value should serialize as 'calc(NaN * 1px)'.
Pass 'calc(1px * clamp(NaN, 0, 10))' as a specified value should serialize as 'calc(NaN * 1px)'.
Fail 'calc(1px * max(0, min(10, NaN)))' as a specified value should serialize as 'calc(NaN * 1px)'.
Fail 'calc(1px * clamp(0, 10, NaN))' as a specified value should serialize as 'calc(NaN * 1px)'.
Fail 'calc(1px * max(0, min(NaN, 10)))' as a specified value should serialize as 'calc(NaN * 1px)'.
Fail 'calc(1px * clamp(0, NaN, 10))' as a specified value should serialize as 'calc(NaN * 1px)'.
Pass 'calc(1px * clamp(-Infinity, 0, infinity))' as a specified value should serialize as 'calc(0px)'.
Pass 'calc(1px * clamp(-inFinity, infinity, 10))' as a specified value should serialize as 'calc(10px)'.
Fail 'calc(1 * min(NaN * 1pt, NaN * 1cm))' as a specified value should serialize as 'calc(NaN * 1px)'.
Fail 'calc(1 * max(NaN * 1cm, NaN * 2Q))' as a specified value should serialize as 'calc(NaN * 1px)'.
Fail 'calc(1 * min(NaN * 2px, NaN * 4em))' as a specified value should serialize as 'calc(NaN * 1px)'.
Fail 'calc(1 * clamp(NaN * 2em, NaN * 4px, NaN * 8pt))' as a specified value should serialize as 'clamp(NaN * 1em, NaN * 1px, NaN * 1px)'.

View file

@ -0,0 +1,67 @@
<!DOCTYPE HTML>
<title>Infinity and NaN: calc() serialization for length values.</title>
<link rel="author" title="Seokho Song" href="mailto:0xdevssh@gmail.com">
<link rel="help" href="https://drafts.csswg.org/css-values/#calc-type-checking">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../support/serialize-testcommon.js"></script>
<div id="target"></div>
<div id="log"></div>
<script>
function test_serialization(t,s, {prop="width"}={}) {
test_specified_serialization(prop, t, s)
}
//TEST CASE | EXPECTED
var test_map = {
"1px * NaN" :"calc(NaN * 1px)",
"1% * NaN" :"calc(NaN * 1%)",
"1in * NaN" :"calc(NaN * 1px)",
"1cm * NaN" :"calc(NaN * 1px)",
"1mm * NaN" :"calc(NaN * 1px)",
"1q * NaN" :"calc(NaN * 1px)",
"1pt * NaN" :"calc(NaN * 1px)",
"1pc * NaN" :"calc(NaN * 1px)",
"1px * nan" :"calc(NaN * 1px)",
"1px * infinity / infinity" :"calc(NaN * 1px)",
"1px * 0 * infinity" :"calc(NaN * 1px)",
"1px * (infinity + -infinity)" :"calc(NaN * 1px)",
"1px * (-infinity + infinity)" :"calc(NaN * 1px)",
"1px * (infinity - infinity)" :"calc(NaN * 1px)",
"1px * infinity" :"calc(infinity * 1px)",
"1px * -infinity" :"calc(-infinity * 1px)",
"1% * infinity" :"calc(infinity * 1%)",
"1% * -infinity" :"calc(-infinity * 1%)",
"1px * iNFinIty" :"calc(infinity * 1px)",
"1px * (infinity + infinity)" :"calc(infinity * 1px)",
"1px * (-infinity + -infinity)" :"calc(-infinity * 1px)",
"1px * 1/infinity" :"calc(0px)",
"1px * infinity * infinity" :"calc(infinity * 1px)",
"1px * -infinity * -infinity" :"calc(infinity * 1px)",
"1 * max(INFinity*3px, 0px)" :"calc(infinity * 1px)",
"1 * min(inFInity*4px, 0px)" :"calc(0px)",
"1 * max(nAn*2px, 0px)" :"calc(NaN * 1px)",
"1 * min(nan*3px, 0px)" :"calc(NaN * 1px)",
"1 * clamp(-INFINITY*20px, 0px, infiniTY*10px)" :"calc(0px)",
"1px * max(NaN, min(0,10))" :"calc(NaN * 1px)",
"1px * clamp(NaN, 0, 10)" :"calc(NaN * 1px)",
"1px * max(0, min(10, NaN))" :"calc(NaN * 1px)",
"1px * clamp(0, 10, NaN)" :"calc(NaN * 1px)",
"1px * max(0, min(NaN, 10))" :"calc(NaN * 1px)",
"1px * clamp(0, NaN, 10)" :"calc(NaN * 1px)",
"1px * clamp(-Infinity, 0, infinity)" :"calc(0px)",
"1px * clamp(-inFinity, infinity, 10)" :"calc(10px)",
"1 * min(NaN * 1pt, NaN * 1cm)" :"calc(NaN * 1px)",
"1 * max(NaN * 1cm, NaN * 2Q)" :"calc(NaN * 1px)",
"1 * min(NaN * 2px, NaN * 4em)" :"calc(NaN * 1px)",
"1 * clamp(NaN * 2em, NaN * 4px, NaN * 8pt)" :"clamp(NaN * 1em, NaN * 1px, NaN * 1px)",
};
for (var exp in test_map) {
test_serialization("calc("+exp+")", test_map[exp]);
}
</script>

View file

@ -0,0 +1,94 @@
"use strict";
/* Functions to test serialization of properties.
Each takes (property, testString, expectedSerialization) arguments.
These functions depend on a #target element existing in the page,
and will error if they don't find one.
Note that test_computed_serialization and test_used_serialization
are identical except for assertion messages;
you need to choose properties with the correct resolved values
to test the value stage that you want.
For ease of use, it's recommended that you define and use
the following function in your test page:
function test_serialization(t,s,c,u, {prop}={}) {
test_specified_serialization(prop || 'text-indent', t, s);
test_computed_serialization(prop || 'text-indent', t, c);
if(u) test_used_serialization(prop || 'margin-left', t, u);
}
(swapping the property names for what you're expecting to test)
Then you can write tests easily as:
test_serialization(
'calc(min(1%, 2%) + max(3%, 4%) + 10%)', // test string
'calc(15%)', // expected specified value
'15%', // expected computed value
'15px'); // expected used value
*/
function test_specified_serialization(prop, t, e) {
const el = document.querySelector("#target");
if(!el) throw new Exception("Couldn't find #target element to run tests on.");
test(()=>{
el.style[prop] = '';
el.style[prop] = t;
const tValue = el.style[prop];
assert_not_equals(tValue, '', `'${t}' should be valid in ${prop}.`);
el.style[prop] = '';
el.style[prop] = e;
const eValue = el.style[prop];
assert_not_equals(eValue, '', `'${e}' should be valid in ${prop}.`);
assert_equals(eValue, e, `'${e}' should round-trip exactly in specified values.`);
assert_equals(tValue, e, `'${t}' and '${e}' should serialize the same in specified values.`);
}, `'${t}' as a specified value should serialize as '${e}'.`);
}
function test_computed_serialization(prop, t, e) {
const el = document.querySelector("#target");
if(!el) throw new Exception("Couldn't find #target element to run tests on.");
test(()=>{
el.style[prop] = '';
el.style[prop] = t;
const tValue = getComputedStyle(el)[prop];
assert_not_equals(tValue, '', `'${t}' should be valid in ${prop}.`);
el.style[prop] = '';
el.style[prop] = e;
const eValue = getComputedStyle(el)[prop];
assert_not_equals(eValue, '', `'${e}' should be valid in ${prop}.`);
assert_equals(eValue, e, `'${e}' should round-trip exactly in computed values.`);
assert_equals(tValue, e, `'${t}' and '${e}' should serialize the same in computed values.`);
}, `'${t}' as a computed value should serialize as '${e}'.`);
}
function test_used_serialization(prop, t, e) {
const el = document.querySelector("#target");
if(!el) throw new Exception("Couldn't find #target element to run tests on.");
test(()=>{
el.style[prop] = '';
el.style[prop] = t;
const tValue = getComputedStyle(el)[prop];
assert_not_equals(tValue, '', `'${t}' should be valid in ${prop}.`);
el.style[prop] = '';
el.style[prop] = e;
const eValue = getComputedStyle(el)[prop];
assert_not_equals(eValue, '', `'${e}' should be valid in ${prop}.`);
assert_equals(eValue, e, `'${e}' should round-trip exactly in used values.`);
assert_equals(tValue, e, `'${t}' and '${e}' should serialize the same in used values.`);
}, `'${t}' as a used value should serialize as '${e}'.`);
}