LibWeb: Support non-fixed <random-value-sharing>

This works by generating random values using XorShift128PlusRNG at
compute time and then caching them on the document using the relevant
random-caching-key
This commit is contained in:
Callum Law 2025-11-05 13:27:48 +13:00 committed by Sam Atkins
parent 86e6aa0291
commit 12e8f503aa
Notes: github-actions[bot] 2025-12-01 11:01:47 +00:00
15 changed files with 226 additions and 54 deletions

View file

@ -192,6 +192,7 @@
"ease-in",
"ease-in-out",
"ease-out",
"element-shared",
"ellipsis",
"embed",
"emoji",

View file

@ -1724,6 +1724,15 @@ bool Parser::context_allows_random_functions() const
return m_value_context.find_first_index_if([](ValueParsingContext context) { return context.has<PropertyID>(); }).has_value();
}
FlyString Parser::random_value_sharing_auto_name() const
{
auto top_level_property_context_index = m_value_context.find_first_index_if([](ValueParsingContext const& context) { return context.has<PropertyID>(); });
auto property_name = string_from_property_id(m_value_context[top_level_property_context_index.value()].get<PropertyID>());
return MUST(String::formatted("{} {}", property_name, m_random_function_index));
}
Vector<ComponentValue> Parser::parse_as_list_of_component_values()
{
return parse_a_list_of_component_values(m_token_stream);

View file

@ -603,6 +603,7 @@ private:
bool context_allows_quirky_length() const;
bool context_allows_tree_counting_functions() const;
bool context_allows_random_functions() const;
FlyString random_value_sharing_auto_name() const;
Vector<RuleContext> m_rule_context;
HashTable<FlyString> m_declared_namespaces;

View file

@ -3798,6 +3798,9 @@ RefPtr<RandomValueSharingStyleValue const> Parser::parse_random_value_sharing(To
tokens.discard_whitespace();
if (!tokens.has_next_token())
return nullptr;
// fixed <number [0,1]>
if (tokens.next_token().is_ident("fixed"sv)) {
tokens.discard_a_token();
@ -3820,8 +3823,51 @@ RefPtr<RandomValueSharingStyleValue const> Parser::parse_random_value_sharing(To
return nullptr;
}
// FIXME: Support non-fixed values
return nullptr;
// [ [ auto | <dashed-ident> ] || element-shared ]
bool has_explicit_auto = false;
Optional<FlyString> dashed_ident;
bool element_shared = false;
while (tokens.has_next_token()) {
if (auto maybe_dashed_ident_value = parse_dashed_ident_value(tokens)) {
if (has_explicit_auto || dashed_ident.has_value())
return nullptr;
dashed_ident = maybe_dashed_ident_value->custom_ident();
tokens.discard_whitespace();
continue;
}
auto maybe_keyword_value = parse_keyword_value(tokens);
if (maybe_keyword_value && maybe_keyword_value->to_keyword() == Keyword::Auto) {
if (has_explicit_auto || dashed_ident.has_value())
return nullptr;
has_explicit_auto = true;
tokens.discard_whitespace();
continue;
}
if (maybe_keyword_value && maybe_keyword_value->to_keyword() == Keyword::ElementShared) {
if (element_shared)
return nullptr;
element_shared = true;
tokens.discard_whitespace();
continue;
}
return nullptr;
}
if (!dashed_ident.has_value())
return RandomValueSharingStyleValue::create_auto(random_value_sharing_auto_name(), element_shared);
return RandomValueSharingStyleValue::create_dashed_ident(dashed_ident.value(), element_shared);
}
// https://drafts.csswg.org/css-values-4/#typedef-dashed-ident

View file

@ -2595,7 +2595,9 @@ String RandomCalculationNode::to_string(CalculationContext const& context, Seria
StringBuilder builder;
builder.append("random("sv);
builder.appendff("{}, ", m_random_value_sharing->to_string(serialization_mode));
auto random_value_sharing_stringified = m_random_value_sharing->to_string(serialization_mode);
if (!random_value_sharing_stringified.is_empty())
builder.appendff("{}, ", random_value_sharing_stringified);
builder.appendff("{}, ", serialize_a_calculation_tree(m_minimum, context, serialization_mode));
builder.append(serialize_a_calculation_tree(m_maximum, context, serialization_mode));
if (m_step)

View file

@ -7,11 +7,15 @@
#include "RandomValueSharingStyleValue.h"
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
#include <LibWeb/DOM/Document.h>
namespace Web::CSS {
ValueComparingNonnullRefPtr<StyleValue const> RandomValueSharingStyleValue::absolutized(ComputationContext const& computation_context) const
{
// https://drafts.csswg.org/css-values-5/#random-caching
// Each instance of a random function in styles has an associated random base value.
// If the random functions <random-value-sharing> is fixed <number>, the random base value is that number.
if (m_fixed_value) {
auto const& absolutized_fixed_value = m_fixed_value->absolutized(computation_context);
@ -21,7 +25,29 @@ ValueComparingNonnullRefPtr<StyleValue const> RandomValueSharingStyleValue::abso
return RandomValueSharingStyleValue::create_fixed(absolutized_fixed_value);
}
TODO();
// Otherwise, the random base value is a pseudo-random real number in the range `[0, 1)` (greater than or equal to 0
// and less than 1), generated from a uniform distribution, and influenced by the functions random caching key.
// A random caching key is a tuple of:
RandomCachingKey random_caching_key {
// 1. A string name: the value of the <dashed-ident>, if specified in <random-value-sharing>; or else a string
// of the form "PROPERTY N", where PROPERTY is the name of the property the random function is used in
// (before shorthand expansion, if relevant), and N is the index of the random function among other random
// functions in the same property value.
.name = m_name.value(),
// 2. An element ID identifying the element the style is being applied to, or null if element-shared is
// specified in <random-value-sharing>.
// FIXME: Use the pseudo element's unique_id() when that's accessible
.element_id = m_element_shared ? Optional<UniqueNodeID> { OptionalNone {} } : Optional<UniqueNodeID> { computation_context.abstract_element->element().unique_id() },
// 3. A document ID identifying the Document the styles are from.
// NB: This is implicit since the cache is stored on the document or the element (which is a child of the document).
};
auto random_base_value = const_cast<DOM::Element&>(computation_context.abstract_element->element()).ensure_css_random_base_value(random_caching_key);
return RandomValueSharingStyleValue::create_fixed(NumberStyleValue::create(random_base_value));
}
double RandomValueSharingStyleValue::random_base_value() const
@ -43,7 +69,18 @@ String RandomValueSharingStyleValue::to_string(SerializationMode serialization_m
if (m_fixed_value)
return MUST(String::formatted("fixed {}", m_fixed_value->to_string(serialization_mode)));
TODO();
StringBuilder builder;
if (!m_is_auto)
builder.appendff("{}", m_name.value());
if (m_element_shared) {
if (!builder.is_empty())
builder.append(' ');
builder.append("element-shared"sv);
}
return builder.to_string_without_validation();
}
}

View file

@ -11,11 +11,26 @@
namespace Web::CSS {
struct RandomCachingKey {
FlyString name;
Optional<Web::UniqueNodeID> element_id;
};
class RandomValueSharingStyleValue : public StyleValueWithDefaultOperators<RandomValueSharingStyleValue> {
public:
static ValueComparingNonnullRefPtr<RandomValueSharingStyleValue const> create_fixed(NonnullRefPtr<StyleValue const> const& fixed_value)
{
return adopt_ref(*new (nothrow) RandomValueSharingStyleValue(fixed_value));
return adopt_ref(*new (nothrow) RandomValueSharingStyleValue(fixed_value, false, {}, false));
}
static ValueComparingNonnullRefPtr<RandomValueSharingStyleValue const> create_auto(FlyString name, bool element_shared)
{
return adopt_ref(*new (nothrow) RandomValueSharingStyleValue({}, true, move(name), element_shared));
}
static ValueComparingNonnullRefPtr<RandomValueSharingStyleValue const> create_dashed_ident(FlyString name, bool element_shared)
{
return adopt_ref(*new (nothrow) RandomValueSharingStyleValue({}, false, move(name), element_shared));
}
virtual ~RandomValueSharingStyleValue() override = default;
@ -28,17 +43,46 @@ public:
bool properties_equal(RandomValueSharingStyleValue const& other) const
{
return m_fixed_value == other.m_fixed_value;
return m_fixed_value == other.m_fixed_value
&& m_is_auto == other.m_is_auto
&& m_name == other.m_name
&& m_element_shared == other.m_element_shared;
}
private:
explicit RandomValueSharingStyleValue(RefPtr<StyleValue const> fixed_value)
explicit RandomValueSharingStyleValue(RefPtr<StyleValue const> fixed_value, bool is_auto, Optional<FlyString> name, bool element_shared)
: StyleValueWithDefaultOperators(Type::RandomValueSharing)
, m_fixed_value(move(fixed_value))
, m_is_auto(is_auto)
, m_name(move(name))
, m_element_shared(element_shared)
{
}
ValueComparingRefPtr<StyleValue const> m_fixed_value;
bool m_is_auto;
Optional<FlyString> m_name;
bool m_element_shared;
};
}
namespace AK {
template<>
struct Traits<Web::CSS::RandomCachingKey> : public DefaultTraits<Web::CSS::RandomCachingKey> {
static unsigned hash(Web::CSS::RandomCachingKey const& key)
{
if (!key.element_id.has_value())
return key.name.hash();
return pair_int_hash(key.name.hash(), Traits<i64>::hash(key.element_id->value()));
}
static bool equals(Web::CSS::RandomCachingKey const& a, Web::CSS::RandomCachingKey const& b)
{
return a.element_id == b.element_id && a.name == b.name;
}
};
}