ladybird/Libraries/LibWeb/CSS/StyleInvalidationData.h
Andreas Kling b01ce45c14 LibWeb: Guard non-subject pseudo-class invalidations
Compound selectors with a non-rightmost pseudo-class used to register
their descendant and sibling invalidation plans directly under the
pseudo-class property. A selector like `.item:hover * .target` ran
the descendant plan for every hovered element, even when that element
could not match `.item`.

Store those non-rightmost plans behind a guard made from stable class
and id subject features in the same compound selector. Deliberately
leave pseudo-classes out of guards, since one mutation can change
multiple state pseudo-classes at once. Also leave tag and attribute
selectors out, since their matching depends on document and namespace
case-sensitivity that the guard property does not carry.

The new style-invalidation test covers the GitHub-shaped
`:hover * :not(...)` case, co-invalidated `:link` and `:any-link`,
partial or complex `:is()`/`:where()` alternatives, and case-sensitive
SVG tag and attribute selectors.
2026-05-24 15:32:39 +02:00

113 lines
3.3 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 };
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);
};
}