From 34bf833a0a458c999b3eedb4344dcae4c7eb648b Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Sun, 19 Jan 2025 17:22:44 +0100 Subject: [PATCH] LibWeb: Expand invalidation sets usage to any attribute change Before this change invalidation sets were only used for "class" and "id" attribute changes. --- Libraries/LibWeb/CSS/InvalidationSet.cpp | 19 +++-- Libraries/LibWeb/CSS/InvalidationSet.h | 7 +- Libraries/LibWeb/CSS/StyleComputer.cpp | 12 ++- .../LibWeb/CSS/StyleInvalidationData.cpp | 20 +++++ Libraries/LibWeb/CSS/StyleInvalidationData.h | 1 + Libraries/LibWeb/DOM/Element.cpp | 75 +++++++++++-------- 6 files changed, 91 insertions(+), 43 deletions(-) diff --git a/Libraries/LibWeb/CSS/InvalidationSet.cpp b/Libraries/LibWeb/CSS/InvalidationSet.cpp index 8314feedee5..117001d39e6 100644 --- a/Libraries/LibWeb/CSS/InvalidationSet.cpp +++ b/Libraries/LibWeb/CSS/InvalidationSet.cpp @@ -37,7 +37,11 @@ namespace AK { unsigned Traits::hash(Web::CSS::InvalidationSet::Property const& invalidation_set_property) { - return pair_int_hash(to_underlying(invalidation_set_property.type), invalidation_set_property.name.hash()); + auto value_hash = invalidation_set_property.value.visit( + [](FlyString const& value) -> int { return value.hash(); }, + [](Web::CSS::PseudoClass const& value) -> int { return to_underlying(value); }, + [](Empty) -> int { return 0; }); + return pair_int_hash(to_underlying(invalidation_set_property.type), value_hash); } ErrorOr Formatter::format(FormatBuilder& builder, Web::CSS::InvalidationSet::Property const& invalidation_set_property) @@ -49,24 +53,29 @@ ErrorOr Formatter::format(FormatBuild } case Web::CSS::InvalidationSet::Property::Type::Class: { TRY(builder.put_string("."sv)); - TRY(builder.put_string(invalidation_set_property.name)); + TRY(builder.put_string(invalidation_set_property.name())); return {}; } case Web::CSS::InvalidationSet::Property::Type::Id: { TRY(builder.put_string("#"sv)); - TRY(builder.put_string(invalidation_set_property.name)); + TRY(builder.put_string(invalidation_set_property.name())); return {}; } case Web::CSS::InvalidationSet::Property::Type::TagName: { - TRY(builder.put_string(invalidation_set_property.name)); + TRY(builder.put_string(invalidation_set_property.name())); return {}; } case Web::CSS::InvalidationSet::Property::Type::Attribute: { TRY(builder.put_string("["sv)); - TRY(builder.put_string(invalidation_set_property.name)); + TRY(builder.put_string(invalidation_set_property.name())); TRY(builder.put_string("]"sv)); return {}; } + case Web::CSS::InvalidationSet::Property::Type::PseudoClass: { + TRY(builder.put_string(":"sv)); + TRY(builder.put_string(pseudo_class_name(invalidation_set_property.value.get()))); + return {}; + } case Web::CSS::InvalidationSet::Property::Type::InvalidateWholeSubtree: { TRY(builder.put_string("*"sv)); return {}; diff --git a/Libraries/LibWeb/CSS/InvalidationSet.h b/Libraries/LibWeb/CSS/InvalidationSet.h index 3799b713b01..3db0ab943ec 100644 --- a/Libraries/LibWeb/CSS/InvalidationSet.h +++ b/Libraries/LibWeb/CSS/InvalidationSet.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace Web::CSS { @@ -24,10 +25,13 @@ public: Id, TagName, Attribute, + PseudoClass, }; Type type; - FlyString name {}; + Variant value { Empty {} }; + + FlyString const& name() const { return value.get(); } bool operator==(Property const& other) const = default; }; @@ -44,6 +48,7 @@ public: void set_needs_invalidate_id(FlyString const& name) { m_properties.set({ Property::Type::Id, name }); } void set_needs_invalidate_tag_name(FlyString const& name) { m_properties.set({ Property::Type::TagName, name }); } void set_needs_invalidate_attribute(FlyString const& name) { m_properties.set({ Property::Type::Attribute, name }); } + void set_needs_invalidate_pseudo_class(PseudoClass pseudo_class) { m_properties.set({ Property::Type::PseudoClass, pseudo_class }); } bool is_empty() const; void for_each_property(Function const& callback) const; diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index 1bd57a09a97..077faa79779 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -447,19 +447,23 @@ bool StyleComputer::invalidation_property_used_in_has_selector(InvalidationSet:: return true; switch (property.type) { case InvalidationSet::Property::Type::Id: - if (m_style_invalidation_data->ids_used_in_has_selectors.contains(property.name)) + if (m_style_invalidation_data->ids_used_in_has_selectors.contains(property.name())) return true; break; case InvalidationSet::Property::Type::Class: - if (m_style_invalidation_data->class_names_used_in_has_selectors.contains(property.name)) + if (m_style_invalidation_data->class_names_used_in_has_selectors.contains(property.name())) return true; break; case InvalidationSet::Property::Type::Attribute: - if (m_style_invalidation_data->attribute_names_used_in_has_selectors.contains(property.name)) + if (m_style_invalidation_data->attribute_names_used_in_has_selectors.contains(property.name())) return true; break; case InvalidationSet::Property::Type::TagName: - if (m_style_invalidation_data->tag_names_used_in_has_selectors.contains(property.name)) + if (m_style_invalidation_data->tag_names_used_in_has_selectors.contains(property.name())) + return true; + break; + case InvalidationSet::Property::Type::PseudoClass: + if (m_style_invalidation_data->pseudo_classes_used_in_has_selectors.contains(property.value.get())) return true; break; default: diff --git a/Libraries/LibWeb/CSS/StyleInvalidationData.cpp b/Libraries/LibWeb/CSS/StyleInvalidationData.cpp index 14b9ec26d1d..ec3e3f0911a 100644 --- a/Libraries/LibWeb/CSS/StyleInvalidationData.cpp +++ b/Libraries/LibWeb/CSS/StyleInvalidationData.cpp @@ -66,6 +66,16 @@ static void collect_properties_used_in_has(Selector::SimpleSelector const& selec } case Selector::SimpleSelector::Type::PseudoClass: { auto const& pseudo_class = selector.pseudo_class(); + switch (pseudo_class.type) { + case PseudoClass::Enabled: + case PseudoClass::Disabled: + case PseudoClass::PlaceholderShown: + case PseudoClass::Checked: + style_invalidation_data.pseudo_classes_used_in_has_selectors.set(pseudo_class.type); + break; + default: + break; + } for (auto const& child_selector : pseudo_class.argument_selector_list) { for (auto const& compound_selector : child_selector->compound_selectors()) { for (auto const& simple_selector : compound_selector.simple_selectors) { @@ -102,6 +112,16 @@ static void build_invalidation_sets_for_simple_selector(Selector::SimpleSelector break; case Selector::SimpleSelector::Type::PseudoClass: { auto const& pseudo_class = selector.pseudo_class(); + switch (pseudo_class.type) { + case PseudoClass::Enabled: + case PseudoClass::Disabled: + case PseudoClass::PlaceholderShown: + case PseudoClass::Checked: + invalidation_set.set_needs_invalidate_pseudo_class(pseudo_class.type); + break; + default: + break; + } if (pseudo_class.type == PseudoClass::Has) break; if (exclude_properties_nested_in_not_pseudo_class == ExcludePropertiesNestedInNotPseudoClass::Yes && pseudo_class.type == PseudoClass::Not) diff --git a/Libraries/LibWeb/CSS/StyleInvalidationData.h b/Libraries/LibWeb/CSS/StyleInvalidationData.h index fb33bada9b9..848711230cb 100644 --- a/Libraries/LibWeb/CSS/StyleInvalidationData.h +++ b/Libraries/LibWeb/CSS/StyleInvalidationData.h @@ -18,6 +18,7 @@ struct StyleInvalidationData { HashTable class_names_used_in_has_selectors; HashTable attribute_names_used_in_has_selectors; HashTable tag_names_used_in_has_selectors; + HashTable pseudo_classes_used_in_has_selectors; InvalidationSet build_invalidation_sets_for_selector(Selector const& selector); }; diff --git a/Libraries/LibWeb/DOM/Element.cpp b/Libraries/LibWeb/DOM/Element.cpp index 5adcef7e31f..08791c1ad08 100644 --- a/Libraries/LibWeb/DOM/Element.cpp +++ b/Libraries/LibWeb/DOM/Element.cpp @@ -1146,15 +1146,32 @@ bool Element::affected_by_invalidation_property(CSS::InvalidationSet::Property c { switch (property.type) { case CSS::InvalidationSet::Property::Type::Class: - return m_classes.contains_slow(property.name); + return m_classes.contains_slow(property.name()); case CSS::InvalidationSet::Property::Type::Id: - return m_id == property.name; + return m_id == property.name(); case CSS::InvalidationSet::Property::Type::TagName: - return local_name() == property.name; + return local_name() == property.name(); case CSS::InvalidationSet::Property::Type::Attribute: { - if (property.name == HTML::AttributeNames::id || property.name == HTML::AttributeNames::class_) + if (property.name() == HTML::AttributeNames::id || property.name() == HTML::AttributeNames::class_) return true; - return has_attribute(property.name); + return has_attribute(property.name()); + } + case CSS::InvalidationSet::Property::Type::PseudoClass: { + switch (property.value.get()) { + case CSS::PseudoClass::Enabled: { + return (is(*this) || is(*this) || is(*this) || is(*this) || is(*this) || is(*this) || is(*this)) + && !is_actually_disabled(); + } + case CSS::PseudoClass::Disabled: { + return is_actually_disabled(); + } + case CSS::PseudoClass::Checked: { + // FIXME: This could be narrowed down to return true only if element is actually checked. + return is(*this) || is(*this); + } + default: + VERIFY_NOT_REACHED(); + } } case CSS::InvalidationSet::Property::Type::InvalidateSelf: return false; @@ -1951,26 +1968,6 @@ ErrorOr Element::scroll_into_view(Optional const& old_value, Optional const& new_value) { // FIXME: Only invalidate if the attribute can actually affect style. @@ -2003,15 +2000,15 @@ void Element::invalidate_style_after_attribute_change(FlyString const& attribute Vector changed_properties; for (auto& old_class : old_classes) { if (!new_classes.contains_slow(old_class)) { - changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Class, .name = FlyString::from_utf8_without_validation(old_class.bytes()) }); + changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Class, .value = FlyString::from_utf8_without_validation(old_class.bytes()) }); } } for (auto& new_class : new_classes) { if (!old_classes.contains_slow(new_class)) { - changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Class, .name = FlyString::from_utf8_without_validation(new_class.bytes()) }); + changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Class, .value = FlyString::from_utf8_without_validation(new_class.bytes()) }); } } - changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Attribute, .name = HTML::AttributeNames::class_ }); + changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Attribute, .value = HTML::AttributeNames::class_ }); invalidate_style(StyleInvalidationReason::ElementAttributeChange, changed_properties); return; } @@ -2019,19 +2016,31 @@ void Element::invalidate_style_after_attribute_change(FlyString const& attribute if (attribute_name == HTML::AttributeNames::id) { Vector changed_properties; if (old_value.has_value()) - changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Id, .name = old_value.value() }); + changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Id, .value = FlyString(old_value.value()) }); if (new_value.has_value()) - changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Id, .name = new_value.value() }); - changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Attribute, .name = HTML::AttributeNames::id }); + changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Id, .value = FlyString(new_value.value()) }); + changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Attribute, .value = HTML::AttributeNames::id }); invalidate_style(StyleInvalidationReason::ElementAttributeChange, changed_properties); return; } - if (is_presentational_hint(attribute_name) - || attribute_name_may_affect_selectors(*this, attribute_name)) { + Vector changed_properties; + if (attribute_name == HTML::AttributeNames::disabled) { + changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Disabled }); + changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Enabled }); + } else if (attribute_name == HTML::AttributeNames::placeholder) { + changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::PlaceholderShown }); + } else if (attribute_name == HTML::AttributeNames::value) { + changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = CSS::PseudoClass::Checked }); + } + + if (is_presentational_hint(attribute_name)) { invalidate_style(StyleInvalidationReason::ElementAttributeChange); return; } + + changed_properties.append({ .type = CSS::InvalidationSet::Property::Type::Attribute, .value = attribute_name }); + invalidate_style(StyleInvalidationReason::ElementAttributeChange, changed_properties); } bool Element::is_hidden() const