LibWeb: Simplify standalone CSS math functions when used outside calc()
Some checks are pending
CI / Lagom (arm64, Sanitizer_CI, false, macos-15, macOS, Clang) (push) Waiting to run
CI / Lagom (x86_64, Fuzzers_CI, false, ubuntu-24.04, Linux, Clang) (push) Waiting to run
CI / Lagom (x86_64, Sanitizer_CI, false, ubuntu-24.04, Linux, GNU) (push) Waiting to run
CI / Lagom (x86_64, Sanitizer_CI, true, ubuntu-24.04, Linux, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (arm64, macos-15, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (x86_64, ubuntu-24.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run

Math functions like abs(), clamp(), round(), etc, can be used by
themselves in property values, without wrapping them in calc().

Before this change, we were neglecting to run calc simplification on the
generated calculation node trees. By doing that manually after parsing a
standalone math function, we score at least a couple hundred WPT points.
This commit is contained in:
Andreas Kling 2025-04-24 17:33:52 +02:00 committed by Andreas Kling
commit 0553bcb35b
Notes: github-actions[bot] 2025-04-24 18:38:57 +00:00
10 changed files with 1298 additions and 2 deletions

View file

@ -0,0 +1,47 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-values-4/#comp-func">
<link rel="author" title="Xiaocheng Hu" href="mailto:xiaochengh@chromium.org">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../support/computed-testcommon.js"></script>
<div id="container" style="font-size: 20px">
<div id="target"></div>
<div id="reference"></div>
</div>
<script>
const property = 'letter-spacing';
function test_length_equals(value, expected) {
const reference = document.getElementById('reference');
reference.style[property] = '';
reference.style[property] = expected;
const computed = getComputedStyle(reference)[property];
test_computed_value(property, value, computed);
}
test_length_equals('clamp(10px, 20px, 30px)', '20px');
test_length_equals('clamp(10px, 5px, 30px)', '10px');
test_length_equals('clamp(10px, 35px, 30px)', '30px');
test_length_equals('clamp(10px, 35px , 30px)', '30px');
test_length_equals('clamp(10px, 35px /*foo*/, 30px)', '30px');
test_length_equals('clamp(10px /* foo */ , 35px, 30px)', '30px');
test_length_equals('clamp(10px , 35px, 30px)', '30px');
// clamp(MIN, VAL, MAX) is identical to max(MIN, min(VAL, MAX)),
// so MIN wins over MAX if they are in the wrong order.
test_length_equals('clamp(30px, 100px, 20px)', '30px');
// also test with negative values
test_length_equals('clamp(-30px, -20px, -10px)', '-20px');
test_length_equals('clamp(-30px, -100px, -10px)', '-30px');
test_length_equals('clamp(-30px, 100px, -10px)', '-10px');
test_length_equals('clamp(-10px, 100px, -30px)', '-10px');
test_length_equals('clamp(-10px, -100px, -30px)', '-10px');
// and negating the result of clamp
test_length_equals('calc(0px + clamp(10px, 20px, 30px))', '20px');
test_length_equals('calc(0px - clamp(10px, 20px, 30px))', '-20px');
test_length_equals('calc(0px + clamp(30px, 100px, 20px))', '30px');
test_length_equals('calc(0px - clamp(30px, 100px, 20px))', '-30px');
</script>

View file

@ -0,0 +1,57 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-values-4/#comp-func">
<link rel="help" href="https://drafts.csswg.org/css-values-4/#angles">
<link rel="help" href="https://drafts.csswg.org/css-values-4/#calc-type-checking">
<link rel="author" title="Xiaocheng Hu" href="mailto:xiaochengh@chromium.org">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../support/numeric-testcommon.js"></script>
<div id="target"></div>
<div id="reference"></div>
<script>
function test_angle_equals(value, expected) {
test_math_used(value, expected, {type: "angle"});
}
// Identity tests
test_angle_equals('min(1deg)', '1deg');
test_angle_equals('min(1grad)', '1grad');
test_angle_equals('min(1rad)', '1rad');
test_angle_equals('min(1turn)', '1turn');
test_angle_equals('max(1deg)', '1deg');
test_angle_equals('max(1grad)', '1grad');
test_angle_equals('max(1rad)', '1rad');
test_angle_equals('max(1turn)', '1turn');
// Comparisons between same units
test_angle_equals('min(1deg, 2deg)', '1deg');
test_angle_equals('min(1grad, 2grad)', '1grad');
test_angle_equals('min(1rad, 2rad)', '1rad');
test_angle_equals('min(1turn, 2turn)', '1turn');
test_angle_equals('max(1deg, 2deg)', '2deg');
test_angle_equals('max(1grad, 2grad)', '2grad');
test_angle_equals('max(1rad, 2rad)', '2rad');
test_angle_equals('max(1turn, 2turn)', '2turn');
// Comparisons between different units
test_angle_equals('min(90deg, 0.26turn)', '90deg');
test_angle_equals('min(1.57rad, 95deg)', '1.57rad');
test_angle_equals('max(91deg, 0.25turn)', '91deg');
test_angle_equals('max(1.58rad, 90deg)', '1.58rad');
// Nestings
test_angle_equals('min(270deg, max(0.25turn, 3.14rad))', '3.14rad');
test_angle_equals('max(0.25turn, min(270deg, 3.14rad))', '3.14rad');
// General calculations
test_angle_equals('calc(min(90deg, 1.58rad) + 0.125turn)', '135deg');
test_angle_equals('calc(min(90deg, 1.58rad) - 0.125turn)', '45deg');
test_angle_equals('calc(min(90deg, 1.58rad) * 1.5', '135deg');
test_angle_equals('calc(min(90deg, 1.58rad) / 2', '45deg');
test_angle_equals('calc(max(90deg, 1.56rad) + 0.125turn', '135deg');
test_angle_equals('calc(max(90deg, 1.56rad) - 0.125turn)', '45deg');
test_angle_equals('calc(max(90deg, 1.56rad) * 1.5', '135deg');
test_angle_equals('calc(max(90deg, 1.56rad) / 2', '45deg');
test_angle_equals('calc(min(90deg, 1.58rad) + max(0.125turn, 49grad))', '135deg');
test_angle_equals('calc(min(90deg, 1.58rad) - max(0.25turn, 99grad))', '0deg');
</script>

View file

@ -0,0 +1,218 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-values-4/#round-func">
<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 style="width: 75px;">
<div id="target"></div>
</div>
<script>
// Simple tests
test_math_used('round(10,10)', '10', {type:'number'});
test_math_used('mod(1,1)', '0', {type:'number'});
test_math_used('rem(1,1)', '0', {type:'number'});
// Test basic round
test_math_used('calc(round(100,10))', '100', {type:'number'});
test_math_used('calc(round(up, 101,10))', '110', {type:'number'});
test_math_used('calc(round(down, 106,10))', '100', {type:'number'});
test_math_used('calc(round(to-zero, 105, 10))', '100', {type:'number'});
test_math_used('calc(round(to-zero, -105, 10))', '-100', {type:'number'});
test_math_used('calc(round(-100, 10))', '-100', {type:'number'});
test_math_used('calc(round(up, -103, 10))', '-100', {type:'number'});
// Test round when first number is a multiple of the second number.
for (let number of [0, 5, -5, 10, -10, 20, -20]) {
test_math_used(`round(up, ${number}, 5)`, `${number}`, {type:'number'});
test_math_used(`round(down, ${number}, 5)`, `${number}`, {type:'number'});
test_math_used(`round(nearest, ${number}, 5)`, `${number}`, {type:'number'});
test_math_used(`round(to-zero, ${number}, 5)`, `${number}`, {type:'number'});
}
// Test basic mod/rem
test_math_used('mod(18,5)', '3', {type:'number'});
test_math_used('rem(18,5)', '3', {type:'number'});
test_math_used('mod(-140,-90)', '-50', {type:'number'});
test_math_used('mod(-18,5)', '2', {type:'number'});
test_math_used('rem(-18,5)', '-3', {type:'number'});
test_math_used('mod(140,-90)', '-40', {type:'number'});
test_math_used('rem(140,-90)', '50', {type:'number'});
// Test basic calculations
test_math_used('calc(round(round(100,10), 10))', '100', {type:'number'});
test_math_used('calc(round(up, round(100,10) + 1,10))', '110', {type:'number'});
test_math_used('calc(round(down, round(100,10) + 2 * 3,10))', '100', {type:'number'});
test_math_used('calc(round(to-zero,round(100,10) * 2 - 95, 10))', '100', {type:'number'});
test_math_used('calc(round(round(100,10)* -1,10))', '-100', {type:'number'});
test_math_used('calc(round(up, -103 + -103 / -103 - 1,10))', '-100', {type:'number'});
test_math_used('calc(mod(18,5) * 2 + mod(17,5))', '8', {type:'number'});
test_math_used('calc(rem(mod(18,5),5))', '3', {type:'number'});
test_math_used('calc(rem(mod(18,5),mod(17,5)))', '1', {type:'number'});
test_math_used('calc(mod(-140,-90))', '-50', {type:'number'});
test_math_used('calc(mod(rem(1,18)* -1,5))', '4', {type:'number'});
// Type check
test_math_used('round(10px,6px)', '12px');
test_math_used('round(10cm,6cm)', '12cm');
test_math_used('round(10mm,6mm)', '12mm');
test_math_used('round(10Q, 6Q)', '12Q');
test_math_used('round(10in,6in)', '12in');
test_math_used('round(10pc,6pc)', '12pc');
test_math_used('round(10pt,6pt)', '12pt');
test_math_used('round(10em,6em)', '12em');
test_math_used('round(10ex,6ex)', '12ex');
test_math_used('round(10ch,6ch)', '12ch');
test_math_used('round(10rem,6rem)', '12rem');
test_math_used('round(10vh,6vh)', '12vh');
test_math_used('round(10vw,6vw)', '12vw');
test_math_used('round(10vmin,6vmin)', '12vmin');
test_math_used('round(10vmax,6vmax)', '12vmax');
test_math_used('round(10s,6s)', '12s', {type:'time'});
test_math_used('round(10ms,6ms)', '12ms', {type:'time'});
test_math_used('round(10deg,6deg)', '12deg', {type:'angle', approx:0.1});
test_math_used('round(10grad,6grad)', '12grad', {type:'angle', approx:0.1});
test_math_used('round(10rad,6rad)', '12rad',{type:'angle', approx:0.1});
test_math_used('round(10turn,6turn)', '12turn',{type:'angle', approx:0.1});
test_math_used('mod(10px,6px)', '4px');
test_math_used('mod(10cm,6cm)', '4cm');
test_math_used('mod(10mm,6mm)', '4mm');
test_math_used('mod(10Q, 6Q)', '4Q');
test_math_used('mod(10in,6in)', '4in');
test_math_used('mod(10pc,6pc)', '4pc');
test_math_used('mod(10em,6em)', '4em');
test_math_used('mod(10ex,6ex)', '4ex');
test_math_used('mod(10ch,6ch)', '4ch');
test_math_used('mod(10rem,6rem)', '4rem');
test_math_used('mod(10vh,6vh)', '4vh', {approx: 0.1});
test_math_used('mod(10vw,6vw)', '4vw', {approx: 0.1});
test_math_used('mod(10vmin,6vmin)', '4vmin', {approx: 0.1});
test_math_used('mod(10vmax,6vmax)', '4vmax', {approx: 0.1});
test_math_used('mod(10s,6s)', '4s', {type:'time'});
test_math_used('mod(10ms,6ms)', '4ms', {type:'time'});
test_math_used('mod(10deg,6deg)', '4deg', {type:'angle', approx:0.1});
test_math_used('mod(10grad,6grad)', '4grad', {type:'angle', approx:0.1});
test_math_used('mod(10rad,6rad)', '4rad',{type:'angle', approx:0.1});
test_math_used('mod(10turn,6turn)', '4turn',{type:'angle', approx:0.1});
test_math_used('rem(10px,6px)', '4px');
test_math_used('rem(10cm,6cm)', '4cm');
test_math_used('rem(10mm,6mm)', '4mm');
test_math_used('rem(10Q, 6Q)', '4Q');
test_math_used('rem(10in,6in)', '4in');
test_math_used('rem(10pc,6pc)', '4pc');
test_math_used('rem(10em,6em)', '4em');
test_math_used('rem(10ex,6ex)', '4ex');
test_math_used('rem(10ch,6ch)', '4ch');
test_math_used('rem(10rem,6rem)', '4rem');
test_math_used('rem(10vh,6vh)', '4vh', {approx: 0.1});
test_math_used('rem(10vw,6vw)', '4vw', {approx: 0.1});
test_math_used('rem(10vmin,6vmin)', '4vmin', {approx: 0.1});
test_math_used('rem(10vmax,6vmax)', '4vmax', {approx: 0.1});
test_math_used('rem(10s,6s)', '4s', {type:'time'});
test_math_used('rem(10ms,6ms)', '4ms', {type:'time'});
test_math_used('rem(10deg,6deg)', '4deg', {type:'angle', approx:0.1});
test_math_used('rem(10grad,6grad)', '4grad', {type:'angle', approx:0.1});
test_math_used('rem(10rad,6rad)', '4rad',{type:'angle', approx:0.1});
test_math_used('rem(10turn,6turn)', '4turn',{type:'angle', approx:0.1});
//Test percentage and mixed units
test_math_used('round(10%,1px)', '8px');
test_math_used('round(10%,5px)', '10px');
test_math_used('round(2rem,5px)', '30px');
test_math_used('round(100px,1rem)', '96px');
test_math_used('round(10s,6000ms)', '12s', {type:'time'});
test_math_used('round(10000ms,6s)', '12s', {type:'time'});
test_math_used('mod(10%,1px)', '0.5px');
test_math_used('mod(10%,5px)', '2.5px');
test_math_used('mod(2rem,5px)', '2px');
test_math_used('mod(100px,1rem)', '4px');
test_math_used('mod(10s,6000ms)', '4s', {type:'time'});
test_math_used('mod(10000ms,6s)', '4s', {type:'time'});
test_math_used('mod(18px,100% / 15)', '3px', {approx: 0.1});
test_math_used('mod(-18px,100% / 10)', '4.5px');
test_math_used('mod(18%,5%)', '3%');
test_math_used('mod(-19%,5%)', '1%');
test_math_used('mod(18vw,5vw)', '3vw');
test_math_used('mod(-18vw,5vw)', '2vw', {approx: 0.1});
test_math_used('rem(10%,1px)', '0.5px');
test_math_used('rem(10%,5px)', '2.5px');
test_math_used('rem(2rem,5px)', '2px');
test_math_used('rem(100px,1rem)', '4px');
test_math_used('rem(10s,6000ms)', '4s', {type:'time'});
test_math_used('rem(10000ms,6s)', '4s', {type:'time'});
test_math_used('rem(18px,100% / 15)', '3px', {approx: 0.1});
test_math_used('rem(-18px,100% / 15)', '-3px', {approx: 0.1});
test_math_used('rem(18vw,5vw)', '3vw');
test_math_used('rem(-18vw,5vw)', '-3vw');
test_math_used('calc(round(1px + 0%, 1px + 0%))', '1px');
test_math_used('calc(mod(3px + 0%, 2px + 0%))', '1px');
test_math_used('calc(rem(3px + 0%, 2px + 0%))', '1px');
test_math_used('round(1px + 0%, 1px)', '1px');
test_math_used('mod(3px + 0%, 2px)', '1px');
test_math_used('rem(3px + 0%, 2px)', '1px');
// In round(A, B), if B is 0, the result is NaN. If A and B are both infinite, the result is NaN.
// In mod(A, B) or rem(A, B), if B is 0, the result is NaN. If A is infinite, the result is NaN.
for (let operator of ['round', 'mod', 'rem']) {
test_math_used(`${operator}(0, 0)`, 'calc(NaN)', {type: 'number'});
test_math_used(`${operator}(-0, 0)`, 'calc(NaN)', {type: 'number'});
test_math_used(`${operator}(Infinity, 0)`, 'calc(NaN)', {type: 'number'});
test_math_used(`${operator}(-Infinity, 0)`, 'calc(NaN)', {type: 'number'});
test_math_used(`${operator}(-4, 0)`, 'calc(NaN)', {type: 'number'});
test_math_used(`${operator}(4, 0)`, 'calc(NaN)', {type: 'number'});
test_math_used(`${operator}(Infinity, Infinity)`, 'calc(NaN)', {type: 'number'});
test_math_used(`${operator}(-Infinity, -Infinity)`, 'calc(NaN)', {type: 'number'});
test_math_used(`${operator}(Infinity, -Infinity)`, 'calc(NaN)', {type: 'number'});
test_math_used(`${operator}(-Infinity, Infinity)`, 'calc(NaN)', {type: 'number'});
}
// In round(A, B), if A is infinite but B is finite, the result is the same infinity.
for (let roundingStrategy of ['up', 'down', 'nearest', 'to-zero']) {
test_math_used(`round(${roundingStrategy}, Infinity, 4)`, 'calc(Infinity)', {type: 'number'});
test_math_used(`round(${roundingStrategy}, -Infinity, 4)`, 'calc(-Infinity)', {type: 'number'});
test_math_used(`round(${roundingStrategy}, Infinity, -4)`, 'calc(Infinity)', {type: 'number'});
test_math_used(`round(${roundingStrategy}, -Infinity, -4)`, 'calc(-Infinity)', {type: 'number'});
}
// If A is finite but B is infinite, the result depends on the <rounding-strategy> and the sign of A:
// nearest & to-zero: If A is positive or 0⁺, return 0⁺. Otherwise, return 0⁻.
for (let roundingStrategy of ['nearest', 'to-zero']) {
test_math_used(`round(${roundingStrategy}, 0, Infinity)`, '0', {type: 'number'});
test_math_used(`round(${roundingStrategy}, 4, Infinity)`, '0', {type: 'number'});
test_math_used(`round(${roundingStrategy}, -0, Infinity)`, 'calc(-0)', {type: 'number'});
test_math_used(`round(${roundingStrategy}, -4, Infinity)`, 'calc(-0)', {type: 'number'});
test_math_used(`round(${roundingStrategy}, 0, -Infinity)`, '0', {type: 'number'});
test_math_used(`round(${roundingStrategy}, 4, -Infinity)`, '0', {type: 'number'});
test_math_used(`round(${roundingStrategy}, -0, -Infinity)`, 'calc(-0)', {type: 'number'});
test_math_used(`round(${roundingStrategy}, -4, -Infinity)`, 'calc(-0)', {type: 'number'});
}
// up: If A is positive (not zero), return +∞. If A is 0⁺, return 0⁺. Otherwise, return 0⁻.
test_math_used('round(up, 1, Infinity)', 'calc(Infinity)', {type: 'number'});
test_math_used('round(up, 0, Infinity)', '0', {type: 'number'});
test_math_used('round(up, -1, Infinity)', 'calc(-0)', {type: 'number'});
test_math_used('round(up, 1, -Infinity)', 'calc(Infinity)', {type: 'number'});
test_math_used('round(up, 0, -Infinity)', '0', {type: 'number'});
test_math_used('round(up, -1, -Infinity)', 'calc(-0)', {type: 'number'});
// down: If A is negative (not zero), return −∞. If A is 0⁻, return 0⁻. Otherwise, return 0⁺.
test_math_used('round(down, 1, Infinity)', 'calc(-0)', {type: 'number'});
test_math_used('round(down, 0, Infinity)', '0', {type: 'number'});
test_math_used('round(down, -1, Infinity)', 'calc(-Infinity)', {type: 'number'});
test_math_used('round(down, 1, -Infinity)', 'calc(-0)', {type: 'number'});
test_math_used('round(down, 0, -Infinity)', '0', {type: 'number'});
test_math_used('round(down, -1, -Infinity)', 'calc(-Infinity)', {type: 'number'});
// In mod(A, B) only, if B is infinite and A has opposite sign to B (including an oppositely-signed zero), the result is NaN.
test_math_used('mod(-0, Infinity)', 'calc(NaN)', {type: 'number'});
test_math_used('mod(0, -Infinity)', 'calc(NaN)', {type: 'number'});
test_math_used('mod(-4, Infinity)', 'calc(NaN)', {type: 'number'});
test_math_used('mod(4, -Infinity)', 'calc(NaN)', {type: 'number'});
</script>

View file

@ -0,0 +1,240 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-values-4/#comp-func">
<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="container" style="font-size: 20px; width: 100px">
<div id="target"></div>
</div>
<script>
function test_zero(expression, { is_negative }) {
test_math_used(`calc(${expression})`, '0', {type:'integer'});
// to test zero sign, make it to negative infinity and clamp it between -1 and 1
test_math_used(`clamp(-1, calc( 1 / sign(${expression})), 1)`, (is_negative)? '-1' : '1', {type:'integer'});
}
function test_length_equals(value, expected, msgExtra) {
test_math_used(value, expected, {msgExtra, type: 'integer'});
}
// Identity tests
test_math_used('abs(1)', '1', {type:'integer'});
test_math_used('sign(1)', '1', {type:'integer'});
test_math_used('abs(-1)', '1', {type:'integer'});
test_math_used('sign(-1)', '-1', {type:'integer'});
// Nestings
test_math_used('abs(sign(1))', '1', {type:'integer'});
test_math_used('abs(sign(sign(1)))', '1', {type:'integer'});
test_math_used('sign(sign(sign(1) + sign(1)))', '1', {type:'integer'});
// General calculations
test_math_used('calc(abs(0.1 + 0.2) + 0.05)', '0.35', {type:'number', approx:0.1});
test_math_used('calc(sign(0.1 + 0.2) - 0.05)', '0.95', {type:'number', approx:0.1});
test_math_used('calc(abs(0.1 + 0.2) * 2)', '0.6', {type:'number', approx:0.1});
test_math_used('calc(abs(sign(0.1) + 0.2) / 2)', '0.6', {type:'number', approx:0.1});
test_math_used('calc(abs(0.1 + 0.2) * -2)', '-0.6', {type:'number', approx:0.1});
test_math_used('calc(sign(0.1 - 0.2) - 0.05)', '-1.05', {type:'number', approx:0.1});
test_math_used('calc(sign(1) + sign(1) - 0.05)', '1.95', {type:'number', approx:0.1});
// Test with <length-percentage>
test_math_used('abs(10px)', '10px', {type:'length'});
test_math_used('abs(10%)', '10px', {type:'length'});
test_math_used('abs(10px + 10%)', '20px', {type:'length'});
test_math_used('calc(10px + abs(10%))', '20px', {type:'length'});
test_math_used('abs(-10px)', '10px', {type:'length'});
test_math_used('abs(-10%)', '10px', {type:'length'});
// (20px + 1px) * (sign(20px - 10px - 10px) + 1)
test_math_used('calc((1em + 1px) * (sign(1em - 10px - 10%) + 1))', '21px', {type:'length'});
// Test sign for zero
test_zero('calc(sign(-0))', {is_negative: true});
test_zero('calc(sign(0))', {is_negative: false});
// Test with NaN and inf
test_math_used('abs(infinity)', 'calc(infinity)', {type:'number'});
test_math_used('abs(-infinity)', 'calc(infinity)', {type:'number'});
test_math_used('abs(NaN)', 'calc(NaN)', {type:'number'});
// Test abs/sign with negate
test_math_used('calc(20 - abs(-10))', '10', {type:'number'});
test_math_used('calc(20 - abs(10))', '10', {type:'number'});
test_math_used('calc(10 - abs(10 - abs(-30))', '-10', {type:'number'});
test_math_used('calc(2 - sign(1))', '1', {type:'number'});
test_math_used('calc(2 - sign(-1))', '3', {type:'number'});
test_math_used('calc(2 - sign(1 - sign(-1)))', '1', {type:'number'});
test_math_used('calc(10 - abs(20 - sign(2 - abs(-20))))', '-11', {type:'number'});
// Type checking sign
test_math_used('sign(1px)', '1', {type:'integer'});
test_math_used('sign(1cm)', '1', {type:'integer'});
test_math_used('sign(1mm)', '1', {type:'integer'});
test_math_used('sign(1Q)', '1', {type:'integer'});
test_math_used('sign(1in)', '1', {type:'integer'});
test_math_used('sign(1pc)', '1', {type:'integer'});
test_math_used('sign(1pt)', '1', {type:'integer'});
test_math_used('sign(1em)', '1', {type:'integer'});
test_math_used('sign(1ex)', '1', {type:'integer'});
test_math_used('sign(1ch)', '1', {type:'integer'});
test_math_used('sign(1rem)', '1', {type:'integer'});
test_math_used('sign(1vh)', '1', {type:'integer'});
test_math_used('sign(1vw)', '1', {type:'integer'});
test_math_used('sign(1vmin)', '1', {type:'integer'});
test_math_used('sign(1vmax)', '1', {type:'integer'});
test_math_used('sign(-1px)', '-1', {type:'integer'});
test_math_used('sign(-1cm)', '-1', {type:'integer'});
test_math_used('sign(-1mm)', '-1', {type:'integer'});
test_math_used('sign(-1Q)', '-1', {type:'integer'});
test_math_used('sign(-1in)', '-1', {type:'integer'});
test_math_used('sign(-1pc)', '-1', {type:'integer'});
test_math_used('sign(-1pt)', '-1', {type:'integer'});
test_math_used('sign(-1em)', '-1', {type:'integer'});
test_math_used('sign(-1ex)', '-1', {type:'integer'});
test_math_used('sign(-1ch)', '-1', {type:'integer'});
test_math_used('sign(-1rem)', '-1', {type:'integer'});
test_math_used('sign(-1vh)', '-1', {type:'integer'});
test_math_used('sign(-1vw)', '-1', {type:'integer'});
test_math_used('sign(-1vmin)', '-1', {type:'integer'});
test_math_used('sign(-1vmax)', '-1', {type:'integer'});
test_math_used('sign(1s)', '1', {type:'integer'});
test_math_used('sign(1ms)', '1', {type:'integer'});
test_math_used('sign(-1s)', '-1', {type:'integer'});
test_math_used('sign(-1ms)', '-1', {type:'integer'});
test_math_used('sign(1deg)', '1', {type:'integer'});
test_math_used('sign(1grad)', '1', {type:'integer'});
test_math_used('sign(1rad)', '1', {type:'integer'});
test_math_used('sign(1turn)', '1', {type:'integer'});
test_math_used('sign(-1deg)', '-1', {type:'integer'});
test_math_used('sign(-1grad)', '-1', {type:'integer'});
test_math_used('sign(-1rad)', '-1', {type:'integer'});
test_math_used('sign(-1turn)', '-1', {type:'integer'});
test_zero('sign(0px)', {is_negative: false});
test_zero('sign(0cm)', {is_negative: false});
test_zero('sign(0mm)', {is_negative: false});
test_zero('sign(0Q)', {is_negative: false});
test_zero('sign(0in)', {is_negative: false});
test_zero('sign(0pc)', {is_negative: false});
test_zero('sign(0pt)', {is_negative: false});
test_zero('sign(0em)', {is_negative: false});
test_zero('sign(0ex)', {is_negative: false});
test_zero('sign(0ch)', {is_negative: false});
test_zero('sign(0rem)', {is_negative: false});
test_zero('sign(0vh)', {is_negative: false});
test_zero('sign(0vw)', {is_negative: false});
test_zero('sign(0vmin)', {is_negative: false});
test_zero('sign(0vmax)', {is_negative: false});
test_zero('sign(-0px)', {is_negative: true});
test_zero('sign(-0cm)', {is_negative: true});
test_zero('sign(-0mm)', {is_negative: true});
test_zero('sign(-0Q)', {is_negative: true});
test_zero('sign(-0in)', {is_negative: true});
test_zero('sign(-0pc)', {is_negative: true});
test_zero('sign(-0pt)', {is_negative: true});
test_zero('sign(-0em)', {is_negative: true});
test_zero('sign(-0ex)', {is_negative: true});
test_zero('sign(-0ch)', {is_negative: true});
test_zero('sign(-0rem)', {is_negative: true});
test_zero('sign(-0vh)', {is_negative: true});
test_zero('sign(-0vw)', {is_negative: true});
test_zero('sign(-0vmin)', {is_negative: true});
test_zero('sign(-0vmax)', {is_negative: true});
test_zero('sign(0s)', {is_negative: false});
test_zero('sign(0ms)', {is_negative: false});
test_zero('sign(-0s)', {is_negative: true});
test_zero('sign(-0ms)', {is_negative: true});
test_zero('sign(0deg)', {is_negative: false});
test_zero('sign(0grad)', {is_negative: false});
test_zero('sign(0rad)', {is_negative: false});
test_zero('sign(0turn)', {is_negative: false});
test_zero('sign(-0deg)', {is_negative: true});
test_zero('sign(-0grad)', {is_negative: true});
test_zero('sign(-0rad)', {is_negative: true});
test_zero('sign(-0turn)', {is_negative: true});
// Type checking abs
test_math_used('abs(1px)', '1px');
test_math_used('abs(1cm)', '1cm');
test_math_used('abs(1mm)', '1mm');
test_math_used('abs(1Q)', '1Q');
test_math_used('abs(1in)', '1in');
test_math_used('abs(1pc)', '1pc');
test_math_used('abs(1pt)', '1pt');
test_math_used('abs(1em)', '1em');
test_math_used('abs(1ex)', '1ex');
test_math_used('abs(1ch)', '1ch');
test_math_used('abs(1rem)', '1rem');
test_math_used('abs(1vh)', '1vh');
test_math_used('abs(1vw)', '1vw');
test_math_used('abs(1vmin)', '1vmin');
test_math_used('abs(1vmax)', '1vmax');
test_math_used('abs(-1px)', '1px');
test_math_used('abs(-1cm)', '1cm');
test_math_used('abs(-1mm)', '1mm');
test_math_used('abs(-1Q)', '1Q');
test_math_used('abs(-1in)', '1in');
test_math_used('abs(-1pc)', '1pc');
test_math_used('abs(-1pt)', '1pt');
test_math_used('abs(-1em)', '1em');
test_math_used('abs(-1ex)', '1ex');
test_math_used('abs(-1ch)', '1ch');
test_math_used('abs(-1rem)', '1rem');
test_math_used('abs(-1vh)', '1vh');
test_math_used('abs(-1vw)', '1vw');
test_math_used('abs(-1vmin)', '1vmin');
test_math_used('abs(-1vmax)', '1vmax');
test_math_used('abs(1s)', '1s', {type:'time'});
test_math_used('abs(1ms)', '1ms', {type:'time'});
test_math_used('abs(-1s)', '1s', {type:'time'});
test_math_used('abs(-1ms)', '1ms', {type:'time'});
test_math_used('abs(1deg)', '1deg', {type:'angle', approx:0.001});
test_math_used('abs(1grad)', '1grad', {type:'angle', approx:0.001});
test_math_used('abs(1rad)', '1rad', {type:'angle', approx:0.001});
test_math_used('abs(1turn)', '1turn', {type:'angle', approx:0.001});
test_math_used('abs(-1deg)', '1deg', {type:'angle', approx:0.001});
test_math_used('abs(-1grad)', '1grad', {type:'angle', approx:0.001});
test_math_used('abs(-1rad)', '1rad', {type:'angle', approx:0.001});
test_math_used('abs(-1turn)', '1turn', {type:'angle', approx:0.001});
// with relative length
document.getElementById('container').style.fontSize = '10px';
test_length_equals('sign(10px - 1em)', '0', 'fontSize=10px');
test_length_equals('sign(10px - 2em)', '-1', 'fontSize=10px');
document.getElementById('container').style.fontSize = '20px';
test_math_used('calc(sign(10%) * 100px)', '100px');
// Basic tests (just px and em) for sign() interaction with units not
// otherwise used by the property.
test_math_used('calc(2.5 - sign(41px - 2em) / 2)', '2', {type:'number'});
test_math_used('calc(2.5 - sign(40px - 2em) / 2)', '2.5', {type:'number'});
test_math_used('calc(2.5 - sign(39px - 2em) / 2)', '3', {type:'number'});
test_math_used('calc(3 + sign(42px - 2em))', '4', {type:'integer'});
test_math_used('calc(3 + sign(40px - 2em))', '3', {type:'integer'});
test_math_used('calc(3 + sign(38px - 2em))', '2', {type:'integer'});
test_math_used('calc(50px + 100px * sign(42px - 2em))', '150px', {type:'length'});
test_math_used('calc(50px + 100px * sign(40px - 2em))', '50px', {type:'length'});
test_math_used('calc(50px + 100px * sign(38px - 2em))', '-50px', {type:'length'});
test_math_used('calc(90deg + 30deg * sign(42px - 2em))', '120deg', {type:'angle'});
test_math_used('calc(90deg + 30deg * sign(40px - 2em))', '90deg', {type:'angle'});
test_math_used('calc(90deg + 30deg * sign(38px - 2em))', '60deg', {type:'angle'});
test_math_used('calc(5s + 15s * sign(42px - 2em))', '20s', {type:'time'});
test_math_used('calc(5s + 15s * sign(40px - 2em))', '5s', {type:'time'});
test_math_used('calc(5s + 15s * sign(38px - 2em))', '-10s', {type:'time'});
test_math_used('calc(100dpi + 20dpi * sign(42px - 2em))', '120dpi', {type:'resolution'});
test_math_used('calc(100dpi + 20dpi * sign(40px - 2em))', '100dpi', {type:'resolution'});
test_math_used('calc(100dpi + 20dpi * sign(38px - 2em))', '80dpi', {type:'resolution'});
test_math_used('calc(3fr + 1fr * sign(42px - 2em))', '4fr', {type:'flex'});
test_math_used('calc(3fr + 1fr * sign(40px - 2em))', '3fr', {type:'flex'});
test_math_used('calc(3fr + 1fr * sign(38px - 2em))', '2fr', {type:'flex'});
// repeat a few with px and rem
test_math_used('calc(2.5 - sign(33px - 2rem) / 2)', '2', {type:'number'});
test_math_used('calc(2.5 - sign(32px - 2rem) / 2)', '2.5', {type:'number'});
test_math_used('calc(2.5 - sign(31px - 2rem) / 2)', '3', {type:'number'});
test_math_used('calc(50px + 100px * sign(34px - 2rem))', '150px', {type:'length'});
test_math_used('calc(50px + 100px * sign(32px - 2rem))', '50px', {type:'length'});
test_math_used('calc(50px + 100px * sign(30px - 2rem))', '-50px', {type:'length'});
</script>