Skip to content

Commit 2a5e389

Browse files
Calme1709AtkinsSJ
authored andcommitted
LibWeb: Implement basic CSS random() function
At the moment this is limited to only fixed value sharing and does not support step values
1 parent 8944130 commit 2a5e389

File tree

17 files changed

+380
-31
lines changed

17 files changed

+380
-31
lines changed

Libraries/LibWeb/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ set(SOURCES
265265
CSS/StyleValues/PositionStyleValue.cpp
266266
CSS/StyleValues/RGBColorStyleValue.cpp
267267
CSS/StyleValues/RadialGradientStyleValue.cpp
268+
CSS/StyleValues/RandomValueSharingStyleValue.cpp
268269
CSS/StyleValues/RectStyleValue.cpp
269270
CSS/StyleValues/RepeatStyleStyleValue.cpp
270271
CSS/StyleValues/ScrollbarColorStyleValue.cpp

Libraries/LibWeb/CSS/MathFunctions.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,28 @@
168168
}
169169
]
170170
},
171+
"random": {
172+
"parameter-validation": "consistent",
173+
"parameters": [
174+
{
175+
"name": "random-value-sharing",
176+
"type": "<random-value-sharing>",
177+
"required": false,
178+
"default": "auto",
179+
"__comment": "NOTE: The actual default is hardcoded and we don't respect the value above, we have it there so we know that this argument has a default"
180+
},
181+
{
182+
"name": "minimum",
183+
"type": "<number>|<dimension>|<percentage>",
184+
"required": true
185+
},
186+
{
187+
"name": "maximum",
188+
"type": "<number>|<dimension>|<percentage>",
189+
"required": true
190+
}
191+
]
192+
},
171193
"rem": {
172194
"parameter-validation": "same",
173195
"parameters": [

Libraries/LibWeb/CSS/Parser/Parser.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1718,6 +1718,12 @@ bool Parser::context_allows_tree_counting_functions() const
17181718
return true;
17191719
}
17201720

1721+
bool Parser::context_allows_random_functions() const
1722+
{
1723+
// For now we only allow random functions within property contexts, see https://drafts.csswg.org/css-values-5/#issue-cd071f29
1724+
return m_value_context.find_first_index_if([](ValueParsingContext context) { return context.has<PropertyID>(); }).has_value();
1725+
}
1726+
17211727
Vector<ComponentValue> Parser::parse_as_list_of_component_values()
17221728
{
17231729
return parse_a_list_of_component_values(m_token_stream);

Libraries/LibWeb/CSS/Parser/Parser.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ enum SpecialContext : u8 {
7777
CubicBezierFunctionXCoordinate,
7878
DOMMatrixInitString,
7979
MediaCondition,
80+
RandomValueSharingFixedValue,
8081
ShadowBlurRadius,
8182
StepsIntervalsJumpNone,
8283
StepsIntervalsNormal,
@@ -379,6 +380,7 @@ class Parser {
379380
RefPtr<CustomIdentStyleValue const> parse_custom_ident_value(TokenStream<ComponentValue>&, ReadonlySpan<StringView> blacklist);
380381
Optional<FlyString> parse_dashed_ident(TokenStream<ComponentValue>&);
381382
RefPtr<CustomIdentStyleValue const> parse_dashed_ident_value(TokenStream<ComponentValue>&);
383+
RefPtr<RandomValueSharingStyleValue const> parse_random_value_sharing(TokenStream<ComponentValue>&);
382384
// NOTE: Implemented in generated code. (GenerateCSSMathFunctions.cpp)
383385
RefPtr<CalculationNode const> parse_math_function(Function const&, CalculationContext const&);
384386
RefPtr<CalculationNode const> parse_a_calc_function_node(Function const&, CalculationContext const&);
@@ -593,6 +595,7 @@ class Parser {
593595
}
594596
bool context_allows_quirky_length() const;
595597
bool context_allows_tree_counting_functions() const;
598+
bool context_allows_random_functions() const;
596599

597600
Vector<RuleContext> m_rule_context;
598601
HashTable<FlyString> m_declared_namespaces;

Libraries/LibWeb/CSS/Parser/ValueParsing.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
6161
#include <LibWeb/CSS/StyleValues/RGBColorStyleValue.h>
6262
#include <LibWeb/CSS/StyleValues/RadialGradientStyleValue.h>
63+
#include <LibWeb/CSS/StyleValues/RandomValueSharingStyleValue.h>
6364
#include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
6465
#include <LibWeb/CSS/StyleValues/RectStyleValue.h>
6566
#include <LibWeb/CSS/StyleValues/RepeatStyleStyleValue.h>
@@ -3789,6 +3790,40 @@ RefPtr<CustomIdentStyleValue const> Parser::parse_custom_ident_value(TokenStream
37893790
return nullptr;
37903791
}
37913792

3793+
// https://drafts.csswg.org/css-values-5/#typedef-random-value-sharing
3794+
RefPtr<RandomValueSharingStyleValue const> Parser::parse_random_value_sharing(TokenStream<ComponentValue>& tokens)
3795+
{
3796+
// <random-value-sharing> = [ [ auto | <dashed-ident> ] || element-shared ] | fixed <number [0,1]>
3797+
auto transaction = tokens.begin_transaction();
3798+
3799+
tokens.discard_whitespace();
3800+
3801+
// fixed <number [0,1]>
3802+
if (tokens.next_token().is_ident("fixed"sv)) {
3803+
tokens.discard_a_token();
3804+
tokens.discard_whitespace();
3805+
3806+
auto context_guard = push_temporary_value_parsing_context(SpecialContext::RandomValueSharingFixedValue);
3807+
if (auto fixed_value = parse_number_value(tokens)) {
3808+
tokens.discard_whitespace();
3809+
3810+
if (tokens.has_next_token())
3811+
return nullptr;
3812+
3813+
if (fixed_value->is_number() && (fixed_value->as_number().number() < 0 || fixed_value->as_number().number() >= 1))
3814+
return nullptr;
3815+
3816+
transaction.commit();
3817+
return RandomValueSharingStyleValue::create_fixed(fixed_value.release_nonnull());
3818+
}
3819+
3820+
return nullptr;
3821+
}
3822+
3823+
// FIXME: Support non-fixed values
3824+
return nullptr;
3825+
}
3826+
37923827
// https://drafts.csswg.org/css-values-4/#typedef-dashed-ident
37933828
Optional<FlyString> Parser::parse_dashed_ident(TokenStream<ComponentValue>& tokens)
37943829
{
@@ -4384,6 +4419,9 @@ RefPtr<CalculatedStyleValue const> Parser::parse_calculated_value(ComponentValue
43844419
case SpecialContext::CubicBezierFunctionXCoordinate:
43854420
// Coordinates on the X axis must be between 0 and 1
43864421
return CalculationContext { .accepted_type_ranges = { { ValueType::Number, { 0, 1 } } } };
4422+
case SpecialContext::RandomValueSharingFixedValue:
4423+
// Fixed values have to be less than one and numbers serialize with six digits of precision
4424+
return CalculationContext { .accepted_type_ranges = { { ValueType::Number, { 0, 0.999999 } } } };
43874425
case SpecialContext::StepsIntervalsJumpNone:
43884426
return CalculationContext { .resolve_numbers_as_integers = true, .accepted_type_ranges = { { ValueType::Integer, { 2, NumericLimits<float>::max() } } } };
43894427
case SpecialContext::StepsIntervalsNormal:

Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.cpp

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
3232
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
3333
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
34+
#include <LibWeb/CSS/StyleValues/RandomValueSharingStyleValue.h>
3435
#include <LibWeb/CSS/StyleValues/ResolutionStyleValue.h>
3536
#include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
3637

@@ -284,6 +285,11 @@ static String serialize_a_math_function(CalculationNode const& fn, CalculationCo
284285
}
285286
}
286287

288+
// AD-HOC: We serialize random() directly since it has abnormal children (e.g. m_random_value_sharing which is not a
289+
// calculation node).
290+
if (fn.type() == CalculationNode::Type::Random)
291+
return as<RandomCalculationNode>(fn).to_string(context, serialization_mode);
292+
287293
// 3. If the calculation tree’s root node is a numeric value, or a calc-operator node, let s be a string initially
288294
// containing "calc(".
289295
// Otherwise, let s be a string initially containing the name of the root node, lowercased (such as "sin" or
@@ -572,6 +578,8 @@ StringView CalculationNode::name() const
572578
return "log"sv;
573579
case Type::Exp:
574580
return "exp"sv;
581+
case Type::Random:
582+
return "random"sv;
575583
case Type::Round:
576584
return "round"sv;
577585
case Type::Mod:
@@ -2434,6 +2442,134 @@ bool ModCalculationNode::equals(CalculationNode const& other) const
24342442
&& m_y->equals(*static_cast<ModCalculationNode const&>(other).m_y);
24352443
}
24362444

2445+
NonnullRefPtr<RandomCalculationNode const> RandomCalculationNode::create(NonnullRefPtr<RandomValueSharingStyleValue const> random_value_sharing, NonnullRefPtr<CalculationNode const> minimum, NonnullRefPtr<CalculationNode const> maximum)
2446+
{
2447+
Optional<NumericType> numeric_type = add_the_types(*minimum, *maximum);
2448+
2449+
return adopt_ref(*new (nothrow) RandomCalculationNode(move(random_value_sharing), move(minimum), move(maximum), move(numeric_type)));
2450+
}
2451+
2452+
RandomCalculationNode::RandomCalculationNode(NonnullRefPtr<RandomValueSharingStyleValue const> random_value_sharing, NonnullRefPtr<CalculationNode const> minimum, NonnullRefPtr<CalculationNode const> maximum, Optional<NumericType> numeric_type)
2453+
: CalculationNode(Type::Random, move(numeric_type))
2454+
, m_random_value_sharing(move(random_value_sharing))
2455+
, m_minimum(move(minimum))
2456+
, m_maximum(move(maximum))
2457+
{
2458+
}
2459+
2460+
RandomCalculationNode::~RandomCalculationNode() = default;
2461+
2462+
bool RandomCalculationNode::contains_percentage() const
2463+
{
2464+
return m_minimum->contains_percentage() || m_maximum->contains_percentage();
2465+
}
2466+
2467+
NonnullRefPtr<CalculationNode const> RandomCalculationNode::with_simplified_children(CalculationContext const& context, CalculationResolutionContext const& resolution_context) const
2468+
{
2469+
ValueComparingRefPtr<RandomValueSharingStyleValue const> simplified_random_value_sharing;
2470+
2471+
// When we are in the absolutization process we should absolutize m_random_value_sharing
2472+
if (resolution_context.length_resolution_context.has_value()) {
2473+
ComputationContext computation_context {
2474+
.length_resolution_context = resolution_context.length_resolution_context.value(),
2475+
.abstract_element = resolution_context.abstract_element
2476+
};
2477+
2478+
simplified_random_value_sharing = m_random_value_sharing->absolutized(computation_context)->as_random_value_sharing();
2479+
} else {
2480+
simplified_random_value_sharing = m_random_value_sharing;
2481+
}
2482+
2483+
ValueComparingNonnullRefPtr<CalculationNode const> simplified_minimum = simplify_a_calculation_tree(m_minimum, context, resolution_context);
2484+
ValueComparingNonnullRefPtr<CalculationNode const> simplified_maximum = simplify_a_calculation_tree(m_maximum, context, resolution_context);
2485+
2486+
if (simplified_random_value_sharing == m_random_value_sharing && simplified_minimum == m_minimum && simplified_maximum == m_maximum)
2487+
return *this;
2488+
2489+
return RandomCalculationNode::create(simplified_random_value_sharing.release_nonnull(), move(simplified_minimum), move(simplified_maximum));
2490+
}
2491+
2492+
// https://drafts.csswg.org/css-values-5/#random-evaluation
2493+
Optional<CalculatedStyleValue::CalculationResult> RandomCalculationNode::run_operation_if_possible(CalculationContext const& context, CalculationResolutionContext const& resolution_context) const
2494+
{
2495+
// NB: We don't want to resolve this before computation time even if it's possible
2496+
if (!resolution_context.abstract_element.has_value() && !resolution_context.length_resolution_context.has_value() && resolution_context.percentage_basis.has<Empty>())
2497+
return {};
2498+
2499+
auto random_base_value = m_random_value_sharing->random_base_value();
2500+
2501+
auto minimum = try_get_value_with_canonical_unit(m_minimum, context, resolution_context);
2502+
auto maximum = try_get_value_with_canonical_unit(m_maximum, context, resolution_context);
2503+
2504+
if (!minimum.has_value() || !maximum.has_value())
2505+
return {};
2506+
2507+
auto minimum_value = minimum->value();
2508+
auto maximum_value = maximum->value();
2509+
2510+
// https://drafts.csswg.org/css-values-5/#random-infinities
2511+
// If the maximum value is less than the minimum value, it behaves as if it’s equal to the minimum value.
2512+
if (maximum_value < minimum_value)
2513+
maximum_value = minimum_value;
2514+
2515+
// https://drafts.csswg.org/css-values-5/#random-infinities
2516+
// In random(A, B), if A is infinite, the result is infinite.
2517+
if (isinf(minimum_value))
2518+
return CalculatedStyleValue::CalculationResult { AK::Infinity<double>, numeric_type() };
2519+
2520+
// If A is finite, but the difference between A and B is either infinite or large enough to be treated as infinite
2521+
// in the user agent, the result is NaN.
2522+
if (isinf(maximum_value))
2523+
return CalculatedStyleValue::CalculationResult { AK::NaN<double>, numeric_type() };
2524+
2525+
// Note: As usual for math functions, if any argument calculation is NaN, the result is NaN.
2526+
if (isnan(minimum_value) || isnan(maximum_value))
2527+
return CalculatedStyleValue::CalculationResult { AK::NaN<double>, numeric_type() };
2528+
2529+
// Given a random function with a random base value R, the value of the function is:
2530+
// - for a random() function with min and max, but no step
2531+
// Return min + R * (max - min)
2532+
return CalculatedStyleValue::CalculationResult {
2533+
minimum_value + (random_base_value * (maximum_value - minimum_value)),
2534+
numeric_type()
2535+
};
2536+
}
2537+
2538+
String RandomCalculationNode::to_string(CalculationContext const& context, SerializationMode serialization_mode) const
2539+
{
2540+
StringBuilder builder;
2541+
2542+
builder.append("random("sv);
2543+
builder.appendff("{}, ", m_random_value_sharing->to_string(serialization_mode));
2544+
builder.appendff("{}, ", serialize_a_calculation_tree(m_minimum, context, serialization_mode));
2545+
builder.appendff("{})", serialize_a_calculation_tree(m_maximum, context, serialization_mode));
2546+
2547+
return builder.to_string_without_validation();
2548+
}
2549+
2550+
void RandomCalculationNode::dump(StringBuilder& builder, int indent) const
2551+
{
2552+
builder.appendff("{: >{}}RANDOM:\n", "", indent);
2553+
builder.appendff("{}\n", m_random_value_sharing->to_string(SerializationMode::Normal));
2554+
m_minimum->dump(builder, indent + 2);
2555+
m_maximum->dump(builder, indent + 2);
2556+
}
2557+
2558+
bool RandomCalculationNode::equals(CalculationNode const& other) const
2559+
{
2560+
if (this == &other)
2561+
return true;
2562+
2563+
if (type() != other.type())
2564+
return false;
2565+
2566+
auto const& other_random = as<RandomCalculationNode>(other);
2567+
2568+
return m_random_value_sharing == other_random.m_random_value_sharing
2569+
&& m_minimum == other_random.m_minimum
2570+
&& m_maximum == other_random.m_maximum;
2571+
}
2572+
24372573
NonnullRefPtr<RemCalculationNode const> RemCalculationNode::create(NonnullRefPtr<CalculationNode const> x, NonnullRefPtr<CalculationNode const> y)
24382574
{
24392575
// https://www.w3.org/TR/css-values-4/#determine-the-type-of-a-calculation

Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@ class CalculationNode : public RefCounted<CalculationNode> {
190190
Mod,
191191
Rem,
192192

193+
// Random value generation
194+
// https://drafts.csswg.org/css-values-5/#random
195+
Random,
196+
193197
// Non-math functions
194198
NonMathFunction
195199
};
@@ -228,6 +232,7 @@ class CalculationNode : public RefCounted<CalculationNode> {
228232
case Type::Round:
229233
case Type::Mod:
230234
case Type::Rem:
235+
case Type::Random:
231236
return true;
232237

233238
default:
@@ -747,6 +752,30 @@ class ModCalculationNode final : public CalculationNode {
747752
NonnullRefPtr<CalculationNode const> m_y;
748753
};
749754

755+
class RandomCalculationNode final : public CalculationNode {
756+
public:
757+
static NonnullRefPtr<RandomCalculationNode const> create(NonnullRefPtr<RandomValueSharingStyleValue const>, NonnullRefPtr<CalculationNode const> minimum, NonnullRefPtr<CalculationNode const> maximum);
758+
~RandomCalculationNode();
759+
760+
virtual bool contains_percentage() const override;
761+
virtual NonnullRefPtr<CalculationNode const> with_simplified_children(CalculationContext const&, CalculationResolutionContext const&) const override;
762+
virtual Optional<CalculatedStyleValue::CalculationResult> run_operation_if_possible(CalculationContext const&, CalculationResolutionContext const&) const override;
763+
764+
// NOTE: We don't return children here as serialization is handled ad-hoc
765+
virtual Vector<NonnullRefPtr<CalculationNode const>> children() const override { return {}; }
766+
767+
String to_string(CalculationContext const&, SerializationMode serialization_mode) const;
768+
769+
virtual void dump(StringBuilder&, int indent) const override;
770+
virtual bool equals(CalculationNode const&) const override;
771+
772+
private:
773+
RandomCalculationNode(NonnullRefPtr<RandomValueSharingStyleValue const>, NonnullRefPtr<CalculationNode const>, NonnullRefPtr<CalculationNode const>, Optional<NumericType>);
774+
ValueComparingNonnullRefPtr<RandomValueSharingStyleValue const> m_random_value_sharing;
775+
ValueComparingNonnullRefPtr<CalculationNode const> m_minimum;
776+
ValueComparingNonnullRefPtr<CalculationNode const> m_maximum;
777+
};
778+
750779
class RemCalculationNode final : public CalculationNode {
751780
public:
752781
static NonnullRefPtr<RemCalculationNode const> create(NonnullRefPtr<CalculationNode const>, NonnullRefPtr<CalculationNode const>);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) 2025, Callum Law <callumlaw1709@outlook.com>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include "RandomValueSharingStyleValue.h"
8+
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
9+
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
10+
11+
namespace Web::CSS {
12+
13+
ValueComparingNonnullRefPtr<StyleValue const> RandomValueSharingStyleValue::absolutized(ComputationContext const& computation_context) const
14+
{
15+
if (m_fixed_value) {
16+
auto const& absolutized_fixed_value = m_fixed_value->absolutized(computation_context);
17+
18+
if (m_fixed_value == absolutized_fixed_value)
19+
return *this;
20+
21+
return RandomValueSharingStyleValue::create_fixed(absolutized_fixed_value);
22+
}
23+
24+
TODO();
25+
}
26+
27+
double RandomValueSharingStyleValue::random_base_value() const
28+
{
29+
VERIFY(m_fixed_value);
30+
VERIFY(m_fixed_value->is_number() || (m_fixed_value->is_calculated() && m_fixed_value->as_calculated().resolves_to_number()));
31+
32+
if (m_fixed_value->is_number())
33+
return m_fixed_value->as_number().number();
34+
35+
if (m_fixed_value->is_calculated())
36+
return m_fixed_value->as_calculated().resolve_number({}).value();
37+
38+
VERIFY_NOT_REACHED();
39+
}
40+
41+
String RandomValueSharingStyleValue::to_string(SerializationMode serialization_mode) const
42+
{
43+
if (m_fixed_value)
44+
return MUST(String::formatted("fixed {}", m_fixed_value->to_string(serialization_mode)));
45+
46+
TODO();
47+
}
48+
49+
}

0 commit comments

Comments
 (0)