ladybird/Libraries/LibWeb/CSS/SelectorEngine.h
Andreas Kling 86e0e281a0 LibWeb: Avoid unnecessary pseudo-element style recomputation
Track the synthetic pseudo-elements that matched while computing an
originating element's normal style. Store the transient match set as a
bitfield, then copy those bits into ComputedProperties. Use them during
style invalidation to skip pseudo style recomputation when neither the
old nor new originating style matched pseudo rules and no pseudo style
already exists.

This shaves roughly 500 ms off loading the Ladybird GitHub repository.

Materialize synthetic pseudo styles on demand for CSSOM reads so
getComputedStyle(element, "::before") still computes skipped styles when
script asks for them. Add coverage for a universal pseudo selector, and
update style invalidation counter expectations for the reduced work.
2026-06-18 19:50:34 +02:00

63 lines
2.2 KiB
C++

/*
* Copyright (c) 2018-2024, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/HashMap.h>
#include <LibWeb/CSS/Selector.h>
#include <LibWeb/DOM/Element.h>
namespace Web::SelectorEngine {
enum class SelectorKind {
Normal,
Relative,
};
enum class HasMatchResult : u8 {
Matched,
NotMatched,
};
struct HasResultCacheKey {
CSS::Selector const* selector;
GC::Ptr<DOM::Element const> element;
void visit_edges(GC::Cell::Visitor& visitor)
{
visitor.visit(element);
}
bool operator==(HasResultCacheKey const&) const = default;
};
struct HasResultCacheKeyTraits : Traits<HasResultCacheKey> {
static unsigned hash(HasResultCacheKey const& key)
{
return pair_int_hash(ptr_hash(key.selector), ptr_hash(key.element.ptr()));
}
};
using HasResultCache = HashMap<HasResultCacheKey, HasMatchResult, HasResultCacheKeyTraits>;
struct MatchContext {
GC::Ptr<CSS::CSSStyleSheet const> style_sheet_for_rule {};
GC::Ptr<DOM::Element const> subject {};
GC::Ptr<DOM::ShadowRoot const> rule_shadow_root {}; // Shadow root the matched rule belongs to
bool collect_per_element_selector_involvement_metadata { false };
// True while we are evaluating the argument of a :has() pseudo-class.
// Elements visited by selector walks (descendants, siblings, etc.) while
// this is set get marked as in_has_scope so the invalidation walker can
// later terminate once it leaves the scope. Transparent to callers; set
// by matches_has_pseudo_class with a ScopeGuard.
bool inside_has_argument_match { false };
HasResultCache* has_result_cache { nullptr };
};
bool matches(CSS::Selector const&, DOM::AbstractElement const&, GC::Ptr<DOM::Element const> shadow_host, MatchContext& context, GC::Ptr<DOM::ParentNode const> scope = {}, SelectorKind selector_kind = SelectorKind::Normal, GC::Ptr<DOM::Element const> anchor = nullptr);
bool matches_originating_element_for_pseudo_element(CSS::Selector const&, CSS::PseudoElement, DOM::AbstractElement const&, GC::Ptr<DOM::Element const> shadow_host, MatchContext&, GC::Ptr<DOM::ParentNode const> scope = {});
}