Files
ladybird/Libraries/LibWeb/CSS/StyleInvalidationData.h
Andreas Kling 9fae2bcff9 LibWeb: Avoid unrelated structural :has invalidation
Track the simple selector features that appear inside :has() arguments
on each StyleScope, then consult that metadata before scheduling an
ancestor walk for a structural mutation. If the mutated subtree has no
tag, id, class, attribute, or pseudo-class feature that any cached
:has() argument cares about, skip the walk entirely.

Stay conservative for featureless-sensitive arguments such as :has(*),
:has(:not(...)), :has(:empty), and child-index pseudos: an unfeatured
node can still start or stop matching there. Track that case via a new
has_selectors_sensitive_to_featureless_subtree_changes flag on
StyleInvalidationData and fall back to the old conservative walk.

Stay conservative for pseudo-classes the subtree filter cannot probe
(:focus, :hover, validation pseudos). Move :default out of the set of
trackable feature pseudo-classes for the same reason; it now triggers
the conservative walk where it previously recorded metadata.

Tag and attribute names are stored lowercased, so for non-HTML elements
(SVG, MathML) treat lowercased matches as scheduling hints only; the
actual :has() match still goes through case-sensitive selector matching.

Test counter expectations are rebaselined to reflect the skipped walks
and reduced recomputations. Matching behavior is unchanged.
2026-04-28 15:34:49 +02:00

98 lines
2.8 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 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;
};
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);
};
}