LibWeb: Skip inverted non-canonical values when simplifying product node

This matches the logic with the non-inverted children.

Gains us 2 WPT tests.
This commit is contained in:
Callum Law 2025-07-31 00:14:04 +12:00 committed by Sam Atkins
commit c3b1013018
Notes: github-actions[bot] 2025-08-06 13:56:43 +00:00
17 changed files with 387 additions and 44 deletions

View file

@ -3304,32 +3304,39 @@ NonnullRefPtr<CalculationNode const> simplify_a_calculation_tree(CalculationNode
// its childs value), expressed in the results canonical unit.
Optional<CalculatedStyleValue::CalculationResult> accumulated_result;
bool is_valid = true;
for (auto const& child : children) {
if (child->type() == CalculationNode::Type::Numeric) {
auto const& numeric_child = as<NumericCalculationNode>(*child);
auto accumulate = [&accumulated_result, &resolution_context, &context](NumericCalculationNode const& numeric_child, bool invert) {
auto child_type = numeric_child.numeric_type();
if (!child_type.has_value()) {
is_valid = false;
break;
}
if (!child_type.has_value())
return false;
// FIXME: The spec doesn't handle unresolved percentages here, but if we don't exit when we see one,
// we'll get a wrongly-typed value after multiplying the types.
// Same goes for other numerics with non-canonical units.
// Spec bug: https://github.com/w3c/csswg-drafts/issues/11588
if ((numeric_child.value().has<Percentage>() && context.percentages_resolve_as.has_value())
|| !numeric_child.is_in_canonical_unit()) {
is_valid = false;
break;
}
if ((numeric_child.value().has<Percentage>() && context.percentages_resolve_as.has_value()) || !numeric_child.is_in_canonical_unit())
return false;
auto child_value = CalculatedStyleValue::CalculationResult::from_value(numeric_child.value(), resolution_context, child_type);
if (accumulated_result.has_value()) {
if (invert)
child_value.invert();
if (accumulated_result.has_value())
accumulated_result->multiply_by(child_value);
} else {
accumulated_result = move(child_value);
}
if (!accumulated_result->type().has_value()) {
else
accumulated_result = child_value;
if (!accumulated_result->type().has_value())
return false;
return true;
};
for (auto const& child : children) {
if (child->type() == CalculationNode::Type::Numeric) {
if (!accumulate(as<NumericCalculationNode>(*child), false)) {
is_valid = false;
break;
}
@ -3337,26 +3344,7 @@ NonnullRefPtr<CalculationNode const> simplify_a_calculation_tree(CalculationNode
}
if (child->type() == CalculationNode::Type::Invert) {
auto const& invert_child = as<InvertCalculationNode>(*child);
if (invert_child.child().type() != CalculationNode::Type::Numeric) {
is_valid = false;
break;
}
auto const& grandchild = as<NumericCalculationNode>(invert_child.child());
auto child_type = child->numeric_type();
if (!child_type.has_value()) {
is_valid = false;
break;
}
auto child_value = CalculatedStyleValue::CalculationResult::from_value(grandchild.value(), resolution_context, grandchild.numeric_type());
child_value.invert();
if (accumulated_result.has_value()) {
accumulated_result->multiply_by(child_value);
} else {
accumulated_result = move(child_value);
}
if (!accumulated_result->type().has_value()) {
if (invert_child.child().type() != CalculationNode::Type::Numeric || !accumulate(as<NumericCalculationNode>(invert_child.child()), true)) {
is_valid = false;
break;
}

View file

@ -0,0 +1,18 @@
Harness status: OK
Found 12 tests
2 Pass
10 Fail
Pass box should be orange if the calc between px-em in @media was correct
Fail box should be orange if the calc between vh+em in @media was correct
Fail box should be orange if the calc between vw-em in @media was correct
Fail box should be orange if the calc between vw+vh in @media was correct
Fail box should be orange if the calc between vh+px in @media was correct
Fail box should be orange if the calc between vw+px in @media was correct
Pass box should be orange if the calc between px/em*em in @media was correct
Fail box should be orange if the calc between vh*em in @media was correct
Fail box should be orange if the calc between vh*vw/em*px/vh in @media was correct
Fail box should be orange if the calc between vw/px*vh in @media was correct
Fail box should be orange if the calc between vh*vw/em*px in @media was correct
Fail box should be orange if the calc between vw*vh*px*em/px/px/px in @media was correct

View file

@ -0,0 +1,13 @@
Harness status: OK
Found 7 tests
5 Pass
2 Fail
Fail testing width: calc(5px * 10lh / 1px)
Pass testing width: calc(20% * 0.5em / 1px)
Pass testing width: calc(4px * 4em / 1px)
Fail testing width: calc(400px / 4lh * 1px)
Pass testing width: calc(20% / 0.5em * 1px)
Pass testing width: calc(52px * 1px / 10%)
Pass testing width: calc(100px * 1px / 1px / 1)

View file

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>test calc with mixed units in media queries</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<link rel="help" href="https://www.w3.org/TR/css3-values/#calc-computed-value">
</head>
<body>
<iframe src="./support/mixed-units-01.html" title="px-em" frameborder="0" height="10" width="100"></iframe>
<iframe src="./support/mixed-units-02.html" title="vh+em" frameborder="0" height="10" width="100"></iframe>
<iframe src="./support/mixed-units-03.html" title="vw-em" frameborder="0" height="10" width="100"></iframe>
<iframe src="./support/mixed-units-04.html" title="vw+vh" frameborder="0" height="10" width="100"></iframe>
<iframe src="./support/mixed-units-05.html" title="vh+px" frameborder="0" height="10" width="100"></iframe>
<iframe src="./support/mixed-units-06.html" title="vw+px" frameborder="0" height="10" width="100"></iframe>
<iframe src="./support/mixed-units-07.html" title="px/em*em" frameborder="0" height="10" width="100"></iframe>
<iframe src="./support/mixed-units-08.html" title="vh*em" frameborder="0" height="10" width="100"></iframe>
<iframe src="./support/mixed-units-09.html" title="vh*vw/em*px/vh" frameborder="0" height="10" width="100"></iframe>
<iframe src="./support/mixed-units-10.html" title="vw/px*vh" frameborder="0" height="10" width="100"></iframe>
<iframe src="./support/mixed-units-11.html" title="vh*vw/em*px" frameborder="0" height="10" width="100"></iframe>
<iframe src="./support/mixed-units-12.html" title="vw*vh*px*em/px/px/px" frameborder="0" height="10" width="100"></iframe>
<script>
for (const frame of document.querySelectorAll("iframe")) {
async_test((t) => {
frame.addEventListener("load", t.step_func(() => {
const body = frame.contentWindow.document.body;
const actual = frame.contentWindow.getComputedStyle(body).getPropertyValue("background-color");
assert_equals(actual, "rgb(255, 165, 0)");
t.done();
}));
}, `box should be orange if the calc between ${frame.title} in @media was correct`);
}
</script>
</body>
</html>

View file

@ -0,0 +1,97 @@
<!DOCTYPE html>
<meta charset="UTF-8">
<title>CSS Values Test: computed value of calc() values using multiplication/division with mixed units</title>
<link rel="help" href="https://www.w3.org/TR/css-values-4/#calc-computed-value">
<meta name="flags" content="">
<meta content="This meta-test checks for the resolution and absolutization of computed values with mixed units to 'px' when multiplication/division is used." name="assert">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<style>
html {
font-size: 30px;
}
body {
font-size: 16px;
line-height: 1.25; /* computed value: 20px */
width: 520px;
height: 500px;
margin: 20px;
}
div#target {
height: 100px;
width: 100px;
}
</style>
<div id="target"></div>
<script>
function startTesting() {
var targetElement = document.getElementById("target");
function verifyComputedStyle(property_name, specified_value, expected_value) {
test(function() {
targetElement.style.setProperty(property_name, "initial");
targetElement.style.setProperty(property_name, specified_value);
assert_equals(getComputedStyle(targetElement)[property_name], expected_value);
}, `testing ${property_name}: ${specified_value}`);
}
verifyComputedStyle("width", "calc(5px * 10lh / 1px)", "1000px");
/*
10lh = 200px
5px * 200px / 1px = 1000px^2 / 1px = 1000px
Total = 1000px
*/
verifyComputedStyle("width", "calc(20% * 0.5em / 1px)", "832px");
/*
20% of 520px = 104px
0.5em = 8px
104px * 8px / 1px = 832px^2 / 1px = 832px
Total = 832px
*/
verifyComputedStyle("width", "calc(4px * 4em / 1px)", "256px");
/*
4em = 64px
4px * 64px / 1px = 256px^2 / 1px = 256px
Total = 256px
*/
verifyComputedStyle("width", "calc(400px / 4lh * 1px)", "5px");
/*
4lh = 80px
400px / 80px * 1px = 5 * 1px = 5px
Total = 5px
*/
verifyComputedStyle("width", "calc(20% / 0.5em * 1px)", "13px");
/*
20% of 520px = 104px
0.5em = 8px
104px / 8px * 1px = 13 * 1px = 13px
Total = 13px
*/
verifyComputedStyle("width", "calc(52px * 1px / 10%)", "1px");
/*
10% of 520px = 52px
52px * 1px / 52px = 1px
Total = 1px
*/
verifyComputedStyle("width", "calc(100px * 1px / 1px / 1)", "100px");
}
startTesting();
</script>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background: rgb(0, 0, 255);
}
@media (width: calc(116px - 1em)) {
body {
background: rgb(255, 165, 0);
}
}
</style>
</head>
<body></body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background: rgb(0, 0, 255);
}
@media (width: calc(200vh + 5em)) {
body {
background: rgb(255, 165, 0);
}
}
</style>
</head>
<body></body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background: rgb(0, 0, 255);
}
@media (height: calc(100vw - 5.625em)) {
body {
background: rgb(255, 165, 0);
}
}
</style>
</head>
<body></body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background: rgb(0, 0, 255);
}
@media (width: calc(10vw + 900vh)) {
body {
background: rgb(255, 165, 0);
}
}
</style>
</head>
<body></body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background: rgb(0, 0, 255);
}
@media (width: calc(900vh + 10px)) {
body {
background: rgb(255, 165, 0);
}
}
</style>
</head>
<body></body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background: rgb(0, 0, 255);
}
@media (width: calc(90vw + 10px)) {
body {
background: rgb(255, 165, 0);
}
}
</style>
</head>
<body></body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background: rgb(0, 0, 255);
}
@media (width: calc(100px / 1em * 1em)) {
body {
background: rgb(255, 165, 0);
}
}
</style>
</head>
<body></body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background: rgb(0, 0, 255);
}
@media (width: calc((50vh * 5em) / 4px)) {
body {
background: rgb(255, 165, 0);
}
}
</style>
</head>
<body></body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background: rgb(0, 0, 255);
}
@media (height: calc(50vw / 0.3125em * 10px)) {
body {
background: rgb(255, 165, 0);
}
}
</style>
</head>
<body></body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background: rgb(0, 0, 255);
}
@media (width: calc(10vw / 10px * 1000vh)) {
body {
background: rgb(255, 165, 0);
}
}
</style>
</head>
<body></body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background: rgb(0, 0, 255);
}
@media (width: calc(8000vh * 1vw / 1em * 8px / 40vh)) {
body {
background: rgb(255, 165, 0);
}
}
</style>
</head>
<body></body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background: rgb(0, 0, 255);
}
@media (width: calc(100vw * 10vh * 1px * 0.0625em / 1px / 1px / 1px)) {
body {
background: rgb(255, 165, 0);
}
}
</style>
</head>
<body></body>
</html>