This is preparatory work for routing presentational hints through the
same cascade pipeline as ordinary CSS declarations. Currently those
hints bypass `cascade_declarations()` and write into
`CascadedProperties` directly, which means `var()` substitution has to
be duplicated in a separate pass. To unify them at a single point, we
need to be able to feed an arbitrary property list.
Per "find flattened slotables", a <slot> whose root is a shadow root is
recursed through, not appended to the result. ::slotted() in an outer
shadow must therefore not match such an intermediate slot.
Fixes the gallery on Reddit comment pages: a re-slotted <slot> was
picking up `::slotted(:not([slot])) { display: grid }` from the inner
shadow, which made the <ul> size to its content rather than the flex
container, leaving the carousel's "next" button with a 0px translate.
Share the style cache for shadow roots whose only active author sheet is
the same constructed stylesheet. Matching already carries the effective
shadow root separately, so the cache can be reused while selectors such
as :host and ::slotted() still evaluate against each consuming shadow
root.
Keep the optimization conservative by falling back to the existing
per-scope cache whenever the shadow root has multiple active sheets, a
non-constructed sheet, or a page user stylesheet. Drop the shared cache
when the stylesheet rules or media query match state change.
Add coverage for two shadow roots adopting the same constructed sheet,
including :host, ::slotted(), and replaceSync() invalidation.
Keep cached MatchingRule entries independent from the shadow root that
owns the rule cache. Thread the effective rule shadow root through style
matching as transient state instead, so a rule cache can later be shared
by multiple scopes without copying every cached rule.
This preserves the existing matching behavior by deriving the effective
rule root from each cache lookup site. Pseudo-class invalidation already
operates on a single style scope, so it no longer needs a per-rule scope
filter.
When computing pseudo-element style and no pseudo-element rules match,
StyleComputer was returning early without clearing the cascaded and
custom property data on the AbstractElement. As a result,
getComputedStyle() on the pseudo-element kept exposing values from a
previous matching state.
Clear both before bailing so a transition from matched to unmatched
leaves the pseudo-element in a clean state.
Previously, we were accidentally creating temporary copies of custom
property maps on both sides of a ternary in `compute_style_impl()`. We
now bind to a static empty sentinel instead so the reference binds
directly to `own_values()` without copying.
When inheriting custom-property data from a parent element, we were
copying the parent's full CustomPropertyData regardless of whether
each property was registered with `inherits: false`. That caused
non-inheriting registered properties to leak from the parent,
contrary to the @property spec.
Wrap the parent-side lookup so we strip any custom property whose
registration says it should not inherit, and only build a fresh
CustomPropertyData when at least one property was actually filtered.
Key the filtered view's cache on both the destination document's
identity and its custom-property registration generation. The
generation counter is local to each document, so a subtree adopted
into another document (or queried via getComputedStyle from another
window) could otherwise pick up a cached view computed under an
unrelated registration set and silently skip non-inheriting filtering
in the new document.
A @keyframes rule scoped to a shadow root was not reliably reached
from an animated slotted light-DOM element: the keyframes lookup
walked the element's own root first, then fell back to the document,
but slotted elements can pick up animation-name from a ::slotted(...)
rule that lives in an ancestor shadow root rather than in the
element's own tree.
Track the shadow-root scope that supplied each winning cascaded
declaration, and use that scope to resolve the matching @keyframes
when processing animation definitions. A shared constructable
stylesheet can be adopted into several scopes at once, so the
declaration object alone is too weak as a key; the per-entry
shadow-root pointer disambiguates which adoption actually contributed.
Also refresh running CSS animations' keyframe sets when style is
recomputed. Previously only the first animation creation path set a
keyframe set, so an existing animation never picked up newly inserted
@keyframes rules.
This allows us to avoid the ugly hack in
`property_accepted_type_ranges()`.
This also updates the `ValueType` to be `opacity-value` rather than
`opacity` to match the spec.
Record per-feature :has() invalidation metadata instead of only tracking
whether some selector somewhere mentions a class, id, attribute, tag,
or pseudo-class. The new buckets preserve the relative selector and a
coarse scope classification for each :has() argument, which gives the
next invalidation step enough information to route mutations more
precisely.
Keep this commit behavior-preserving for mutation handling by only
switching the lookup path over to the new metadata buckets. Expose a
test-only counter for the number of candidate :has() metadata entries a
mutation matched, and add coverage showing that one feature can map to
one or multiple :has() buckets without forcing a document-wide yes/no
answer.
sort_matching_rules() was calling Selector::specificity() inside the
sort comparator. MatchingRule already caches the specificity value
at rule insertion time, so use that directly instead.
This were introduced in dfe5d00 but only papered over the underlying
issue that we were computing style for element belonging to detached
documents - this underlying fix was implemented in c173a66 so these
fallbacks/guards are no longer needed
...instead of separate Element and PseudoElement arguments.
As noted, AbstractElement's constness is weird currently, but that's a
tangent I don't want to go on right now.
When a `@keyframes` rule contains `animation-timing-function` with a
`var()`, we cannot eagerly resolve it to an `EasingFunction` at rule
cache build time because there is no element context available. We now
store the unresolved `StyleValue` and defer resolution to
`collect_animation_into()`, where the animated element's custom
properties can be used to substitute the variable. Previously, an
`animation-timing-function` with a `var()` in a `@keyframe` would cause
a crash.
When a shorthand like `background` containing `var()` is used in
a `::selection` rule, the shorthand was filtered out by the pseudo-
element property whitelist before variable resolution could occur.
This left PendingSubstitutionStyleValue longhands unresolved,
causing either a crash or incorrect computed values.
Allow unresolved shorthands to bypass the pseudo-element filter so
variable resolution can proceed. After resolution and expansion
into longhands, filter out any that the pseudo-element does not
support.
Fixes#8625.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This requires us to front load computation of writing-mode and direction
before we encounter any logical aliases or their physical counterparts
so that we can create a mapping context.
Doing this at compute rather than cascade time achieves a few things:
1) Brings us into line with the spec
2) Avoids the double cascade that was previously required to compute
mapping contexts
3) We now compute values of logical aliases, while
`style_value_for_computed_property` maps logical aliases to their
physical counterparts, this didn't account for all cases (i.e. if
there was no layout node, Typed OM, etc).
4) Removes a hurdle to moving other upstream processes (i.e. arbitrary
substitution function resolution, custom property computation) to
compute time as the spec requires.
Pass the requested font weight and slope to generic_font_name() and use
them to select the most suitable font family. Families that have the
exact requested weight are strongly preferred, followed by families with
more style variety.
This ensures that when CSS requests bold text with a generic font family
like `serif`, we select a family that actually supports bold weights
rather than one that only has regular weight.
Co-authored-by: Jelle Raaijmakers <jelle@ladybird.org>
Per the CSS Animations spec, the animation-timing-function property
describes how the animation progresses between each pair of keyframes,
not as an overall effect-level timing function.
Previously we set it as the effect-level timing function on the
AnimationEffect, which caused easing to be applied to the global
animation progress. This made animations with multiple keyframes
"pause" at the start and end of the full animation cycle instead of
easing smoothly between each pair of keyframes.
Now we:
- Store per-keyframe easing in ResolvedKeyFrame from @keyframes rules
- Store the default easing on CSSAnimation instead of on the effect
- Apply per-keyframe easing to the interval progress during
interpolation, falling back to the CSS animation's default easing
- Also store per-keyframe easing from JS-created KeyframeEffects to
avoid incorrectly applying CSS default easing to replaced effects
When processing CSS animations, we were only looking up @keyframes
rules from the document-level rule cache, passing nullptr for the
shadow root parameter. This meant that @keyframes defined inside
shadow DOM <style> elements were never found, and animations
referencing them would silently have no keyframes.
Fix this by first checking the element's containing shadow root for
@keyframes rules, then falling back to the document-level rules.
Per CSS2 section 9.7, if position has the value absolute or fixed,
the computed value of float is none. We were already blockifying the
display for such elements, but not resetting the computed value of
float. This made the wrong value observable via getComputedStyle().
The :host::part() pattern allows a shadow DOM's own stylesheet to
style its internal elements that have been exposed via the part
attribute. Previously, ::part() rules were only collected from
ancestor shadow roots (for external part styling), but never from the
element's own containing shadow root.
Fix this by also collecting ::part() rules from the element's own
shadow root in collect_matching_rules(). Additionally, fix the
selector engine's ::part() compound matching to preserve the
shadow_host when the rule originates from the part element's own
shadow root, allowing :host to match correctly in the same compound
selector.
This fixes 2 previously failing WPT tests:
- css/css-shadow-parts/host-part-002.html
- css/css-shadow-parts/host-part-nesting.html
When an element inside a shadow DOM is itself a shadow host, and a
rule from the parent shadow DOM uses a selector like
`:host([attr]) .child`, the combinator traversal needs to reach the
outer shadow host to match `:host([attr])`.
Previously, when the styled element was a shadow host and the rule
came from outside its own shadow root, we set shadow_host_to_use to
nullptr. This caused traverse_up() to use parent() which cannot
cross the shadow boundary, preventing the selector from reaching the
outer :host.
Fix this by using the rule's shadow root's host as the traversal
boundary instead of nullptr. This allows the descendant combinator
to traverse from the element up through the enclosing shadow DOM to
reach the outer shadow host for :host() matching.
The CSS Scoping spec says ::slotted() represents elements assigned
"after flattening" to a slot. When a slot element is itself slotted
into another slot (nested slots), the flattened tree resolves the
chain so that the inner content appears in the outermost slot.
Previously, we only collected ::slotted() rules from the directly
assigned slot's shadow root. This meant that styles defined in an
outer shadow DOM's ::slotted() rules would not apply to elements
that were transitively slotted through intermediate slots.
Fix this by walking up the slot assignment chain when collecting
and matching ::slotted() rules, so that rules from every shadow
root in the chain are considered.
When matching selectors like `:host ::slotted(div)`, the selector
engine needs to traverse up from the slot element to reach the shadow
host for `:host` matching. Previously, we passed shadow_host_to_use
which was derived from the *slotted element's* DOM position. For
elements in the light DOM (not inside any shadow root), this was null,
causing traverse_up() to use parent() instead of
parent_or_shadow_host_element(). This meant the traversal could never
cross the shadow boundary from inside the shadow tree to reach the
host element.
Fix this by deriving the shadow host from the slot's containing shadow
root, which is the correct scope for combinator traversal within
::slotted() rule matching.
When a CSS animation keyframe uses var() referencing a nonexistent or
invalid custom property, variable substitution produces a
guaranteed-invalid value. The animation keyframe processing code did not
handle this case, allowing the value to reach compute_opacity() (and
similar functions) which would hit VERIFY_NOT_REACHED().
Fix this by skipping guaranteed-invalid values in
compute_keyframe_values, matching how the regular cascading code treats
them.
This fixes a crash on chess.com.
Replace VERIFY assertions with fallbacks in Length::for_element()
when computed_properties or root element is null. Guard
inheritance_parent->computed_properties() in StyleComputer.
We can bail earlier in `StyleComputer::compute_style_impl()` when we
know no pseudo-element rules matched, preventing quite a lot of
CascadedProperties churn. This was especially visible in WPT's
`encoding` tests, for which this is a hot path on account of all the
`<span>`s they create.
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.
Previously we didn't clear the computation context caches after:
- Recomputing inherited style
- Computing keyframe values
We now clear the caches in those two cases and verify it has been
cleared before using it.
Fixes#7959
Elements in internal shadow trees that represent CSS pseudo-elements
(e.g. the DetailsContent slot in <details>) store their cascaded
properties on the host element's pseudo-element data, not on the
element itself. This meant that when slotted elements walked the
inheritance chain and encountered such a slot, they would dereference
null cascaded properties and crash.
Fix this by copying the cascaded properties onto the slot element
itself after computing its style, keeping both cascaded and computed
properties accessible in the same place.
We can avoid some overhead by computing the values immediately instead
of setting them within `ComputedProperties` and then calling
`compute_property_values`
Previously we computed font properties separately from other properties
for two reasons:
1) These font properties were computed using a different length
resolution context than the rest of the properties.
2) These properties were required to be computed before creating the
length resolution context for the rest of the properties.
The first issue was solved in the previous commit by introducing a
generic method to get the computation context for a property, and
the second is solved in this commit by computing properties in the
required order.
This simplifies the code a bit and opens up some opportunities for
optimization.
The computation context used is the main thing distinguishing the
computation of font/non-font properties so having a generic method to
handle this will allow us to consolidate logic between the two.
Replace per-element OrderedHashMap storage for custom properties with
a RefCounted chain (CustomPropertyData) that enables structural
sharing. Each chain node stores only the properties declared directly
on its element, with a parent pointer to the inherited chain.
Elements that don't override any custom properties share the parent's
data directly (just a RefPtr copy). During cascade, only entries that
actually differ from the parent are stored in own_values - the rest
are inherited through the chain. During var() resolution, resolved
values are compared against the parent's and matching entries are
dropped, enabling further sharing.
The chain uses a depth limit (max 32) with flattening, plus
absorption of small parent nodes (threshold 8) to keep lookups fast.
This reduces custom property memory from ~79 MB to ~5.7 MB on
cloudflare.com.
The author_rules vector always contains at least one layer (the
unlayered entry), so checking is_empty() was always false. Instead,
check whether any layer actually contains rules.
These get computed to an equivalent RGBColorStyleValue.
To support this, we now store the computed color-scheme on the
ComputationContext when computing properties that might contain a color.
This has a nice bonus of correcting the css-accent-color test's result.
Every user of this actually wants an ancestor in the flat tree - taking
things like `<slot>` into account. So rename it and adjust its behavior
to use that.
In a future commit we will require this to be in it's computed form
before calling `ComputedProperties::computed_font_list()`.
We will also depend on other properties (e.g. `font-kerning`,
`font-variant-*`) but these are unaffected by computation.
The main change here is that we now properly absolutize values which
means we now support `random()` and `sibling-{count,index}()`
We are also more consistent with how we handle computation for the other
font properties
Previously we implemented an all encompassing `MathDepthStyleValue`
specifically for the `math-depth` property, this was unnecessary since
we can represent `auto-add` and `<integer>` using existing `StyleValue`
classes.
This brings the values created from parsing in line with those set via
`StylePropertyMap` which allows us to simplify computation
Replace two uses of heap-allocated Bitmap with stack-allocated
FixedBitmap. The sizes are known at compile time, so there's no need
to hit malloc for these.
This means we now allow oblique angles when parsing the `font`
shorthand.
This also required us to rename the existing `FontStyle` enum to
`FontStyleKeyword`
Previously we would do two cascaded property lookups per property during
style computation - the first to get the value of the property and the
second to check whether it was marked as important. We now do this as a
single lookup to save some unnecessary work.
This adds visit_edges(Cell::Visitor&) methods to various helper structs
that contain GC pointers, and makes sure they are called from owning
GC-heap-allocated objects as needed.
These were found by our Clang plugin after expanding its capabilities.
The added rules will be enforced by CI going forward.
Previously the logic to compute transitions was split across
`ComputedProperties`, `StyleComputer`, and `Animatable` - this commit
consolidates it all in `ComputedProperties`
The `:has()` pseudo-class requires traversing descendants (or siblings)
to find matches.
With this change we cache results keyed by `(Selector*, Element*)`
pairs. The cache is stored in `StyleComputer` and cleared at the start
of each style computation pass in `Document::update_style()`.
When `:has()` uses a descendant combinator and we find a match, we also
cache that all ancestors between the matching descendant and the
anchor match. For example with `div:has(.target)`:
```html
<div id="A"> <!-- checking :has(.target) here -->
<div id="B">
<div id="C">
<span class="target"/>
</div>
</div>
</div>
```
When we find `.target` while checking `div#A`, we also cache that
`div#B` and `div#C` match `:has(.target)` since they also contain
`.target`. Later when styling these elements, we get cache hits and skip
traversal.
...when the style is `none` or `hidden`. `outline-width` is not affected
by `outline-style: none` at all.
In our codebase, that means doing the border-width conversion when
assigning to ComputedValues.
Corresponds to:
2a3d1e4d1009f11f2ef9
In level 2 of the web animations spec, times are no longer always
measures in milliseconds, they can also be percents when dealing with
progress-based (i.e. scroll-based) timelines.
We don't actually support percent times yet but this change will make it
easier to implement when we do.
Web Animations Level 2 disallows setting some `AnimationEffect` timing
values (start delay, end delay, iteration duration) directly and instead
allows authors to set the specified values which are then normalized
into the actual used values taking into account the type of the
associated timeline (i.e. progress- vs time-based)
This makes us consistent with how we handle this value within the
`background` shorthand and allows us to remove the special handling in
`StyleComputer::for_each_property_expanding_shorthands`
Computing the font for an element in `compute_font` is premature since
we are yet to apply animated properties - instead we should compute the
value on the fly (with a cache to avoid unnecessary work) to ensure we
are respecting the latest values
Font computation and loading is distinct enough from style computation
that it makes more sense to have this in it's own class.
This will be useful later when we move the font loading process to
`ComputedProperties` in order to respect animated values.
Since we resolve any relative lengths at compute time there's no need
for the value to be passed around as a `NumberOrCalculated` and we can
just resolve it within `ComputedProperties::font_variation_settings`.
The only place this is used it is used with value_or so there's no need
to return it is an `Optional`.
This is only used for loading fonts (which occurs during style
computation) so there's no need to store it in `ComputedValues`
From the spec:
> The owning element of a transition refers to the element or
pseudo-element to which the transition-property property was applied
that generated the animation.
https://drafts.csswg.org/css-transitions-2/#owning-element
Previously we only stored the element.
An upcoming commit will migrate the contents of Headers.h/cpp to LibHTTP
for use outside of LibWeb. These CORS and MIME helpers depend on other
LibWeb facilities, however, so they cannot be moved.
Previously we would only update these if:
a) We had a cascaded value for `transition-property`
b) The source of that cascaded value had changed since we last
registered transitions
This meant that there were a lot of changes we didn't apply:
- Changes exclusively to properties other than `transition-property`
(e.g. `transition-duration`, `transition-behavior`, etc)
- Removing the `transition-property` property
- Updating the `transition-property` property in a way that didn't
change it's source (e.g. setting it within inline-style)
Unfortunately this does mean that we now register transitions for all
properties on most elements since "all" is the initial value for
"transition-property" which isn't great for performance, but that can be
looked at in later commits.
Also renames the `clear_transitions` function to clarify this doesn't
affect the associated transition animations.
This fixes an issue where transitions weren't being cancelled when the
relevant transition-property entry was no longer present