ladybird/Libraries/LibWeb/CSS/StyleInvalidationData.h
Andreas Kling a0459a3ff3 LibWeb: Avoid broad invalidation for nth-child filters
Property invalidation inside :nth-child(... of ...) used to become a
whole-subtree invalidation plan. That is broader than needed for
property changes that only affect the filtered sibling list, such as
:has() becoming true or false for one sibling.

Add an invalidation plan bit that marks the element and structurally
affected siblings instead. Keep whole-subtree invalidation when a
stronger plan already requires it, and cover both :nth-child and
:nth-last-child filters with :has() regression tests.
2026-06-12 15:13:29 +02:00

115 lines
3.4 KiB
C++

/*
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/HashMap.h>
#include <AK/NonnullRefPtr.h>
#include <AK/RefCounted.h>
#include <AK/Vector.h>
#include <LibWeb/CSS/InvalidationSet.h>
#include <LibWeb/CSS/Selector.h>
#include <LibWeb/Forward.h>
namespace Web::CSS {
enum class ExcludePropertiesNestedInNotPseudoClass : bool {
No,
Yes,
};
enum class InsideNthChildPseudoClass {
No,
Yes,
};
enum class HasArgumentScope : u8 {
ChildrenOnly,
AllDescendants,
NextSiblingOnly,
AllFollowingSiblings,
Complex,
};
struct InvalidationPlan;
struct InvalidationGuard {
bool is_empty() const { return property_sets.is_empty(); }
bool operator==(InvalidationGuard const&) const;
// Every set is an OR group; the guard matches when every group matches.
Vector<InvalidationSet> property_sets;
};
struct GuardedInvalidationRule {
InvalidationGuard guard;
NonnullRefPtr<InvalidationPlan> payload;
bool operator==(GuardedInvalidationRule const&) const;
};
struct DescendantInvalidationRule {
InvalidationSet match_set;
bool match_any { false };
NonnullRefPtr<InvalidationPlan> payload;
bool operator==(DescendantInvalidationRule const&) const;
};
enum class SiblingInvalidationReach {
Adjacent,
Subsequent,
};
struct SiblingInvalidationRule {
SiblingInvalidationReach reach;
InvalidationSet match_set;
bool match_any { false };
NonnullRefPtr<InvalidationPlan> payload;
bool operator==(SiblingInvalidationRule const&) const;
};
struct InvalidationPlan final : RefCounted<InvalidationPlan> {
static NonnullRefPtr<InvalidationPlan> create() { return adopt_ref(*new InvalidationPlan); }
bool is_empty() const;
void include_all_from(InvalidationPlan const&);
bool operator==(InvalidationPlan const&) const;
bool invalidate_self { false };
bool invalidate_whole_subtree { false };
bool invalidate_self_and_structurally_affected_siblings { false };
Vector<DescendantInvalidationRule> descendant_rules;
Vector<SiblingInvalidationRule> sibling_rules;
Vector<GuardedInvalidationRule> guarded_rules;
};
struct HasInvalidationMetadata {
Selector const* relative_selector { nullptr };
HasArgumentScope scope { HasArgumentScope::Complex };
bool operator==(HasInvalidationMetadata const&) const = default;
};
struct StyleInvalidationData;
void build_invalidation_sets_for_simple_selector(Selector::SimpleSelector const&, InvalidationSet&, ExcludePropertiesNestedInNotPseudoClass, StyleInvalidationData&, InsideNthChildPseudoClass);
struct StyleInvalidationData {
HashMap<InvalidationSet::Property, NonnullRefPtr<InvalidationPlan>> invalidation_plans;
HashMap<FlyString, Vector<HasInvalidationMetadata>> ids_used_in_has_selectors;
HashMap<FlyString, Vector<HasInvalidationMetadata>> class_names_used_in_has_selectors;
HashMap<FlyString, Vector<HasInvalidationMetadata>> attribute_names_used_in_has_selectors;
HashMap<FlyString, Vector<HasInvalidationMetadata>> tag_names_used_in_has_selectors;
HashMap<PseudoClass, Vector<HasInvalidationMetadata>> pseudo_classes_used_in_has_selectors;
bool has_selectors_sensitive_to_featureless_subtree_changes { false };
void build_invalidation_sets_for_selector(Selector const& selector);
void build_invalidation_sets_for_scope_boundary_selector(Selector const& selector);
};
}