LibWeb: Implement CSS progress() math function

`no-clamp` tests are added in-tree since there aren't any in WPT yet.
This commit is contained in:
Callum Law 2026-06-19 16:30:09 +12:00 committed by Sam Atkins
parent 043adecb13
commit fdca036ff3
13 changed files with 489 additions and 1 deletions

View file

@ -443,6 +443,7 @@
"new-base-60",
"newa",
"nko-cardinal",
"no-clamp",
"no-clip",
"no-close-quote",
"no-common-ligatures",

View file

@ -168,6 +168,27 @@
}
]
},
"progress": {
"parameter-validation": "consistent",
"__comment": "`no-clamp` is implemented ad-hoc in the parser since it's unusual in that it isn't comma separated",
"parameters": [
{
"name": "value",
"type": "<number>|<dimension>|<percentage>",
"required": true
},
{
"name": "start-value",
"type": "<number>|<dimension>|<percentage>",
"required": true
},
{
"name": "end-value",
"type": "<number>|<dimension>|<percentage>",
"required": true
}
]
},
"random": {
"parameter-validation": "consistent",
"parameters": [

View file

@ -320,7 +320,7 @@ static void serialize_a_math_function(StringBuilder& builder, CalculationNode co
// Concatenate all of the results using ", " (comma followed by space), then append the result to s.
// Spec issue: https://github.com/w3c/csswg-drafts/issues/11783
// The three AD-HOCs in this step are mentioned there.
// The four AD-HOCs in this step are mentioned there.
// AD-HOC: Numeric nodes have no children and should serialize directly.
// AD-HOC: calc-operator nodes should also serialize directly, instead of separating their children by commas.#
if (fn.type() == CalculationNode::Type::Numeric || fn.is_calc_operator_node()) {
@ -334,6 +334,14 @@ static void serialize_a_math_function(StringBuilder& builder, CalculationNode co
builder.append(CSS::to_string(rounding_strategy));
first = false;
}
// AD-HOC: For `progress()` we serialize 'no-clamp' separately since it's not a calculation node and shouldn't
// be followed by a comma.
if (fn.type() == CalculationNode::Type::Progress) {
if (static_cast<ProgressCalculationNode const&>(fn).no_clamp())
builder.append("no-clamp "sv);
}
for (auto const& child : fn.children()) {
if (!first)
builder.append(", "sv);
@ -612,6 +620,8 @@ StringView CalculationNode::name() const
return "atan2"sv;
case Type::Pow:
return "pow"sv;
case Type::Progress:
return "progress"sv;
case Type::Sqrt:
return "sqrt"sv;
case Type::Hypot:
@ -1004,6 +1014,113 @@ GC::Ptr<CSSNumericValue> ProductCalculationNode::reify(JS::Realm& realm) const
return CSSMathProduct::create(realm, numeric_type().value(), reified_children.as_nonnull());
}
NonnullRefPtr<ProgressCalculationNode const> ProgressCalculationNode::create(bool no_clamp, NonnullRefPtr<CalculationNode const> value, NonnullRefPtr<CalculationNode const> start_value, NonnullRefPtr<CalculationNode const> end_value)
{
// https://drafts.csswg.org/css-values-5/#progress
// The result of progress() is a <number> made consistent with the consistent type of its arguments
auto numeric_type = NumericType {}.made_consistent_with(add_the_types({ value, start_value, end_value }).value()).value();
return adopt_ref(*new (nothrow) ProgressCalculationNode(no_clamp, move(value), move(start_value), move(end_value), numeric_type));
}
ProgressCalculationNode::ProgressCalculationNode(bool no_clamp, NonnullRefPtr<CalculationNode const> value, NonnullRefPtr<CalculationNode const> start_value, NonnullRefPtr<CalculationNode const> end_value, Optional<NumericType> numeric_type)
: CalculationNode(Type::Progress, numeric_type)
, m_no_clamp(no_clamp)
, m_value(move(value))
, m_start_value(move(start_value))
, m_end_value(move(end_value))
{
}
ProgressCalculationNode::~ProgressCalculationNode() = default;
bool ProgressCalculationNode::contains_percentage() const
{
return m_value->contains_percentage()
|| m_start_value->contains_percentage()
|| m_end_value->contains_percentage();
}
NonnullRefPtr<CalculationNode const> ProgressCalculationNode::with_simplified_children(CalculationContext const& calculation_context, CalculationResolutionContext const& calculation_resolution_context) const
{
auto simplified_value = simplify_a_calculation_tree(m_value, calculation_context, calculation_resolution_context);
auto simplified_start_value = simplify_a_calculation_tree(m_start_value, calculation_context, calculation_resolution_context);
auto simplified_end_value = simplify_a_calculation_tree(m_end_value, calculation_context, calculation_resolution_context);
if (simplified_value == m_value && simplified_start_value == m_start_value && simplified_end_value == m_end_value)
return *this;
return create(m_no_clamp, move(simplified_value), move(simplified_start_value), move(simplified_end_value));
}
Optional<CalculatedStyleValue::CalculationResult> ProgressCalculationNode::run_operation_if_possible(CalculationContext const& context, CalculationResolutionContext const& resolution_context) const
{
auto maybe_value = try_get_value_with_canonical_unit(*m_value, context, resolution_context);
auto maybe_start_value = try_get_value_with_canonical_unit(*m_start_value, context, resolution_context);
auto maybe_end_value = try_get_value_with_canonical_unit(*m_end_value, context, resolution_context);
if (!maybe_value.has_value() || !maybe_start_value.has_value() || !maybe_end_value.has_value())
return {};
// https://drafts.csswg.org/css-values-5/#calculate-a-progress-function
// If the progress start value and progress end value are different values
if (maybe_start_value != maybe_end_value) {
// (progress value - progress start value) / (progress end value - progress start value), clamped to the [0,1] range if no-clamp is not specified.
auto progress = (maybe_value->value() - maybe_start_value->value()) / (maybe_end_value->value() - maybe_start_value->value());
if (!m_no_clamp)
progress = clamp(progress, 0.0, 1.0);
return CalculatedStyleValue::CalculationResult { progress, numeric_type() };
}
// If the progress start value and progress end value are the same value
{
// 0 if no-clamp is not specified.
if (!m_no_clamp)
return CalculatedStyleValue::CalculationResult { 0.0, numeric_type() };
// Otherwise, 0, -∞, or +∞, depending on whether progress value is equal to, less than, or greater than the shared value.
if (maybe_value->value() == maybe_start_value->value())
return CalculatedStyleValue::CalculationResult { 0.0, numeric_type() };
if (maybe_value->value() < maybe_start_value->value())
return CalculatedStyleValue::CalculationResult { -AK::Infinity<double>, numeric_type() };
return CalculatedStyleValue::CalculationResult { AK::Infinity<double>, numeric_type() };
}
}
void ProgressCalculationNode::dump(StringBuilder& builder, int indent) const
{
builder.appendff("{: >{}}PROGRESS (no-clamp: {})\n", "", indent, m_no_clamp);
m_value->dump(builder, indent + 2);
m_start_value->dump(builder, indent + 2);
m_end_value->dump(builder, indent + 2);
}
bool ProgressCalculationNode::equals(CalculationNode const& other) const
{
if (this == &other)
return true;
if (type() != other.type())
return false;
auto const& other_progress = static_cast<ProgressCalculationNode const&>(other);
return m_no_clamp == other_progress.m_no_clamp
&& m_value->equals(other_progress.m_value)
&& m_start_value->equals(other_progress.m_start_value)
&& m_end_value->equals(other_progress.m_end_value);
}
bool ProgressCalculationNode::is_computationally_independent() const
{
return m_value->is_computationally_independent()
&& m_start_value->is_computationally_independent()
&& m_end_value->is_computationally_independent();
}
NonnullRefPtr<NegateCalculationNode const> NegateCalculationNode::create(NonnullRefPtr<CalculationNode const> value)
{
return adopt_ref(*new (nothrow) NegateCalculationNode(move(value)));

View file

@ -150,6 +150,7 @@ private:
X(Clamp) \
X(Sum) \
X(Product) \
X(Progress) \
X(Negate) \
X(Invert) \
X(Abs) \
@ -217,6 +218,7 @@ public:
case Type::Atan:
case Type::Atan2:
case Type::Pow:
case Type::Progress:
case Type::Sqrt:
case Type::Hypot:
case Type::Log:
@ -335,6 +337,32 @@ private:
Vector<NonnullRefPtr<CalculationNode const>> m_values;
};
class ProgressCalculationNode final : public CalculationNode {
public:
static NonnullRefPtr<ProgressCalculationNode const> create(bool no_clamp, NonnullRefPtr<CalculationNode const> value, NonnullRefPtr<CalculationNode const> start_value, NonnullRefPtr<CalculationNode const> end_value);
~ProgressCalculationNode();
virtual bool contains_percentage() const override;
virtual NonnullRefPtr<CalculationNode const> with_simplified_children(CalculationContext const&, CalculationResolutionContext const&) const override;
virtual Optional<CalculatedStyleValue::CalculationResult> run_operation_if_possible(CalculationContext const&, CalculationResolutionContext const&) const override;
virtual Vector<NonnullRefPtr<CalculationNode const>> children() const override { return { { m_value, m_start_value, m_end_value } }; }
virtual void dump(StringBuilder&, int indent) const override;
virtual bool equals(CalculationNode const&) const override;
virtual bool is_computationally_independent() const override;
bool no_clamp() const { return m_no_clamp; }
private:
ProgressCalculationNode(bool no_clamp, NonnullRefPtr<CalculationNode const> value, NonnullRefPtr<CalculationNode const> start_value, NonnullRefPtr<CalculationNode const> end_value, Optional<NumericType> numeric_type);
bool m_no_clamp;
NonnullRefPtr<CalculationNode const> m_value;
NonnullRefPtr<CalculationNode const> m_start_value;
NonnullRefPtr<CalculationNode const> m_end_value;
};
class NegateCalculationNode final : public CalculationNode {
public:
static NonnullRefPtr<NegateCalculationNode const> create(NonnullRefPtr<CalculationNode const>);

View file

@ -124,6 +124,15 @@ static Optional<RoundingStrategy> parse_rounding_strategy(TokenStream<ComponentV
RefPtr<CalculationNode const> Parser::parse_math_function(Function const& function, CalculationContext const& context)
{
TokenStream stream { function.value };
// AD-HOC: `progress()`'s `no-clamp` argument that isn't comma separated like the other arguments, so we need to
// parse it first.
bool progress_function_no_clamp = false;
if (function.name.equals_ignoring_ascii_case("progress"sv)) {
if (auto keyword = parse_specific_keyword_value(stream, { { Keyword::NoClamp } }))
progress_function_no_clamp = true;
}
auto arguments = parse_a_comma_separated_list_of_component_values(stream);
auto const& percentages_resolve_as = context.percentages_resolve_as;
""")
@ -393,6 +402,11 @@ RefPtr<CalculationNode const> Parser::parse_math_function(Function const& functi
""")
# Generate the call to the constructor
out.write(f" return {name_titlecase}CalculationNode::create(")
if name == "progress":
out.write("progress_function_no_clamp")
if parameters:
out.write(", ")
for parameter_index, parameter in enumerate(parameters):
parameter_type_string = parameter["type"]
if parameter_type_string == "<rounding-strategy>":

View file

@ -0,0 +1,12 @@
specified: calc(-1)
computed: -1
specified: calc(2)
computed: 2
specified: progress(no-clamp 1em, 0px, 100px)
computed: 0
specified: calc(-infinity)
computed: -2147483648
specified: calc(0)
computed: 0
specified: calc(infinity)
computed: 2147483647

View file

@ -0,0 +1,28 @@
Harness status: OK
Found 23 tests
23 Pass
Pass progress(1, 0, 1) should be used-value-equivalent to 1
Pass progress(progress(1, 0, 1), progress(0px, 0px, 1px), progress(1deg, 0deg, 1deg)) should be used-value-equivalent to 1
Pass progress(sign(-10px) * 10px, (10px - 10px), 10px * progress(1deg, 0deg, 1deg)) should be used-value-equivalent to 0
Pass calc(progress(100px, 0px, 50px) * 10px + 100px) should be used-value-equivalent to 110px
Pass calc(progress(100, 0, sign(50px))) should be used-value-equivalent to 1
Pass calc(progress(abs(5%), hypot(3%, 4%), 10%)) should be used-value-equivalent to 0
Pass progress(50em, 10em, 110em) should be used-value-equivalent to 0.4
Pass progress(1000em, 10em, 110em) should be used-value-equivalent to 1
Pass scale(progress(50em, 10rem, 110em)) should be used-value-equivalent to scale(1)
Pass scale(progress(1000em, 10rem, 110em)) should be used-value-equivalent to scale(1)
Pass scale(progress(0em, 0rem, 0em)) should be used-value-equivalent to scale(0)
Pass scale(progress(sign(1em - 1rem) * 1ex, 0rem, 0em)) should be used-value-equivalent to scale(0)
Pass calc(progress(1, 0, 1) * 10px) should be used-value-equivalent to 10px
Pass calc(progress(1, 0, 1) * 1s) should be used-value-equivalent to 1s
Pass calc(progress(1, 0, 1) * 1deg) should be used-value-equivalent to 1deg
Pass calc(progress(sign(1001em - 10lh * progress(100px, 2rex, 10ex)) * 10em, 2rem, 12em) / 2) should be used-value-equivalent to 0.4
Pass calc(progress(sign(1001em - 10lh * progress(100px, 2rex, 10ex)) * 20em, 2rem, 12em) * 10) should be used-value-equivalent to 10
Pass calc(progress(sign(1001em - 10lh * progress(100px, 2rex, 10ex)) * 20em, 2rem, 12em) * 30) should be used-value-equivalent to 30
Pass calc(progress(sign(1001em - 10lh * progress(100px, 2rex, 10ex)) * 20em, 2rem, 12em) / 4) should be used-value-equivalent to 0.25
Pass calc(progress(sign(1001em - 10lh * progress(100px, 2rex, 10ex)) * 20em, 2rem, 12em) * 4) should be used-value-equivalent to 4
Pass calc(progress(sign(1001em - 10lh * progress(100px, 2rex, 10ex)) * 20em, 2rem, 12em) * 2) should be used-value-equivalent to 2
Pass rotate3d(progress(21em, 1rem, 11em), progress(21em, 1rem, 11em), progress(21em, 1rem, 11em), calc(progress(11em, 1rem, 11em) * 2deg)) should be used-value-equivalent to rotate3d(2, 2, 2, 2deg)
Pass calc(50% - 0px * clamp(0, progress(0cqw, 0px, 0px), 1)) should be used-value-equivalent to 50px

View file

@ -0,0 +1,32 @@
Harness status: OK
Found 27 tests
27 Pass
Pass e.style['opacity'] = "progress()" should not set the property value
Pass e.style['opacity'] = "progress( )" should not set the property value
Pass e.style['opacity'] = "progress(,)" should not set the property value
Pass e.style['opacity'] = "progress(1, )" should not set the property value
Pass e.style['opacity'] = "progress(1)" should not set the property value
Pass e.style['opacity'] = "progress(50%, 0)" should not set the property value
Pass e.style['opacity'] = "progress(0, 1,)" should not set the property value
Pass e.style['opacity'] = "progress(from,)" should not set the property value
Pass e.style['opacity'] = "progress(from 1, 0)" should not set the property value
Pass e.style['opacity'] = "progress(from 1, 0 1)" should not set the property value
Pass e.style['opacity'] = "progress(from 1 0)" should not set the property value
Pass e.style['opacity'] = "progress(0,, 0)" should not set the property value
Pass e.style['opacity'] = "progress(to,,,,)" should not set the property value
Pass e.style['opacity'] = "progress(0,,, 10,, 200)" should not set the property value
Pass e.style['opacity'] = "progress(0,,, 10,,, 200)" should not set the property value
Pass e.style['opacity'] = "progress(0,, 10,, 200)" should not set the property value
Pass e.style['opacity'] = "progress(0 from 10 to 200)" should not set the property value
Pass e.style['opacity'] = "progress(5%, 0, 8" should not set the property value
Pass e.style['opacity'] = "progress(5%, 0deg, 8deg" should not set the property value
Pass e.style['opacity'] = "progress(5, 0deg, 8deg" should not set the property value
Pass e.style['opacity'] = "progress(5, 0%, 8deg" should not set the property value
Pass e.style['opacity'] = "progress(5, 0%, sign(10px)" should not set the property value
Pass e.style['opacity'] = "progress(5%, 0px, 10px" should not set the property value
Pass e.style['letter-spacing'] = "calc(1px * progress(10deg, 0, 10))" should not set the property value
Pass e.style['letter-spacing'] = "calc(1px * progress(10, 0px, 10))" should not set the property value
Pass e.style['opacity'] = "progress(10px * 10px, 10px * 10px, 10px * 10px)" should not set the property value
Pass e.style['opacity'] = "progress(10px, 10px * 10px, 10px * 10px)" should not set the property value

View file

@ -0,0 +1,39 @@
Harness status: OK
Found 34 tests
34 Pass
Pass 'progress(100px, 0px, 100px)' as a specified value should serialize as 'calc(1)'.
Pass 'scale(progress(100px, 0px, 100px))' as a specified value should serialize as 'scale(calc(1))'.
Pass 'progress(100px, 0px, 100px)' as a computed value should serialize as '1'.
Pass 'scale(progress(100px, 0px, 100px))' as a computed value should serialize as 'matrix(1, 0, 0, 1, 0, 0)'.
Pass 'progress(10em, 0px, 10em)' as a specified value should serialize as 'progress(10em, 0px, 10em)'.
Pass 'scale(progress(10em, 0px, 10em))' as a specified value should serialize as 'scale(progress(10em, 0px, 10em))'.
Pass 'progress(10em, 0px, 10em)' as a computed value should serialize as '1'.
Pass 'scale(progress(10em, 0px, 10em))' as a computed value should serialize as 'matrix(1, 0, 0, 1, 0, 0)'.
Pass 'progress(10em, 0px, 10rem)' as a specified value should serialize as 'progress(10em, 0px, 10rem)'.
Pass 'scale(progress(10em, 0px, 10rem))' as a specified value should serialize as 'scale(progress(10em, 0px, 10rem))'.
Pass 'progress(10em, 0px, 10rem)' as a computed value should serialize as '1'.
Pass 'scale(progress(10em, 0px, 10rem))' as a computed value should serialize as 'matrix(1, 0, 0, 1, 0, 0)'.
Pass 'progress(100px, (10px - 10px), 100px)' as a specified value should serialize as 'calc(1)'.
Pass 'scale(progress(100px, (10px - 10px), 100px))' as a specified value should serialize as 'scale(calc(1))'.
Pass 'progress(100px, (10px - 10px), 100px)' as a computed value should serialize as '1'.
Pass 'scale(progress(100px, (10px - 10px), 100px))' as a computed value should serialize as 'matrix(1, 0, 0, 1, 0, 0)'.
Pass 'progress(1%, (10% - 10%), 100%)' as a specified value should serialize as 'calc(0.01)'.
Pass 'scale(progress(1%, (10% - 10%), 100%))' as a specified value should serialize as 'scale(calc(0.01))'.
Pass 'progress(1%, (10% - 10%), 100%)' as a computed value should serialize as '0.01'.
Pass 'scale(progress(1%, (10% - 10%), 100%))' as a computed value should serialize as 'matrix(0.01, 0, 0, 0.01, 0, 0)'.
Pass 'calc(0.5 * progress(100px, 0px, 100px))' as a specified value should serialize as 'calc(0.5)'.
Pass 'scale(calc(0.5 * progress(100px, 0px, 100px)))' as a specified value should serialize as 'scale(calc(0.5))'.
Pass 'calc(0.5 * progress(100px, 0px, 100px))' as a computed value should serialize as '0.5'.
Pass 'scale(calc(0.5 * progress(100px, 0px, 100px)))' as a computed value should serialize as 'matrix(0.5, 0, 0, 0.5, 0, 0)'.
Pass 'calc(0.5 * progress(200px, 0px, 100px))' as a specified value should serialize as 'calc(0.5)'.
Pass 'scale(calc(0.5 * progress(200px, 0px, 100px)))' as a specified value should serialize as 'scale(calc(0.5))'.
Pass 'calc(0.5 * progress(200px, 0px, 100px))' as a computed value should serialize as '0.5'.
Pass 'scale(calc(0.5 * progress(200px, 0px, 100px)))' as a computed value should serialize as 'matrix(0.5, 0, 0, 0.5, 0, 0)'.
Pass 'calc(0.5 * progress(-100px, 0px, 100px))' as a specified value should serialize as 'calc(0)'.
Pass 'scale(calc(0.5 * progress(-100px, 0px, 100px)))' as a specified value should serialize as 'scale(calc(0))'.
Pass 'calc(0.5 * progress(-100px, 0px, 100px))' as a computed value should serialize as '0'.
Pass 'scale(calc(0.5 * progress(-100px, 0px, 100px)))' as a computed value should serialize as 'matrix(0, 0, 0, 0, 0, 0)'.
Pass 'calc(50px * progress(100px, 0px, 100px))' as a specified value should serialize as 'calc(50px)'.
Pass 'calc(1px * progress(abs(10%), (10% - 10%), 100% / 10))' as a computed value should serialize as '1px'.

View file

@ -0,0 +1,28 @@
<!doctype html>
<script src="../include.js"></script>
<script>
function printProperty(property, value) {
const element = document.createElement("div");
document.body.append(element);
element.style[property] = value;
println(`specified: ${element.style[property]}`);
println(`computed: ${getComputedStyle(element)[property]}`);
element.remove();
}
test(() => {
// Should produce negative numbers
printProperty("z-index", "progress(no-clamp -100px, 0px, 100px)");
// Should produce numbers greater than 1
printProperty("z-index", "progress(no-clamp 200px, 0px, 100px)");
// Should serialize correctly when not resolvable at parse time
printProperty("z-index", "progress(no-clamp 1em, 0px, 100px)");
// Should produce the expected values when the start and end values are the same
printProperty("z-index", "progress(no-clamp 0px, 1px, 1px)"); // -infinity
printProperty("z-index", "progress(no-clamp 1px, 1px, 1px)"); // 0
printProperty("z-index", "progress(no-clamp 2px, 1px, 1px)"); // +infinity
});
</script>

View file

@ -0,0 +1,56 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-values-5/#progress">
<link rel="author" title="sakhapov@chromuim.org">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../support/numeric-testcommon.js"></script>
<style>
:root {
font-size: 10px;
}
#wrapper {
width: 100px;
}
#target {
font-size: 10px;
}
</style>
<div id="wrapper">
<div id="target"></div>
</div>
<script>
// Identity tests
test_math_used('progress(1, 0, 1)', '1', {type:'number'});
// Nestings
test_math_used('progress(progress(1, 0, 1), progress(0px, 0px, 1px), progress(1deg, 0deg, 1deg))', '1', {type:'number'});
test_math_used('progress(sign(-10px) * 10px, (10px - 10px), 10px * progress(1deg, 0deg, 1deg))', '0', {type:'number'});
// General calculations
test_math_used('calc(progress(100px, 0px, 50px) * 10px + 100px)', '110px');
test_math_used('calc(progress(100, 0, sign(50px)))', '1', {type:'number'});
test_math_used('calc(progress(abs(5%), hypot(3%, 4%), 10%))', '0', {type:'number', approx:0.001});
test_math_used('progress(50em, 10em, 110em)', '0.4', {type:'number', approx:0.1});
test_math_used('progress(1000em, 10em, 110em)', '1', {type:'number', approx:0.1});
test_math_used('scale(progress(50em, 10rem, 110em))', 'scale(1)', {prop:'transform', type:'number', approx:0.1});
test_math_used('scale(progress(1000em, 10rem, 110em))', 'scale(1)', {prop:'transform', type:'number', approx:0.1});
test_math_used('scale(progress(0em, 0rem, 0em))', 'scale(0)', {prop:'transform', type:'number'});
test_math_used('scale(progress(sign(1em - 1rem) * 1ex, 0rem, 0em))', 'scale(0)', {prop:'transform', type:'number'});
// Type checking
test_math_used('calc(progress(1, 0, 1) * 10px)', '10px');
test_math_used('calc(progress(1, 0, 1) * 1s)', '1s', {type:'time'});
test_math_used('calc(progress(1, 0, 1) * 1deg)', '1deg', {type:'angle', approx:0.001});
// Test different number accepting properties
test_math_used('calc(progress(sign(1001em - 10lh * progress(100px, 2rex, 10ex)) * 10em, 2rem, 12em) / 2)', '0.4', {prop:'opacity', type:'number'});
test_math_used('calc(progress(sign(1001em - 10lh * progress(100px, 2rex, 10ex)) * 20em, 2rem, 12em) * 10)', '10', {prop:'order', type:'number'});
test_math_used('calc(progress(sign(1001em - 10lh * progress(100px, 2rex, 10ex)) * 20em, 2rem, 12em) * 30)', '30', {prop:'flex-grow', type:'number'});
test_math_used('calc(progress(sign(1001em - 10lh * progress(100px, 2rex, 10ex)) * 20em, 2rem, 12em) / 4)', '0.25', {prop:'flex-grow', type:'number'});
test_math_used('calc(progress(sign(1001em - 10lh * progress(100px, 2rex, 10ex)) * 20em, 2rem, 12em) * 4)', '4', {prop:'column-count', type:'number'});
test_math_used('calc(progress(sign(1001em - 10lh * progress(100px, 2rex, 10ex)) * 20em, 2rem, 12em) * 2)', '2', {prop:'scale'});
test_math_used('rotate3d(progress(21em, 1rem, 11em), progress(21em, 1rem, 11em), progress(21em, 1rem, 11em), calc(progress(11em, 1rem, 11em) * 2deg))', 'rotate3d(2, 2, 2, 2deg)', {prop:'transform'});
test_math_used('calc(50% - 0px * clamp(0, progress(0cqw, 0px, 0px), 1))', '50px', {type:'length', prop:'width'});
</script>

View file

@ -0,0 +1,46 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-values-5/#progress">
<link rel="author" title="sakhapov@chromuim.org">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../support/parsing-testcommon.js"></script>
<script>
function test_invalid_number(value) {
test_invalid_value('opacity', value);
}
function test_invalid_length(value) {
// 'letter-spacing' accepts <length> only, not <percentage> or any mixes.
test_invalid_value('letter-spacing', value);
}
// Syntax checking
test_invalid_number('progress()');
test_invalid_number('progress( )');
test_invalid_number('progress(,)');
test_invalid_number('progress(1, )');
test_invalid_number('progress(1)');
test_invalid_number('progress(50%, 0)');
test_invalid_number('progress(0, 1,)');
test_invalid_number('progress(from,)');
test_invalid_number('progress(from 1, 0)');
test_invalid_number('progress(from 1, 0 1)');
test_invalid_number('progress(from 1 0)');
test_invalid_number('progress(0,, 0)');
test_invalid_number('progress(to,,,,)');
test_invalid_number('progress(0,,, 10,, 200)');
test_invalid_number('progress(0,,, 10,,, 200)');
test_invalid_number('progress(0,, 10,, 200)');
test_invalid_number('progress(0 from 10 to 200)');
// General tests
test_invalid_number('progress(5%, 0, 8');
test_invalid_number('progress(5%, 0deg, 8deg');
test_invalid_number('progress(5, 0deg, 8deg');
test_invalid_number('progress(5, 0%, 8deg');
test_invalid_number('progress(5, 0%, sign(10px)');
test_invalid_number('progress(5%, 0px, 10px');
test_invalid_length('calc(1px * progress(10deg, 0, 10))');
test_invalid_length('calc(1px * progress(10, 0px, 10))');
test_invalid_number('progress(10px * 10px, 10px * 10px, 10px * 10px)');
test_invalid_number('progress(10px, 10px * 10px, 10px * 10px)');
</script>

View file

@ -0,0 +1,66 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-values-5/#progress">
<link rel="author" title="sakhapov@chromuim.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(t,s,c) {
test_specified_serialization('opacity', t, s);
test_specified_serialization('transform', `scale(${t})`, `scale(${s})`);
test_computed_serialization('opacity', t, c);
test_computed_serialization('transform', `scale(${t})`, `matrix(${c}, 0, 0, ${c}, 0, 0)`);
}
test_serialization(
'progress(100px, 0px, 100px)',
'calc(1)',
'1',
);
test_serialization(
'progress(10em, 0px, 10em)',
'progress(10em, 0px, 10em)',
'1',
);
test_serialization(
'progress(10em, 0px, 10rem)',
'progress(10em, 0px, 10rem)',
'1',
);
test_serialization(
'progress(100px, (10px - 10px), 100px)',
'calc(1)',
'1',
);
test_serialization(
'progress(1%, (10% - 10%), 100%)',
'calc(0.01)',
'0.01',
);
test_serialization(
'calc(0.5 * progress(100px, 0px, 100px))',
'calc(0.5)',
'0.5'
);
test_serialization(
'calc(0.5 * progress(200px, 0px, 100px))',
'calc(0.5)',
'0.5'
);
test_serialization(
'calc(0.5 * progress(-100px, 0px, 100px))',
'calc(0)',
'0'
);
test_specified_serialization(
'width',
'calc(50px * progress(100px, 0px, 100px))',
'calc(50px)'
);
test_computed_serialization(
'width',
'calc(1px * progress(abs(10%), (10% - 10%), 100% / 10))',
'1px',
);
</script>