LibWeb: Parse the will-change property

This property provides a hint to the rendering engine about properties
that are likely to change in the near future, allowing for early
optimizations to be applied.
This commit is contained in:
Tim Ledbetter 2025-08-16 08:39:18 +01:00 committed by Sam Atkins
commit 4f663ca6e7
Notes: github-actions[bot] 2025-08-18 11:37:59 +00:00
16 changed files with 335 additions and 2 deletions

View file

@ -1911,4 +1911,46 @@ ScrollbarWidth ComputedProperties::scrollbar_width() const
return keyword_to_scrollbar_width(value.to_keyword()).release_value();
}
WillChange ComputedProperties::will_change() const
{
auto const& value = property(PropertyID::WillChange);
if (value.to_keyword() == Keyword::Auto)
return WillChange::make_auto();
auto to_will_change_entry = [](StyleValue const& value) -> Optional<WillChange::WillChangeEntry> {
if (value.is_keyword()) {
switch (value.as_keyword().keyword()) {
case Keyword::Contents:
return WillChange::Type::Contents;
case Keyword::ScrollPosition:
return WillChange::Type::ScrollPosition;
default:
VERIFY_NOT_REACHED();
}
}
VERIFY(value.is_custom_ident());
auto custom_ident = value.as_custom_ident().custom_ident();
auto property_id = property_id_from_string(custom_ident);
if (!property_id.has_value())
return {};
return property_id.release_value();
};
if (value.is_value_list()) {
auto const& value_list = value.as_value_list();
Vector<WillChange::WillChangeEntry> will_change_entries;
for (auto const& style_value : value_list.values()) {
if (auto entry = to_will_change_entry(*style_value); entry.has_value())
will_change_entries.append(*entry);
}
return WillChange(move(will_change_entries));
}
auto will_change_entry = to_will_change_entry(value);
if (will_change_entry.has_value())
return WillChange({ *will_change_entry });
return WillChange::make_auto();
}
}

View file

@ -196,6 +196,8 @@ public:
ClipRule clip_rule() const;
float flood_opacity() const;
WillChange will_change() const;
Gfx::FontCascadeList const& computed_font_list() const
{
VERIFY(m_font_list);

View file

@ -87,6 +87,33 @@ struct ScrollbarColorData {
Color track_color { Color::Transparent };
};
struct WillChange {
enum class Type : u8 {
Contents,
ScrollPosition,
};
using WillChangeEntry = Variant<Type, PropertyID>;
WillChange(Vector<WillChangeEntry> values)
: m_value(move(values))
{
}
static WillChange make_auto() { return WillChange(); }
bool is_auto() const { return m_value.is_empty(); }
bool has_contents() const { return m_value.contains_slow(Type::Contents); }
bool has_scroll_position() const { return m_value.contains_slow(Type::ScrollPosition); }
bool has_property(PropertyID property_id) const { return m_value.contains_slow(property_id); }
private:
WillChange()
{
}
Vector<WillChangeEntry> m_value;
};
using CursorData = Variant<NonnullRefPtr<CursorStyleValue const>, CursorPredefined>;
using ListStyleType = Variant<CounterStyleNameKeyword, String>;
@ -231,6 +258,7 @@ public:
};
}
static CSS::ScrollbarWidth scrollbar_width() { return CSS::ScrollbarWidth::Auto; }
static WillChange will_change() { return WillChange::make_auto(); }
};
enum class BackgroundSize {
@ -609,6 +637,8 @@ public:
ScrollbarColorData scrollbar_color() const { return m_inherited.scrollbar_color; }
CSS::ScrollbarWidth scrollbar_width() const { return m_noninherited.scrollbar_width; }
WillChange will_change() const { return m_noninherited.will_change; }
NonnullOwnPtr<ComputedValues> clone_inherited_values() const
{
auto clone = make<ComputedValues>();
@ -804,6 +834,8 @@ protected:
Vector<CounterData, 0> counter_reset;
Vector<CounterData, 0> counter_set;
WillChange will_change { InitialValues::will_change() };
Color flood_color { InitialValues::flood_color() };
float flood_opacity { InitialValues::flood_opacity() };
} m_noninherited;
@ -1014,6 +1046,8 @@ public:
void set_counter_increment(Vector<CounterData> value) { m_noninherited.counter_increment = move(value); }
void set_counter_reset(Vector<CounterData> value) { m_noninherited.counter_reset = move(value); }
void set_counter_set(Vector<CounterData> value) { m_noninherited.counter_set = move(value); }
void set_will_change(WillChange value) { m_noninherited.will_change = move(value); }
};
}

View file

@ -449,6 +449,7 @@
"scale-down",
"screen",
"scroll",
"scroll-position",
"scrollbar",
"se-resize",
"searchfield",

View file

@ -490,6 +490,7 @@ private:
RefPtr<StyleValue const> parse_touch_action_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue const> parse_white_space_shorthand(TokenStream<ComponentValue>&);
RefPtr<StyleValue const> parse_white_space_trim_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue const> parse_will_change_value(TokenStream<ComponentValue>&);
RefPtr<StyleValue const> parse_list_of_time_values(PropertyID, TokenStream<ComponentValue>&);

View file

@ -797,6 +797,11 @@ Parser::ParseErrorOr<NonnullRefPtr<StyleValue const>> Parser::parse_css_value(Pr
if (auto parsed_value = parse_white_space_trim_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();
return ParseError::SyntaxError;
case PropertyID::WillChange:
if (auto parsed_value = parse_will_change_value(tokens); parsed_value && !tokens.has_next_token())
return parsed_value.release_nonnull();
return ParseError::SyntaxError;
default:
break;
}
@ -5692,4 +5697,28 @@ RefPtr<StyleValue const> Parser::parse_white_space_shorthand(TokenStream<Compone
return make_whitespace_shorthand(white_space_collapse, text_wrap_mode, white_space_trim);
}
// https://drafts.csswg.org/css-will-change/#will-change
RefPtr<StyleValue const> Parser::parse_will_change_value(TokenStream<ComponentValue>& tokens)
{
// auto | <animateable-feature>#
// <animateable-feature> = scroll-position | contents | <custom-ident>
if (parse_all_as_single_keyword_value(tokens, Keyword::Auto))
return KeywordStyleValue::create(Keyword::Auto);
auto parse_animateable_feature = [this](TokenStream<ComponentValue>& tokens) -> RefPtr<StyleValue const> {
auto style_value = parse_css_value_for_property(PropertyID::WillChange, tokens);
if (!style_value)
return nullptr;
if (style_value->to_keyword() == Keyword::Auto)
return nullptr;
return style_value;
};
return parse_comma_separated_value_list(tokens, [&parse_animateable_feature](auto& tokens) {
return parse_animateable_feature(tokens);
});
}
}

View file

@ -3565,6 +3565,21 @@
"unitless-length"
]
},
"will-change": {
"affects-layout": false,
"affects-stacking-context": true,
"animation-type": "none",
"inherited": false,
"initial": "auto",
"valid-types": [
"custom-ident ![all,auto,contents,none,scroll-position,will-change]"
],
"valid-identifiers": [
"auto",
"scroll-position",
"contents"
]
},
"word-break": {
"animation-type": "discrete",
"initial": "normal",

View file

@ -1015,6 +1015,7 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)
computed_values.set_mix_blend_mode(computed_style.mix_blend_mode());
computed_values.set_view_transition_name(computed_style.view_transition_name());
computed_values.set_contain(computed_style.contain());
computed_values.set_will_change(computed_style.will_change());
computed_values.set_caret_color(computed_style.caret_color(*this));
computed_values.set_color_interpolation(computed_style.color_interpolation());

View file

@ -258,6 +258,7 @@ All properties associated with getComputedStyle(document.body):
"view-transition-name",
"white-space-trim",
"width",
"will-change",
"x",
"y",
"z-index"

View file

@ -725,6 +725,8 @@ All supported properties and their default values exposed from CSSStylePropertie
'whiteSpaceTrim': 'none'
'white-space-trim': 'none'
'width': '284px'
'willChange': 'auto'
'will-change': 'auto'
'wordBreak': 'normal'
'word-break': 'normal'
'wordSpacing': 'normal'

View file

@ -256,6 +256,7 @@ vertical-align: baseline
view-transition-name: none
white-space-trim: none
width: 784px
will-change: auto
x: 0px
y: 0px
z-index: auto

View file

@ -1,8 +1,8 @@
Harness status: OK
Found 255 tests
Found 256 tests
248 Pass
249 Pass
7 Fail
Pass accent-color
Pass border-collapse
@ -256,6 +256,7 @@ Pass vertical-align
Pass view-transition-name
Pass white-space-trim
Fail width
Pass will-change
Pass x
Pass y
Pass z-index

View file

@ -0,0 +1,111 @@
Harness status: OK
Found 106 tests
106 Pass
Pass e.style['will-change'] = "auto transform" should not set the property value
Pass e.style['will-change'] = "auto, transform" should not set the property value
Pass e.style['will-change'] = "contents auto" should not set the property value
Pass e.style['will-change'] = "contents, auto" should not set the property value
Pass e.style['will-change'] = "transform, initial" should not set the property value
Pass e.style['will-change'] = "initial, transform" should not set the property value
Pass e.style['will-change'] = "initial, initial" should not set the property value
Pass e.style['will-change'] = "initial, inherit" should not set the property value
Pass e.style['will-change'] = "initial, unset" should not set the property value
Pass e.style['will-change'] = "initial, revert" should not set the property value
Pass e.style['will-change'] = "initial, revert-layer" should not set the property value
Pass e.style['will-change'] = "initial, default" should not set the property value
Pass e.style['will-change'] = "initial, will-change" should not set the property value
Pass e.style['will-change'] = "initial, none" should not set the property value
Pass e.style['will-change'] = "initial, all" should not set the property value
Pass e.style['will-change'] = "transform, inherit" should not set the property value
Pass e.style['will-change'] = "inherit, transform" should not set the property value
Pass e.style['will-change'] = "inherit, initial" should not set the property value
Pass e.style['will-change'] = "inherit, inherit" should not set the property value
Pass e.style['will-change'] = "inherit, unset" should not set the property value
Pass e.style['will-change'] = "inherit, revert" should not set the property value
Pass e.style['will-change'] = "inherit, revert-layer" should not set the property value
Pass e.style['will-change'] = "inherit, default" should not set the property value
Pass e.style['will-change'] = "inherit, will-change" should not set the property value
Pass e.style['will-change'] = "inherit, none" should not set the property value
Pass e.style['will-change'] = "inherit, all" should not set the property value
Pass e.style['will-change'] = "transform, unset" should not set the property value
Pass e.style['will-change'] = "unset, transform" should not set the property value
Pass e.style['will-change'] = "unset, initial" should not set the property value
Pass e.style['will-change'] = "unset, inherit" should not set the property value
Pass e.style['will-change'] = "unset, unset" should not set the property value
Pass e.style['will-change'] = "unset, revert" should not set the property value
Pass e.style['will-change'] = "unset, revert-layer" should not set the property value
Pass e.style['will-change'] = "unset, default" should not set the property value
Pass e.style['will-change'] = "unset, will-change" should not set the property value
Pass e.style['will-change'] = "unset, none" should not set the property value
Pass e.style['will-change'] = "unset, all" should not set the property value
Pass e.style['will-change'] = "transform, revert" should not set the property value
Pass e.style['will-change'] = "revert, transform" should not set the property value
Pass e.style['will-change'] = "revert, initial" should not set the property value
Pass e.style['will-change'] = "revert, inherit" should not set the property value
Pass e.style['will-change'] = "revert, unset" should not set the property value
Pass e.style['will-change'] = "revert, revert" should not set the property value
Pass e.style['will-change'] = "revert, revert-layer" should not set the property value
Pass e.style['will-change'] = "revert, default" should not set the property value
Pass e.style['will-change'] = "revert, will-change" should not set the property value
Pass e.style['will-change'] = "revert, none" should not set the property value
Pass e.style['will-change'] = "revert, all" should not set the property value
Pass e.style['will-change'] = "transform, revert-layer" should not set the property value
Pass e.style['will-change'] = "revert-layer, transform" should not set the property value
Pass e.style['will-change'] = "revert-layer, initial" should not set the property value
Pass e.style['will-change'] = "revert-layer, inherit" should not set the property value
Pass e.style['will-change'] = "revert-layer, unset" should not set the property value
Pass e.style['will-change'] = "revert-layer, revert" should not set the property value
Pass e.style['will-change'] = "revert-layer, revert-layer" should not set the property value
Pass e.style['will-change'] = "revert-layer, default" should not set the property value
Pass e.style['will-change'] = "revert-layer, will-change" should not set the property value
Pass e.style['will-change'] = "revert-layer, none" should not set the property value
Pass e.style['will-change'] = "revert-layer, all" should not set the property value
Pass e.style['will-change'] = "transform, default" should not set the property value
Pass e.style['will-change'] = "default, transform" should not set the property value
Pass e.style['will-change'] = "default, initial" should not set the property value
Pass e.style['will-change'] = "default, inherit" should not set the property value
Pass e.style['will-change'] = "default, unset" should not set the property value
Pass e.style['will-change'] = "default, revert" should not set the property value
Pass e.style['will-change'] = "default, revert-layer" should not set the property value
Pass e.style['will-change'] = "default, default" should not set the property value
Pass e.style['will-change'] = "default, will-change" should not set the property value
Pass e.style['will-change'] = "default, none" should not set the property value
Pass e.style['will-change'] = "default, all" should not set the property value
Pass e.style['will-change'] = "transform, will-change" should not set the property value
Pass e.style['will-change'] = "will-change, transform" should not set the property value
Pass e.style['will-change'] = "will-change, initial" should not set the property value
Pass e.style['will-change'] = "will-change, inherit" should not set the property value
Pass e.style['will-change'] = "will-change, unset" should not set the property value
Pass e.style['will-change'] = "will-change, revert" should not set the property value
Pass e.style['will-change'] = "will-change, revert-layer" should not set the property value
Pass e.style['will-change'] = "will-change, default" should not set the property value
Pass e.style['will-change'] = "will-change, will-change" should not set the property value
Pass e.style['will-change'] = "will-change, none" should not set the property value
Pass e.style['will-change'] = "will-change, all" should not set the property value
Pass e.style['will-change'] = "transform, none" should not set the property value
Pass e.style['will-change'] = "none, transform" should not set the property value
Pass e.style['will-change'] = "none, initial" should not set the property value
Pass e.style['will-change'] = "none, inherit" should not set the property value
Pass e.style['will-change'] = "none, unset" should not set the property value
Pass e.style['will-change'] = "none, revert" should not set the property value
Pass e.style['will-change'] = "none, revert-layer" should not set the property value
Pass e.style['will-change'] = "none, default" should not set the property value
Pass e.style['will-change'] = "none, will-change" should not set the property value
Pass e.style['will-change'] = "none, none" should not set the property value
Pass e.style['will-change'] = "none, all" should not set the property value
Pass e.style['will-change'] = "transform, all" should not set the property value
Pass e.style['will-change'] = "all, transform" should not set the property value
Pass e.style['will-change'] = "all, initial" should not set the property value
Pass e.style['will-change'] = "all, inherit" should not set the property value
Pass e.style['will-change'] = "all, unset" should not set the property value
Pass e.style['will-change'] = "all, revert" should not set the property value
Pass e.style['will-change'] = "all, revert-layer" should not set the property value
Pass e.style['will-change'] = "all, default" should not set the property value
Pass e.style['will-change'] = "all, will-change" should not set the property value
Pass e.style['will-change'] = "all, none" should not set the property value
Pass e.style['will-change'] = "all, all" should not set the property value
Pass e.style['will-change'] = "will-change" should not set the property value
Pass e.style['will-change'] = "none" should not set the property value
Pass e.style['will-change'] = "all" should not set the property value

View file

@ -0,0 +1,14 @@
Harness status: OK
Found 9 tests
9 Pass
Pass e.style['will-change'] = "auto" should set the property value
Pass e.style['will-change'] = "scroll-position" should set the property value
Pass e.style['will-change'] = "contents" should set the property value
Pass e.style['will-change'] = "transform" should set the property value
Pass e.style['will-change'] = "background-color" should set the property value
Pass e.style['will-change'] = "scroll-position, contents" should set the property value
Pass e.style['will-change'] = "scroll-position, transform" should set the property value
Pass e.style['will-change'] = "contents, transform" should set the property value
Pass e.style['will-change'] = "transform, background-color" should set the property value

View file

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS Will Change Test: parsing will-change with invalid values</title>
<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
<link rel="help" href="https://drafts.csswg.org/css-will-change/#propdef-will-change">
<meta name="assert" content="will-change only supports the grammar 'auto | <animateable-feature>#'.">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../css/support/parsing-testcommon.js"></script>
</head>
<body>
<script>
test_invalid_value("will-change", "auto transform");
test_invalid_value("will-change", "auto, transform");
test_invalid_value("will-change", "contents auto");
test_invalid_value("will-change", "contents, auto");
let excludedKeywords = [
// CSS-wide keywords are excluded from <custom-ident>
// https://drafts.csswg.org/css-values-4/#identifier-value
"initial",
"inherit",
"unset",
"revert",
"revert-layer",
"default",
// will-change additionally excludes the following from <custom-ident>
"will-change",
"none",
"all",
];
for (let keyword of excludedKeywords) {
test_invalid_value("will-change", `transform, ${keyword}`);
test_invalid_value("will-change", `${keyword}, transform`);
for (let k of excludedKeywords) {
test_invalid_value("will-change", `${keyword}, ${k}`);
}
}
test_invalid_value("will-change", "will-change");
test_invalid_value("will-change", "none");
test_invalid_value("will-change", "all");
</script>
</body>
</html>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS Will Change Test: parsing will-change with valid values</title>
<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
<link rel="help" href="https://drafts.csswg.org/css-will-change/#propdef-will-change">
<meta name="assert" content="will-change supports the full grammar 'auto | <animateable-feature>#'.">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../css/support/parsing-testcommon.js"></script>
</head>
<body>
<script>
test_valid_value("will-change", "auto");
// <animateable-feature> = scroll-position | contents | <custom-ident>
test_valid_value("will-change", "scroll-position");
test_valid_value("will-change", "contents");
test_valid_value("will-change", "transform");
test_valid_value("will-change", "background-color");
test_valid_value("will-change", "scroll-position, contents");
test_valid_value("will-change", "scroll-position, transform");
test_valid_value("will-change", "contents, transform");
test_valid_value("will-change", "transform, background-color");
</script>
</body>
</html>