Commit graph

9 commits

Author SHA1 Message Date
Andreas Kling
f45bf0e770 LibWeb: Bucket ancestor-filtered rules by ancestor hash
Add a late rule-cache bucket for selectors that do not expose a
current-element bucket but do expose ancestor-filter metadata. These
rules previously stayed in the global other bucket and relied on the
ancestor filter after broad enumeration.

Visit the new buckets only when the current ancestor bloom filter may
contain the bucket hash. Keep pseudo-class invalidation caches opted
out because those caches answer targeted invalidation questions rather
than normal style collection.

On the GitHub profile, other-bucket candidates fell from about 5.50k
to about 1.07k per 1k rule collection calls.
2026-06-19 09:36:53 +02:00
Andreas Kling
4c87227078 LibWeb: Track interaction pseudo-classes in :has() metadata
Record hover, focus, focus-visible, focus-within, and target pseudo
classes in :has() invalidation metadata. This lets the existing
property invalidation path schedule :has() ancestor invalidation only
for scopes whose :has() selectors mention the changed pseudo-class.

Interaction pseudo-class invalidation previously scheduled :has()
ancestor invalidation for every style scope containing any :has()
selector. That kept selectors like .wrapper:has(:focus) * correct, but
also caused unrelated hover and focus changes to fan out through broad
:has() descendant invalidation rules.

Update style invalidation coverage so unrelated hover changes avoid the
extra :has() walk, while a descendant :has(:focus) rule still restyles
its affected descendants when focus changes.
2026-06-12 15:13:29 +02:00
Andreas Kling
162412c81a LibWeb: Narrow pseudo-class self invalidation
Only mark an element itself dirty for interaction pseudo-class changes
when matching rules can plausibly match that element. Check every style
scope observing the element, and keep the normal invalidation plan for
descendants and siblings.

Treat pseudo-elements as neutral during the plausibility check so rules
like a:hover::before still invalidate the originating element. Add text
coverage for non-matching hover rules and pseudo-element hover changes.
2026-05-30 02:13:23 +02:00
Andreas Kling
fa579754bf LibWeb: Use invalidation plans for pseudo-class changes
Replace PseudoClassInvalidator's subtree scan with targeted
invalidation for elements whose pseudo-class state changes. This scopes
state changes to affected selectors instead of rechecking a whole
common-ancestor subtree.

Use the ancestor chain that matches each pseudo-class. Hover walks the
shadow-including chain. :focus-within walks the flat-tree chain, so
slotted content invalidates its assigned slot and relevant shadow-tree
descendants. Focus, FocusVisible, and Target invalidate just the state
node.

Route each affected element through Element::invalidate_style with the
pseudo-class property. This uses the same invalidation-plan machinery as
Disabled, Checked, and other pseudo-class state changes.

Interaction state pseudo classes are not tracked in :has() metadata, so
schedule :has() ancestor invalidation explicitly when the state flips.
The callers no longer need cross-scope branching. The chain walk handles
shadow boundaries, and property invalidation already visits every
observer style scope.
2026-05-23 23:37:36 +02:00
Andreas Kling
817cb7432f LibWeb: Keep shadow rule caches for user styles
Do not invalidate shadow-root rule caches when the document user style
sheet changes. Shadow trees still need style invalidation because
document user rules can match their descendants, but their local author
rule caches do not include the document user sheet.

Since user and content-blocker sheets now live only in the document
scope, shadow-DOM state changes also have to consult the document user
selector insights for :has() and pseudo-class invalidation.

Add content blocker coverage for user-style refreshes, shadow :has()
state changes, and shadow pseudo-class invalidation.
2026-05-23 22:03:46 +02:00
Andreas Kling
107e44e9c6 LibWeb: Invalidate focus state for shadow parts
Include ::part() rules when comparing pseudo-class rule matches during
focus state invalidation. For observer style scopes, also walk the
containing shadow roots of the old and new focused nodes so document and
ancestor shadow scopes can invalidate exported part targets.

Add coverage for direct and exported parts using :focus, :focus-visible,
and :focus-within.
2026-05-21 08:56:05 +02:00
Andreas Kling
c92ea86c30 LibWeb: Invalidate shadow focus-within styles
Match :focus-within by walking flat-tree parents so shadow hosts and
slotted content follow the Selectors definition instead of DOM ancestry.

When focus state changes, also run pseudo-class invalidation in shadow
style scopes that can observe the focused node. This lets shadow-root
rules such as :host(:focus-within)::before and ::slotted(:focus) update
without waiting for an unrelated DOM mutation to restyle the host.

Add focused coverage for host pseudo-elements, shadow descendants, and
slotted controls, and update the style-invalidation counter baseline for
the lower focus-family no-op restyle counts.
2026-05-21 08:56:05 +02:00
Sam Atkins
35b43f7d3a LibWeb/CSS: Insert a combinator before all pseudo-element selectors
Previously, and according to the spec, `a::part(foo)::before` would be a
single CompoundSelector, even though it matches against 3 different
targets. This meant some awkward swapping of targets in the middle of
matching, and in particular it made `::part()` and `::slotted()` quite
hacky, requiring them to track extra data on the MatchContext to then
use later. This was scattered around and difficult to follow.

Partly inspired by Gecko, this commit instead introduces an invisible
PseudoElement combinator. After parsing a selector, we find any
CompoundSelectors that contain a pseudo-element and split them up, so
that each CompoundSelector only has a single target in the end. Where
the pseudo-element was at the start of a CompoundSelector, we insert an
invisible universal selector before it to represent its originating
element.

So now, a CompoundSelector deals with one target, and switching targets
is done at the combinator.

The one inconsistency is that we match the target of ::slotted()
and ::part() in pseudo_element_transition_target(), instead of before
then when processing the SimpleSelector. This is to avoid repeating the
same computations twice.

No outward-facing behaviour changes, though the invalidation metrics
have changed.
2026-05-13 11:03:02 +01:00
Andreas Kling
61a18d91d6 LibWeb: Move pseudo-class state invalidation into a helper
Document.cpp contained the CSS rule-cache matching used to decide which
elements need style updates when hover, focus, or target state changes.
Move that logic into CSS::Invalidation::PseudoClassInvalidator.

Document still owns the current state slots and chooses when a state
transition happens. The helper now owns the selector matching and
recursive invalidation pass for those pseudo-class transitions.
2026-04-29 15:47:23 +02:00