Files
ladybird/Libraries/LibWeb/CSS/InvalidationSet.cpp
Andreas Kling 97e2b05004 LibWeb: Merge equivalent style invalidation plans
Compare invalidation sets, rules, and plans structurally so repeated
descendant and sibling invalidation entries can be merged even when
they were built as separate payload objects.

Also deduplicate pending and active descendant invalidations in the
style invalidator so equivalent rules are not re-applied as the DOM
walk descends. This reduces :has() invalidation fanout while keeping
behavior the same.
2026-04-20 13:20:41 +02:00

123 lines
4.1 KiB
C++

/*
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/CSS/InvalidationSet.h>
namespace Web::CSS {
bool InvalidationSet::operator==(InvalidationSet const& other) const
{
if (m_needs_invalidate_self != other.m_needs_invalidate_self)
return false;
if (m_needs_invalidate_whole_subtree != other.m_needs_invalidate_whole_subtree)
return false;
if (m_properties.size() != other.m_properties.size())
return false;
for (auto const& property : m_properties) {
if (!other.m_properties.contains(property))
return false;
}
return true;
}
void InvalidationSet::include_all_from(InvalidationSet const& other)
{
m_needs_invalidate_self |= other.m_needs_invalidate_self;
m_needs_invalidate_whole_subtree |= other.m_needs_invalidate_whole_subtree;
for (auto const& property : other.m_properties)
m_properties.set(property);
}
bool InvalidationSet::is_empty() const
{
return !m_needs_invalidate_self && !m_needs_invalidate_whole_subtree && m_properties.is_empty();
}
void InvalidationSet::for_each_property(Function<IterationDecision(Property const&)> const& callback) const
{
if (m_needs_invalidate_self) {
if (callback({ Property::Type::InvalidateSelf }) == IterationDecision::Break)
return;
}
if (m_needs_invalidate_whole_subtree) {
if (callback({ Property::Type::InvalidateWholeSubtree }) == IterationDecision::Break)
return;
}
for (auto const& property : m_properties) {
if (callback(property) == IterationDecision::Break)
return;
}
}
}
namespace AK {
unsigned Traits<Web::CSS::InvalidationSet::Property>::hash(Web::CSS::InvalidationSet::Property const& invalidation_set_property)
{
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<void> Formatter<Web::CSS::InvalidationSet::Property>::format(FormatBuilder& builder, Web::CSS::InvalidationSet::Property const& invalidation_set_property)
{
switch (invalidation_set_property.type) {
case Web::CSS::InvalidationSet::Property::Type::InvalidateSelf: {
TRY(builder.put_string("$"sv));
return {};
}
case Web::CSS::InvalidationSet::Property::Type::Class: {
TRY(builder.put_string("."sv));
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()));
return {};
}
case Web::CSS::InvalidationSet::Property::Type::TagName: {
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("]"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<Web::CSS::PseudoClass>())));
return {};
}
case Web::CSS::InvalidationSet::Property::Type::InvalidateWholeSubtree: {
TRY(builder.put_string("*"sv));
return {};
}
default:
VERIFY_NOT_REACHED();
}
}
ErrorOr<void> Formatter<Web::CSS::InvalidationSet>::format(FormatBuilder& builder, Web::CSS::InvalidationSet const& invalidation_set)
{
bool first = true;
invalidation_set.for_each_property([&](auto const& property) {
if (!first)
builder.builder().append(", "sv);
builder.builder().appendff("{}", property);
return IterationDecision::Continue;
});
return {};
}
}