Commit graph

2467 commits

Author SHA1 Message Date
Tim Ledbetter
a04e74f97d LibWeb: Templatize for_each_matching_attribute callback
Eliminates ~0.4% of profile time spent in AK::Function wrappers on
https://sainsburys.co.uk
2026-05-14 13:28:19 +02:00
Tim Ledbetter
68f832095a LibWeb: Skip animated property lookup when no animations are present 2026-05-14 13:28:19 +02:00
Gingeh
d8b986aaee LibWeb: Use cached hash for InvalidationSet equality 2026-05-13 14:10:54 +02:00
Callum Law
33705dc12f LibWeb: Generate CSS <paint> parsing
This changes the shape of the parsed value when we don't have a fallback
color for a `<url>` value from having no second value in the
`StyleValueList` to having an `EmptyOptionalStyleValue`.
2026-05-13 11:26:20 +01:00
Callum Law
fe121d403d LibWeb: Add EmptyOptionalStyleValue
This will be used in generated code to represent missing optionals i.e.
`<foo> <bar>?` with only a `<foo>` value will be represented by a
`StyleValueList` containing the relevant `<foo>` style value and an
`EmptyOptionalStyleValue`
2026-05-13 11:26:20 +01:00
Callum Law
6361dcaae5 Meta: Handle groups in CSS value parsing code generation
We don't yet support required groups so for the moment these just fall
back to generating the parsing code of their child
2026-05-13 11:26:20 +01:00
Sam Atkins
cb14d574de LibWeb: Invalidate descendants for container-* property changes
Whenever an ancestor's container-name or container-type changes, it
affects the matching of any `@container` queries, and so can affect the
style of any descendant.
2026-05-13 11:05:31 +01:00
Sam Atkins
67046049d9 LibWeb: Evaluate container queries when collecting matching style rules
MatchingRules now have a container_rule member which stores the nearest
ancestor CSSContainerRule, if any. When populating the rule cache, we
maintain a stack of CSSContainerRules that we are within, and use
record the last one on the MatchingRule, so that it's O(1) instead of
having to walk up the rule's ancestors each time. This does mean we
have reimplement some "for each rule" code.

When collecting rules to apply to an element, we see if the MatchingRule
has a container_rule, and if so, we evaluate that rule's query to see
if the element has a matching container. We then also match any ancestor
container rules, using the cached parent container rule.

Add a custom test to cover the case of nested name-only `@container`s,
which WPT lacks currently as far as I can tell.
2026-05-13 11:05:31 +01:00
Sam Atkins
1f66b1cc13 LibWeb: Implement ContainerQuery evaluation
ContainerQuery now stores its feature requirements. When evaluated, it
uses these and the passed-in container name to identify a container
element to match against, then evaluates its condition against that
element.

None of this is yet in use anywhere, but will be soon.
2026-05-13 11:05:31 +01:00
Sam Atkins
b6e8239203 LibWeb: Collect container query feature requirements
A given element may be a container in different ways, depending on its
`container-type` property. For a container query to match an element,
that element must have the required container type for each feature
that the query checks. This commit implement a step to collect those
required types, so that we can quickly eliminate potential container
elements that lack a required containment type.
2026-05-13 11:05:31 +01:00
Sam Atkins
2a89c56143 LibWeb/CSS: Cache CSSContainerRule's nearest ancestor CSSContainerRule
Once we start evaluating `@container` matches, we will need to check if
any ancestor `@container`s also match. Caching the nearest one, if any,
will let us skip walking the entire ancestor chain.
2026-05-13 11:05:31 +01:00
Sam Atkins
88aea2e11d LibWeb: Give BooleanExpression an evaluation context
Different users of BooleanExpression have different requirements for
evaluation:
- `@media` needs a Document
- `@supports` doesn't need anything
- `@container` needs a container Element

To support these without expanding the API, replace the Document*
parameter with a BooleanExpressionEvaluationContext type which contains
these different values.

No behaviour changes.
2026-05-13 11:05:31 +01:00
Sam Atkins
442319c676 LibWeb/CSS: Update grammar for @container
Corresponds to
efc0c6f847
2026-05-13 11:05:31 +01: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
Callum Law
f578d95be0 LibWeb: Support multiple keywords in parse_specific_keyword_value
This will allow us to combine multiple calls into one and avoid
repeatedly parsing and then discarding `KeywordStyleValue`s
2026-05-13 11:00:07 +01:00
mikiubo
b9ed64d1ce LibWeb: Add dedicated overflow-clip-margin style value
Store overflow-clip-margin as a structured style value instead of
collapsing it into generic keyword/length lists during parsing.

Keep omitted visual-box as a distinct state so painting can apply
the spec default per element type (content-box for replaced,
padding-box otherwise), while preserving canonical serialization.
2026-05-12 14:09:13 +02:00
mikiubo
a3e420bb9e LibWeb: Parse visual-box in overflow-clip-margin
Support content-box, padding-box, and border-box in
overflow-clip-margin parsing, including combinations with length.

Canonicalize parsed values to match the grammar and expected CSSOM
serialization in css-overflow parsing tests.
2026-05-12 14:09:13 +02:00
Tim Ledbetter
013ede7214 LibWeb: Use targeted invalidation when adopting a stylesheet
Previously, adding or removing a constructed `CSSStyleSheet` via
`document.adoptedStyleSheets` invalidated style for the entire document
or shadow root. We now route this through the same style invalidation
machinery that's used when we add or remove `<style>` elements,
resulting in fewer full invalidations.
2026-05-10 20:19:32 +02:00
Tim Ledbetter
ca43780bba LibWeb: Move stylesheet add/remove invalidation into shared helper
No behavior change.
2026-05-10 20:19:32 +02:00
Andreas Kling
5cf90c0d0d LibWeb: Avoid stale delayed resource callbacks
Delayed preload and image animation callbacks can outlive the objects
they notify. Incremental sweeping makes this easier to hit because stale
callback state may be reclaimed before the delayed work runs.

Use a weak link element when firing preload load and error events, and
use a weak image style value from animated image timers instead of a raw
pointer.
2026-05-10 10:58:11 +02:00
Shannon Booth
5adfd1c43a LibWeb/Bindings: Generate struct definitions from IDL dictionaries
Previously we were inconsistent by generating code for enum definitions
but not generating code for dictionaries. With future changes to the
IDL generator to expose helpers to convert to and from IDL values
this produced circular depdendencies. To solve this problem, also
generate the dictionary definitions in bindings headers.
2026-05-09 10:49:49 +02:00
Tim Ledbetter
ac685a8a79 LibWeb: Only invalidate style on links matching :local-link
Currently, this is the only pseudo class which is URL-sensitive. When
we implement proper tracking of visited URLs this will need to be
replaced with something more comprehensive.
2026-05-08 12:30:29 +01:00
Tim Ledbetter
f29bc0c996 LibWeb: Use scroll compensation for background-attachment: fixed
Instead of baking the current scroll offset into the background
positioning area at record time, use the `ScrollCompensation` visual
context node to negate ancestor scroll frames dynamically at replay
time. This keeps the background fixed relative to the viewport even
when the display list is cached and replayed at different scroll
positions, and works correctly with arbitrarily nested scroll
containers.
2026-05-08 13:09:40 +02:00
Andreas Kling
98a9c36ba7 LibWeb: Make structural-position pseudos targetable when sole feature
When a sheet's rightmost compound carries :first-child, :last-child,
or :only-child but no other class/tag/id/attr feature, the sheet
add/remove path can target the boundary children directly instead of
falling back to a whole-subtree invalidation.

The narrowing in extend_style_sheet_invalidation_set_with_style_rule
deliberately only kicks in when the structural pseudo is the only
feature in the rightmost compound. A selector like `.foo:first-child`
keeps targeting `.foo` rather than every first-child in the
document.

Adds matchers for :first-child / :last-child / :only-child in
InvalidationSetMatcher so the targeted walk can recognize them on
each candidate element.
2026-05-07 19:32:27 +02:00
Andreas Kling
62440c1472 LibWeb: Make :host, :root, and interaction-state pseudos targetable
Stylesheet add/remove previously fell back to a whole-subtree
invalidation whenever a sheet's rightmost compound carried one of
these pseudo-classes. They each match a small, knowable set of
elements at any moment, so the invalidation walk can target them
directly:

- :host matches one element per shadow root and :root matches the
  html element. Mark them targetable.
- :hover, :focus, :focus-visible, :focus-within, :active, and
  :target all match at most a handful of elements at a time. Mark
  them targetable and add matchers in InvalidationSetMatcher that
  consult Document::hovered_node, focused_area, target_element,
  and the element's own focused/active state.
2026-05-07 19:32:27 +02:00
Andreas Kling
0ee13f9a82 LibWeb: Use targeted invalidation for @keyframes in added/removed sheets
@keyframes rules only affect elements that already reference the named
animation. Instead of falling back to a whole-subtree invalidation
when a sheet contains @keyframes, walk the sheet's @keyframes rules in
StyleSheetList::add_sheet/remove_sheet and reuse the existing per-rule
helper to dirty just the elements that reference each animation-name.
2026-05-07 19:32:27 +02:00
Andreas Kling
89933f18d1 LibWeb: Don't force broad invalidation when adding/removing @font-face
@font-face declares a resource whose effect on computed style is
deferred until the font actually loads, which is handled by the
CSSFontLoaded path. Adding or removing the at-rule itself does not
change any element's computed style, so the sheet add/remove path
no longer has to fall back to a whole-subtree invalidation when a
sheet contains @font-face.
2026-05-07 19:32:27 +02:00
Sam Atkins
d7344a96bb LibWeb: Parse and propagate container and container-name properties 2026-05-07 17:48:53 +02:00
Aliaksandr Kalenik
f8640d813a LibGfx+LibWeb: Make DecodedImageFrame a value type
DecodedImageFrame only wraps a ref-counted Bitmap and color-space
metadata. The frame object itself does not provide shared mutable
state or lifetime ownership beyond those members, so ref-counting it
adds an unnecessary layer of indirection.
2026-05-07 16:08:13 +02:00
Aliaksandr Kalenik
568b7ce7ea LibWeb: Make Paintable tree ref-counted
The Paintable tree and its supplemental painting data structures were
GC allocated because that was the easiest way to manage it and avoid
leaks introduced by ref cycles. This included the Paintable subclasses
themselves plus StackingContext, ChromeWidget, Scrollbar, ResizeHandle,
and scroll-frame state.

We are now trying to reduce GC allocation churn on layout and painting
updates, so keeping this short-lived rendering tree outside the JS heap
is a better fit. Move Paintable to RefCountedTreeNode, make painting
helpers ref-counted or weakly reference Paintables, and update the
layout and event-handler call sites to use RefPtr/WeakPtr ownership.
2026-05-07 15:03:44 +02:00
Tim Ledbetter
49949181d7 LibWeb: Apply backwards fill to CSS transitions
CSS transitions must keep their start value in the cascade during the
`transition-delay` period. Set the transition effect fill mode to
backwards so the before phase resolves to the start keyframe.
2026-05-07 11:00:32 +01:00
Callum Law
b1d0746292 LibWeb: Absolutize <paint> fallback colors
Previously we stored these within the `URLStyleValue` which didn't
itself have an `absolutized` method so wouldn't absolutize the fallback
color. We now store the two values alongside each other in a
`StyleValueList` which correctly handles absolutization
2026-05-07 10:07:09 +01:00
Callum Law
23e5a4ed64 LibWeb: Always apply CSS stroke/fill property changes
Previously changes to `none` from any other value would be ignored. This
also moves handling into `ComputedProperties` in line with other
properties.
2026-05-07 10:07:09 +01:00
Andreas Kling
93194ea205 LibWeb: Account stylesheet storage as external memory
Report stylesheet source text, rule lists, namespace caches,
declaration vectors, custom property maps, and owner sets through
the GC external memory hook.
2026-05-07 10:03:09 +02:00
Andreas Kling
de7ed9a498 LibWeb: Target :first/:last/:only-child sibling invalidation precisely
invalidate_structurally_affected_siblings used to mark every previous
or next sibling that had ever observed :first-child, :last-child, or
:only-child. Their match result can flip for at most one element per
single mutation, so compute that transition target once and only mark
it. The :nth-child and :nth-last-child paths still re-evaluate every
affected sibling since their indices shift on every mutation.

Drops elementStyleNoopRecomputations from ~22000 to ~15000 on the
github.com/LadybirdBrowser/ladybird page (60s settle, three-run
median). Rebaselines the structural-pseudo-class-precision test, with
the :nth-child / :nth-last-child counters intentionally unchanged.
2026-05-06 21:22:02 +02:00
Tim Ledbetter
d750b49f4a LibWeb: Filter rule matches by target pseudo-element
When computing style for a pseudo-element, only consider rules whose
rightmost pseudo-element matches the one being queried.
2026-05-05 21:26:00 +01:00
Aliaksandr Kalenik
76c79ee522 LibGfx: Remove ImmutableBitmap
DecodedImageFrame now owns decoded bitmap pixels directly, so the
separate ImmutableBitmap wrapper no longer carries useful semantics.
Remove the class and pass decoded image frames or bitmaps at the
boundaries where pixels are actually required.

The Skia image cache now keys off DecodedImageFrame, matching the
display-list commands that paint decoded images. Video frames stay
owned by LibMedia, with the explicit YUV-to-bitmap conversion living
at HTMLVideoElement's decoded-frame entry point for canvas and WebGL
callers.
2026-05-05 14:39:17 -05:00
Aliaksandr Kalenik
40f2abb7fe LibGfx+LibWeb: Add DecodedImageFrame
Decoded image data should not continue to traffic in ImmutableBitmap now
that the bitmap wrapper is being retired. Introduce DecodedImageFrame as
the paintable decoded-image unit and store a Bitmap plus ColorSpace in
it directly.

Thread the new frame type through decoded image data, display-list
image commands, filters, canvas drawImage, patterns, WebGL texture
upload, and CSS/SVG image consumers. ImmutableBitmap remains only at
the legacy boundaries that still need it, such as HTML video snapshots
and callers that explicitly ask for a bitmap snapshot.

This keeps color-space ownership with the decoded frame while making
the expensive or legacy ImmutableBitmap path explicit at the few call
sites that still need it.
2026-05-05 14:39:17 -05:00
Jelle Raaijmakers
2f39b2dd63 LibWeb: Split shadow stylesheet host-side invalidation reach
Broad shadow-root stylesheet changes already restyle the whole shadow
tree, but host-side fallout does not always need a document-wide
invalidation. Split the host-side reach classification so selectors
contained to the host subtree, such as `:host *` and `:host > *`,
invalidate the host, while sibling-escaping selectors such as
`:host + :has(*)` still invalidate the host's root.

Recognize sibling escapes through positive selector-list pseudos such as
`:is()` and `:where()` as well, including selectors like
`:is(:host) + :has(*)` and `:is(:host + .item)`.

This avoids turning host-contained shadow stylesheet changes into full
document style invalidations. On https://pomax.github.io/bezierinfo/,
this reduces the time to produce a layout tree from about 8.7s to 3.6s
on my machine.
2026-05-05 18:36:34 +02:00
Tim Ledbetter
1cc288730c LibWeb: Stop storing CascadedProperties on each element
`CascadedProperties` is now allocated fresh when style is recomputed.
`Element` and `PseudoElement` no longer keep hold of cascaded
properties.
2026-05-05 16:02:23 +02:00
Tim Ledbetter
c26922c898 LibWeb: Cache values needed after the cascade finishes
Previously, we consulted `cascaded_properties()` in a couple of places
after the cascade pass for the relevant element had finished, forcing
`CascadedProperties` to outlive style resolution.

We now keep the small set of values these consumers need on
`ComputedProperties`. We keep hold of resolved specified values for
properties whose computation depends on inherited info, so they can be
re-resolved when an ancestor changes. We also keep the raw winning
cascaded font-size, as this is needed by the time-traveling monospace
font quirk implemented by `recascade_font_size_if_needed()`.
2026-05-05 16:02:23 +02:00
Tim Ledbetter
6132b5fa1b LibWeb: Resolve <th> text-align using only the parent's computed value 2026-05-05 16:02:23 +02:00
Andreas Kling
355fb6b825 LibWeb: Stream Rust CSS tokenizer tokens over FFI
Avoid building a temporary Rust token vector before calling back into
C++. The tokenizer now invokes the callback as each token is produced,
while borrowing the already-filtered input for source slices.

Reserve an initial C++ token capacity from the input size so the common
path avoids repeated growth while appending the converted tokens.

With this change, the Rust CSS tokenizer is now ~1.3x faster than the
C++ CSS tokenizer at churning through all the https://vercel.com/ CSS.
2026-05-03 17:22:17 +02:00
Shannon Booth
132daf35be LibWeb/Bindings: Use Vector for sequences of GC::Root
Generate plain Vector storage for IDL sequences whose element type is
already wrapped in GC::Root, instead of using GC::RootVector.
2026-05-03 10:56:45 +02:00
Sam Atkins
4278194d96 LibWeb/CSS: Port the CSS Tokenizer to Rust
test-css-tokenizer is updated to run both the C++ and Rust tokenizers
and compare their output, to ensure they behave identically. The Parser
still uses the C++ Tokenizer.

The LibWeb crate, FFI layer etc are all based on the existing ones for
other libraries.

This is a direct AI translation to get us started, and not idiomatic
Rust. Future work can be done to make it more sensible.
2026-05-03 09:49:00 +02:00
Sam Atkins
8b09a8e568 LibWeb/CSS: Add missing spec comment to consume_string_token() 2026-05-03 09:49:00 +02:00
Sam Atkins
30043210e3 LibWeb/CSS: Stop using a define for the replacement character 2026-05-03 09:49:00 +02:00
Callum Law
8e433ed742 LibWeb: Implement custom display interpolation
The newly failing tests in
`css-display/animations/display-interpolation.html` are due to the test
not having been updated for the new spec level and is in line with other
browsers.
2026-05-01 18:46:14 +02:00
Callum Law
2203c0d6aa LibWeb: Always store display as DisplayStyleValue
Previously values set via Typed-OM would be stored as
`KeywordStyleValue` which fell back to `inline` within
`ComputedProperties::display()`.
2026-05-01 18:46:14 +02:00
Callum Law
c84c944f20 LibWeb: Implement reification of DisplayStyleValue
Values which can be represented by a single keyword are reified as
`CSSKeywordValue` and those which can't are reified as `CSSStyleValue`
as before
2026-05-01 18:46:14 +02:00