Commit graph

15 commits

Author SHA1 Message Date
Aliaksandr Kalenik
f2cd23659c LibWeb: Fix :has() invalidation with nested :is() selectors
Replace the broad whole-subtree fallback for :has() invalidation with
a more targeted approach. The old code unconditionally overwrote
fine-grained :has() invalidation plans with invalidate_whole_subtree
for every non-rightmost compound containing :has(). This prevented
optimization for direct cases like `.a:has(.b) .c`.

The new approach propagates pseudo_class:Has through :is()/:where()
argument processing when :has() appears in non-rightmost compounds of
the inner selector. For complex :is() arguments (multiple compounds),
it falls back to whole-subtree invalidation since the outer plan can't
correctly capture the nested combinator structure.
2026-03-10 17:56:42 +01:00
Aliaksandr Kalenik
9df1372452 LibWeb: Implement sibling invalidation sets
Replace flat InvalidationSet with recursive InvalidationPlan trees
that preserve selector combinator structure. Previously, selectors
with sibling combinators (+ and ~) fell back to whole-subtree
invalidation. Now the StyleInvalidator walks the DOM following
combinator-specific rules, so ".a + .b" only invalidates the
adjacent sibling matching ".b" rather than the entire subtree.

Plans are compiled at stylesheet parse time by walking selector
compounds right-to-left. For ".a .b + .c":
```
  [.c]: plan = { invalidate_self }
        register: "c" → plan

  [.b]: wrap("+", righthand)
        plan = { sibling_rules: [match ".c", adjacent, {self}] }
        register: "b" → plan

  [.a]: wrap(" ", righthand)
        plan = { descendant_rules: [match ".b", <sibling plan>] }
        register: "a" → plan
```

Changing class "a" produces a plan that walks descendants for ".b",
checks ".b"'s adjacent sibling for ".c", and invalidates only that
element.
2026-03-09 18:35:46 +01:00
Aliaksandr Kalenik
201e985bea LibWeb: Track :required/:optional in :has() invalidation data
These pseudo-classes were missing from collect_properties_used_in_has,
which meant changes to the `required` attribute did not trigger
:has()-based ancestor invalidation.
2026-03-09 18:35:46 +01:00
Andreas Kling
5f434a442a LibWeb: Use targeted style invalidation when adding a new stylesheet
Instead of doing a full document style invalidation when a stylesheet is
dynamically added, we now analyze the new sheet's selectors to determine
which elements could potentially be affected, and only invalidate those.

This works by building an InvalidationSet from the rightmost compound
selector (the "subject") of each rule in the new stylesheet, extracting
class, ID, tag name, attribute, and pseudo-class features. We then walk
the DOM tree and only mark elements matching those features as needing a
style update.

If any selector has a rightmost compound that is purely universal (no
identifying features), or uses a pseudo-class not supported by the
invalidation set matching logic, we fall back to full invalidation.
2026-02-02 21:08:30 +01:00
Gingeh
3fe148f2d4 LibWeb: Implement the :default pseudo-class 2025-05-24 10:31:34 +01:00
Gingeh
7acc0f4a42 LibWeb: Implement :required/:optional pseudo-classes 2025-05-24 10:31:34 +01:00
Aliaksandr Kalenik
e677ab1699 LibWeb: Narrow :has() style invalidation to ancestor nodes
The current implementation of `:has()` style invalidation is divided
into two cases:
- When used in subject position (e.g., `.a:has(.b)`).
- When in a non-subject position (e.g., `.a > .b:has(.c)`).

This change focuses on improving the first case. For non-subject usage,
we still perform a full tree traversal and invalidate all elements
affected by the `:has()` pseudo-class invalidation set.

We already optimize subject `:has()` invalidations by limiting
invalidated elements to ones that were tested against `has()` selectors
during selector matching. However, selectors like `div:has(.a)`
currently cause every div element in the document to be invalidated.
By modifying the invalidation traversal to consider only ancestor nodes
(and, optionally, their siblings), we can drastically reduce the number
of invalidated elements for broad selectors like the example above.

On Discord, when scrolling through message history, this change allows
to reduce number of invalidated elements from ~1k to ~5.
2025-02-10 01:13:53 +01:00
Andreas Kling
39abd75d59 LibWeb: Use invalidation sets for link-related pseudo classes
- :any-link
- :link
- :local-link
2025-02-08 18:11:24 +01:00
Aliaksandr Kalenik
a6bea99959 LibWeb: Use invalidation sets for :defined style invalidation 2025-02-06 20:07:11 +01:00
Aliaksandr Kalenik
d762d16938 LibWeb: Use invalidation sets for :has() invalidation
Prior to this change, we invalidated all elements in the document if it
used any selectors with :has(). This change aims to improve that by
applying a combination of techniques:
- Collect metadata for each element if it was matched against a selector
  with :has() in the subject position. This is needed to invalidate all
  elements that could be affected by selectors like `div:has(.a:empty)`
  because they are not covered by the invalidation sets.
- Use invalidation sets to invalidate elements that are affected by
  selectors with :has() in a non-subject position.

Selectors like `.a:has(.b) + .c` still cause whole-document invalidation
because invalidation sets cover only descendants, not siblings. As a
result, there is no performance improvement on github.com due to this
limitation. However, youtube.com and discord.com benefit from this
change.
2025-01-29 09:30:18 +01:00
Aliaksandr Kalenik
74dc335b28 LibWeb: Allow to early break from InvalidationSet::for_each_property() 2025-01-29 09:30:18 +01:00
Aliaksandr Kalenik
aaff6f98b1 LibWeb: Fix collection of pseudo class names used in :has()
Before this change we were saving all pseudo class names as used in
:has() regardless of whether they are nested inside :has() or not.
2025-01-26 00:52:38 +01:00
Aliaksandr Kalenik
d5a82040e3 LibWeb: Fix underinvalidation of :nth-child using invalidation sets
For all invalidation properties nested into nth-child argument list we
need to invalidate whole subtree to make sure style of sibling elements
will be recalculated.
2025-01-25 10:16:21 +01:00
Aliaksandr Kalenik
34bf833a0a LibWeb: Expand invalidation sets usage to any attribute change
Before this change invalidation sets were only used for "class" and "id"
attribute changes.
2025-01-19 19:54:38 +01:00
Aliaksandr Kalenik
c5f2a88f69 LibWeb: Use invalidation sets to reduce style recalculation
Implements idea described in
https://docs.google.com/document/d/1vEW86DaeVs4uQzNFI5R-_xS9TcS1Cs_EUsHRSgCHGu8

Invalidation sets are used to reduce the number of elements marked for
style recalculation by collecting metadata from style rules about the
dependencies between properties that could affect an element’s style.

Currently, this optimization is only applied to style invalidation
triggered by class list mutations on an element.
2025-01-19 19:54:38 +01:00