Skip to content

Commit d1fbb7b

Browse files
kalenikaliaksandrawesomekling
authored andcommitted
LibWeb: Invalidate less elements affected by CSS custom properties
Before this change, whenever element's attributes changed, we would add a flag to "pending invalidation", indicating that all descendants whose style uses CSS custom properties needed to be recomputed. This resulted in severe overinvalidation, because we would run invalidation regardless of whether any custom property on affected element actually changed. This change takes another approach, and now we decide whether descendant's style needs to be recomputed based on whether ancestor's style recomputation results in a change of custom properties, though this approach adds a little overhead to style computation as now we have to compare old vs new hashmap of custom properties. This brings substantial improvement on discord and x.com where, before this change, advantage of using invalidation sets was lost and we had to recompute all descendants, because almost all of them use custom properties.
1 parent cbe4ba6 commit d1fbb7b

File tree

13 files changed

+71
-67
lines changed

13 files changed

+71
-67
lines changed

Libraries/LibWeb/CSS/Parser/ValueParsing.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4339,8 +4339,10 @@ NonnullRefPtr<CSSStyleValue const> Parser::resolve_unresolved_style_value(Parsin
43394339
NonnullRefPtr<CSSStyleValue const> Parser::resolve_unresolved_style_value(DOM::AbstractElement& element, GuardedSubstitutionContexts& guarded_contexts, PropertyIDOrCustomPropertyName property, UnresolvedStyleValue const& unresolved)
43404340
{
43414341
// AD-HOC: Report that we might rely on custom properties.
4342-
// FIXME: This over-invalidates. Find a way of invalidating only when we need to - specifically, when var() is used.
4343-
element.element().set_style_uses_css_custom_properties(true);
4342+
if (unresolved.includes_attr_function())
4343+
element.element().set_style_uses_attr_css_function();
4344+
if (unresolved.includes_var_function())
4345+
element.element().set_style_uses_var_css_function();
43444346

43454347
// To replace substitution functions in a property prop:
43464348
auto const& property_name = property.visit(

Libraries/LibWeb/CSS/StyleComputer.cpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2454,17 +2454,17 @@ GC::Ref<ComputedProperties> StyleComputer::create_document_style() const
24542454
return style;
24552455
}
24562456

2457-
GC::Ref<ComputedProperties> StyleComputer::compute_style(DOM::Element& element, Optional<CSS::PseudoElement> pseudo_element) const
2457+
GC::Ref<ComputedProperties> StyleComputer::compute_style(DOM::Element& element, Optional<CSS::PseudoElement> pseudo_element, Optional<bool&> did_change_custom_properties) const
24582458
{
2459-
return *compute_style_impl(element, move(pseudo_element), ComputeStyleMode::Normal);
2459+
return *compute_style_impl(element, move(pseudo_element), ComputeStyleMode::Normal, did_change_custom_properties);
24602460
}
24612461

2462-
GC::Ptr<ComputedProperties> StyleComputer::compute_pseudo_element_style_if_needed(DOM::Element& element, Optional<CSS::PseudoElement> pseudo_element) const
2462+
GC::Ptr<ComputedProperties> StyleComputer::compute_pseudo_element_style_if_needed(DOM::Element& element, Optional<CSS::PseudoElement> pseudo_element, Optional<bool&> did_change_custom_properties) const
24632463
{
2464-
return compute_style_impl(element, move(pseudo_element), ComputeStyleMode::CreatePseudoElementStyleIfNeeded);
2464+
return compute_style_impl(element, move(pseudo_element), ComputeStyleMode::CreatePseudoElementStyleIfNeeded, did_change_custom_properties);
24652465
}
24662466

2467-
GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::Element& element, Optional<CSS::PseudoElement> pseudo_element, ComputeStyleMode mode) const
2467+
GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::Element& element, Optional<CSS::PseudoElement> pseudo_element, ComputeStyleMode mode, Optional<bool&> did_change_custom_properties) const
24682468
{
24692469
build_rule_cache_if_needed();
24702470

@@ -2488,6 +2488,9 @@ GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::Element& elem
24882488
PseudoClassBitmap attempted_pseudo_class_matches;
24892489
auto matching_rule_set = build_matching_rule_set(element, pseudo_element, attempted_pseudo_class_matches, did_match_any_pseudo_element_rules, mode);
24902490

2491+
DOM::AbstractElement abstract_element { element, pseudo_element };
2492+
auto old_custom_properties = abstract_element.custom_properties();
2493+
24912494
// Resolve all the CSS custom properties ("variables") for this element:
24922495
// FIXME: Also resolve !important custom properties, in a second cascade.
24932496
if (!pseudo_element.has_value() || pseudo_element_supports_property(*pseudo_element, PropertyID::Custom)) {
@@ -2533,6 +2536,11 @@ GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::Element& elem
25332536

25342537
auto computed_properties = compute_properties(element, pseudo_element, cascaded_properties);
25352538
computed_properties->set_attempted_pseudo_class_matches(attempted_pseudo_class_matches);
2539+
2540+
if (did_change_custom_properties.has_value() && abstract_element.custom_properties() != old_custom_properties) {
2541+
*did_change_custom_properties = true;
2542+
}
2543+
25362544
return computed_properties;
25372545
}
25382546

Libraries/LibWeb/CSS/StyleComputer.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ class StyleComputer {
145145

146146
[[nodiscard]] GC::Ref<ComputedProperties> create_document_style() const;
147147

148-
[[nodiscard]] GC::Ref<ComputedProperties> compute_style(DOM::Element&, Optional<CSS::PseudoElement> = {}) const;
149-
[[nodiscard]] GC::Ptr<ComputedProperties> compute_pseudo_element_style_if_needed(DOM::Element&, Optional<CSS::PseudoElement>) const;
148+
[[nodiscard]] GC::Ref<ComputedProperties> compute_style(DOM::Element&, Optional<CSS::PseudoElement> = {}, Optional<bool&> did_change_custom_properties = {}) const;
149+
[[nodiscard]] GC::Ptr<ComputedProperties> compute_pseudo_element_style_if_needed(DOM::Element&, Optional<CSS::PseudoElement>, Optional<bool&> did_change_custom_properties) const;
150150

151151
[[nodiscard]] RuleCache const& get_pseudo_class_rule_cache(PseudoClass) const;
152152

@@ -217,7 +217,7 @@ class StyleComputer {
217217
[[nodiscard]] MatchingRuleSet build_matching_rule_set(DOM::Element const&, Optional<PseudoElement>, PseudoClassBitmap& attempted_pseudo_class_matches, bool& did_match_any_pseudo_element_rules, ComputeStyleMode) const;
218218

219219
LogicalAliasMappingContext compute_logical_alias_mapping_context(DOM::Element&, Optional<CSS::PseudoElement>, ComputeStyleMode, MatchingRuleSet const&) const;
220-
[[nodiscard]] GC::Ptr<ComputedProperties> compute_style_impl(DOM::Element&, Optional<CSS::PseudoElement>, ComputeStyleMode) const;
220+
[[nodiscard]] GC::Ptr<ComputedProperties> compute_style_impl(DOM::Element&, Optional<CSS::PseudoElement>, ComputeStyleMode, Optional<bool&> did_change_custom_properties) const;
221221
[[nodiscard]] GC::Ref<CascadedProperties> compute_cascaded_values(DOM::Element&, Optional<CSS::PseudoElement>, bool did_match_any_pseudo_element_rules, ComputeStyleMode, MatchingRuleSet const&, Optional<LogicalAliasMappingContext>, ReadonlySpan<PropertyID> properties_to_cascade) const;
222222
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_ascending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive);
223223
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_descending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive);

Libraries/LibWeb/CSS/StyleProperty.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,11 @@ namespace Web::CSS {
1111

1212
StyleProperty::~StyleProperty() = default;
1313

14+
bool StyleProperty::operator==(StyleProperty const& other) const
15+
{
16+
if (important != other.important || property_id != other.property_id || custom_name != other.custom_name)
17+
return false;
18+
return value->equals(*other.value);
19+
}
20+
1421
}

Libraries/LibWeb/CSS/StyleProperty.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ struct StyleProperty {
2323
CSS::PropertyID property_id;
2424
NonnullRefPtr<CSSStyleValue const> value;
2525
FlyString custom_name {};
26+
27+
bool operator==(StyleProperty const& other) const;
2628
};
2729

2830
}

Libraries/LibWeb/CSS/StyleValues/UnresolvedStyleValue.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class UnresolvedStyleValue final : public CSSStyleValue {
2525

2626
Vector<Parser::ComponentValue> const& values() const { return m_values; }
2727
bool contains_arbitrary_substitution_function() const { return m_substitution_functions_presence.has_any(); }
28+
bool includes_attr_function() const { return m_substitution_functions_presence.attr; }
29+
bool includes_var_function() const { return m_substitution_functions_presence.var; }
2830

2931
virtual bool equals(CSSStyleValue const& other) const override;
3032

Libraries/LibWeb/DOM/Document.cpp

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1427,7 +1427,7 @@ void Document::update_layout(UpdateLayoutReason reason)
14271427
}
14281428
}
14291429

1430-
[[nodiscard]] static CSS::RequiredInvalidationAfterStyleChange update_style_recursively(Node& node, CSS::StyleComputer& style_computer, bool needs_inherited_style_update)
1430+
[[nodiscard]] static CSS::RequiredInvalidationAfterStyleChange update_style_recursively(Node& node, CSS::StyleComputer& style_computer, bool needs_inherited_style_update, bool recompute_elements_depending_on_custom_properties)
14311431
{
14321432
bool const needs_full_style_update = node.document().needs_full_style_update();
14331433
CSS::RequiredInvalidationAfterStyleChange invalidation;
@@ -1440,12 +1440,14 @@ void Document::update_layout(UpdateLayoutReason reason)
14401440
// We will still recompute style for the children, though.
14411441
bool is_display_none = false;
14421442

1443+
bool did_change_custom_properties = false;
14431444
CSS::RequiredInvalidationAfterStyleChange node_invalidation;
14441445
if (is<Element>(node)) {
1445-
if (needs_full_style_update || node.needs_style_update()) {
1446-
node_invalidation = static_cast<Element&>(node).recompute_style();
1446+
auto& element = static_cast<Element&>(node);
1447+
if (needs_full_style_update || node.needs_style_update() || (recompute_elements_depending_on_custom_properties && element.style_uses_var_css_function())) {
1448+
node_invalidation = element.recompute_style(did_change_custom_properties);
14471449
} else if (needs_inherited_style_update) {
1448-
node_invalidation = static_cast<Element&>(node).recompute_inherited_style();
1450+
node_invalidation = element.recompute_inherited_style();
14491451
}
14501452
is_display_none = static_cast<Element&>(node).computed_properties()->display().is_none();
14511453
}
@@ -1464,21 +1466,25 @@ void Document::update_layout(UpdateLayoutReason reason)
14641466
node.set_needs_style_update(false);
14651467
invalidation |= node_invalidation;
14661468

1469+
if (did_change_custom_properties) {
1470+
recompute_elements_depending_on_custom_properties = true;
1471+
}
1472+
14671473
bool children_need_inherited_style_update = !invalidation.is_none();
1468-
if (needs_full_style_update || node.child_needs_style_update() || children_need_inherited_style_update) {
1474+
if (needs_full_style_update || node.child_needs_style_update() || children_need_inherited_style_update || recompute_elements_depending_on_custom_properties) {
14691475
if (node.is_element()) {
14701476
if (auto shadow_root = static_cast<DOM::Element&>(node).shadow_root()) {
14711477
if (needs_full_style_update || shadow_root->needs_style_update() || shadow_root->child_needs_style_update()) {
1472-
auto subtree_invalidation = update_style_recursively(*shadow_root, style_computer, children_need_inherited_style_update);
1478+
auto subtree_invalidation = update_style_recursively(*shadow_root, style_computer, children_need_inherited_style_update, recompute_elements_depending_on_custom_properties);
14731479
if (!is_display_none)
14741480
invalidation |= subtree_invalidation;
14751481
}
14761482
}
14771483
}
14781484

14791485
node.for_each_child([&](auto& child) {
1480-
if (needs_full_style_update || child.needs_style_update() || children_need_inherited_style_update || child.child_needs_style_update()) {
1481-
auto subtree_invalidation = update_style_recursively(child, style_computer, children_need_inherited_style_update);
1486+
if (needs_full_style_update || child.needs_style_update() || children_need_inherited_style_update || child.child_needs_style_update() || recompute_elements_depending_on_custom_properties) {
1487+
auto subtree_invalidation = update_style_recursively(child, style_computer, children_need_inherited_style_update, recompute_elements_depending_on_custom_properties);
14821488
if (!is_display_none)
14831489
invalidation |= subtree_invalidation;
14841490
}
@@ -1523,7 +1529,7 @@ void Document::update_style()
15231529

15241530
style_computer().reset_ancestor_filter();
15251531

1526-
auto invalidation = update_style_recursively(*this, style_computer(), false);
1532+
auto invalidation = update_style_recursively(*this, style_computer(), false, false);
15271533
if (!invalidation.is_none())
15281534
invalidate_display_list();
15291535
if (invalidation.rebuild_stacking_context_tree)

Libraries/LibWeb/DOM/Element.cpp

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -683,10 +683,12 @@ static CSS::RequiredInvalidationAfterStyleChange compute_required_invalidation(C
683683
return invalidation;
684684
}
685685

686-
CSS::RequiredInvalidationAfterStyleChange Element::recompute_style()
686+
CSS::RequiredInvalidationAfterStyleChange Element::recompute_style(bool& did_change_custom_properties)
687687
{
688688
VERIFY(parent());
689689

690+
m_style_uses_attr_css_function = false;
691+
m_style_uses_var_css_function = false;
690692
m_affected_by_has_pseudo_class_in_subject_position = false;
691693
m_affected_by_has_pseudo_class_in_non_subject_position = false;
692694
m_affected_by_has_pseudo_class_with_relative_selector_that_has_sibling_combinator = false;
@@ -697,7 +699,7 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_style()
697699
m_sibling_invalidation_distance = 0;
698700

699701
auto& style_computer = document().style_computer();
700-
auto new_computed_properties = style_computer.compute_style(*this);
702+
auto new_computed_properties = style_computer.compute_style(*this, {}, did_change_custom_properties);
701703

702704
// Tables must not inherit -libweb-* values for text-align.
703705
// FIXME: Find the spec for this.
@@ -737,7 +739,7 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_style()
737739
style_computer.push_ancestor(*this);
738740

739741
auto pseudo_element_style = computed_properties(pseudo_element);
740-
auto new_pseudo_element_style = style_computer.compute_pseudo_element_style_if_needed(*this, pseudo_element);
742+
auto new_pseudo_element_style = style_computer.compute_pseudo_element_style_if_needed(*this, pseudo_element, did_change_custom_properties);
741743

742744
// TODO: Can we be smarter about invalidation?
743745
if (pseudo_element_style && new_pseudo_element_style) {
@@ -2472,24 +2474,13 @@ void Element::invalidate_style_after_attribute_change(FlyString const& attribute
24722474
{
24732475
Vector<CSS::InvalidationSet::Property, 1> changed_properties;
24742476
StyleInvalidationOptions style_invalidation_options;
2475-
if (is_presentational_hint(attribute_name)) {
2477+
if (is_presentational_hint(attribute_name) || style_uses_attr_css_function()) {
24762478
style_invalidation_options.invalidate_self = true;
24772479
}
24782480

2479-
if (style_uses_css_custom_properties()) {
2480-
// A css custom property can be hooked on to this element by any attribute
2481-
// so invalidate elements and rerender them in that scenario
2482-
style_invalidation_options.invalidate_elements_that_use_css_custom_properties = true;
2483-
}
2484-
24852481
if (attribute_name == HTML::AttributeNames::style) {
24862482
style_invalidation_options.invalidate_self = true;
2487-
// even if we don't have custom properties, the new "style" attribute could add one
2488-
style_invalidation_options.invalidate_elements_that_use_css_custom_properties = true;
24892483
} else if (attribute_name == HTML::AttributeNames::class_) {
2490-
// adding or removing classes can add new custom properties to this element
2491-
style_invalidation_options.invalidate_elements_that_use_css_custom_properties = true;
2492-
24932484
Vector<StringView> old_classes;
24942485
Vector<StringView> new_classes;
24952486
if (old_value.has_value())

Libraries/LibWeb/DOM/Element.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ class Element
201201

202202
void run_attribute_change_steps(FlyString const& local_name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_);
203203

204-
CSS::RequiredInvalidationAfterStyleChange recompute_style();
204+
CSS::RequiredInvalidationAfterStyleChange recompute_style(bool& did_change_custom_properties);
205205
CSS::RequiredInvalidationAfterStyleChange recompute_inherited_style();
206206

207207
Optional<CSS::PseudoElement> use_pseudo_element() const { return m_use_pseudo_element; }
@@ -256,8 +256,10 @@ class Element
256256
void set_custom_properties(Optional<CSS::PseudoElement>, HashMap<FlyString, CSS::StyleProperty> custom_properties);
257257
[[nodiscard]] HashMap<FlyString, CSS::StyleProperty> const& custom_properties(Optional<CSS::PseudoElement>) const;
258258

259-
bool style_uses_css_custom_properties() const { return m_style_uses_css_custom_properties; }
260-
void set_style_uses_css_custom_properties(bool value) { m_style_uses_css_custom_properties = value; }
259+
bool style_uses_attr_css_function() const { return m_style_uses_attr_css_function; }
260+
void set_style_uses_attr_css_function() { m_style_uses_attr_css_function = true; }
261+
bool style_uses_var_css_function() const { return m_style_uses_var_css_function; }
262+
void set_style_uses_var_css_function() { m_style_uses_var_css_function = true; }
261263

262264
// NOTE: The function is wrapped in a GC::HeapFunction immediately.
263265
HTML::TaskID queue_an_element_task(HTML::Task::Source, Function<void()>);
@@ -605,7 +607,8 @@ class Element
605607

606608
bool m_in_top_layer : 1 { false };
607609
bool m_rendered_in_top_layer : 1 { false };
608-
bool m_style_uses_css_custom_properties : 1 { false };
610+
bool m_style_uses_attr_css_function : 1 { false };
611+
bool m_style_uses_var_css_function : 1 { false };
609612
bool m_affected_by_has_pseudo_class_in_subject_position : 1 { false };
610613
bool m_affected_by_has_pseudo_class_in_non_subject_position : 1 { false };
611614
bool m_affected_by_direct_sibling_combinator : 1 { false };

Libraries/LibWeb/DOM/Node.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,11 +515,11 @@ void Node::invalidate_style(StyleInvalidationReason reason, Vector<CSS::Invalida
515515
set_needs_style_update(true);
516516
}
517517

518-
if (!invalidation_set.has_properties() && !options.invalidate_elements_that_use_css_custom_properties) {
518+
if (!invalidation_set.has_properties()) {
519519
return;
520520
}
521521

522-
document().style_invalidator().add_pending_invalidation(*this, move(invalidation_set), options.invalidate_elements_that_use_css_custom_properties);
522+
document().style_invalidator().add_pending_invalidation(*this, move(invalidation_set));
523523
}
524524

525525
Utf16String Node::child_text_content() const

0 commit comments

Comments
 (0)