Commit Graph

2154 Commits

Author SHA1 Message Date
Callum Law
860ec7aea3 LibWeb: Store type of MediaFeatureValue explicitly
In a later commit we will be storing the value as a `StyleValue` and
thus won't be able to easily get it's type
2026-03-24 14:00:01 +00:00
Callum Law
d55e0a858a LibWeb: Add reusable Resolution::from_style_value method 2026-03-24 14:00:01 +00:00
Callum Law
b07ef9435e LibWeb: Remove fallback behavior from color_or_fallback
All of the properties using this method only accept color values so the
fallback behavior wasn't required
2026-03-24 13:56:01 +01:00
Callum Law
e2f2f813b6 LibWeb: Handle accent-color auto value explicitly
`accent-color` is the only user of the fallback functionality of
`color_or_fallback`, by handling this explicitly we can remove that
fallback functionality in a later commit.

Also includes a couple of improvements for `accent-color` specifically:
 - We don't set it in `ComputedValues` twice.
 - `ComputedProperties::accent_color` returns a non-optional value
   (since we always have one)
 - `ComputedProperties::accent_color` takes a `ColorResolutionContext`
   instead of generating one itself from a `LayoutNode`, this will allow
   us to reuse shared resolution contexts in the future
2026-03-24 13:56:01 +01:00
Charlie Tonneslan
e564eff402 LibWeb/CSS: Fix typo overriden -> overridden in comments 2026-03-24 11:08:23 +01:00
Rob Ryan
281c2ce545 LibWeb: Seed ancestor context for computing style with no layout node 2026-03-24 05:02:48 +01:00
Andreas Kling
a5db4c874e LibWeb: Skip declaration parsing in block contents when not applicable
When parsing block contents, the CSS parser speculatively tries to parse
each item as a declaration first. If that fails, it restores the token
position and tries again as a qualified rule. This means every qualified
rule inside an at-rule block (e.g. @layer, @media) gets parsed twice:
once as a failed declaration (which consumes all tokens via
consume_the_remnants_of_a_bad_declaration), and then again successfully
as a rule.

Add a lookahead that checks for the `ident whitespace* ':'` pattern
before attempting declaration parsing. Since declarations must start
with this pattern per spec, we can skip the attempt entirely when it
doesn't match and go straight to qualified rule parsing.

This is a massive win on large Tailwind CSS stylesheets (like the one
used by chatgpt.com) where thousands of rules inside @layer blocks were
being double-parsed. On a 1.2MB Tailwind v4 stylesheet, parse time goes
from ~2000ms to ~95ms (21x speedup).
2026-03-23 09:28:23 +01:00
Andreas Kling
2e6e597c94 LibWeb: Skip font sources with unsupported formats early
Filter out unsupported @font-face sources (e.g. .eot files, or sources
with an explicit unsupported format() hint) when populating the
FontFace's URL list, rather than fetching and attempting to decode them.

This matches the behavior of Blink and WebKit, which both reject .eot
URLs by filename when no format() hint is present, to avoid conflicts
with old WinIE-style @font-face rules.

Previously, we would fetch .eot files over the network, fail to decode
them, and then reject the font load promise, leading to a flood of
"NetworkError: Failed to load font" messages in the console.
2026-03-23 09:03:57 +01:00
Andreas Kling
76298a7f0c LibWeb: Only switch FontFaceSet to loaded when a loading font is removed
FontFaceSet::delete_() was calling switch_to_loaded() whenever the
loading fonts list was empty after removing a font, even if the font
was never in the loading list to begin with. Per spec, we should only
switch to loaded if the font was the last item in the loading list.
2026-03-23 09:03:57 +01:00
Davi Gomes
6d77c9edd1 Libraries: Move #pragma once above include headers
The #pragma once was placed after the #include directives instead of
immediately after the copyright comment, inconsistent with every other
header file
2026-03-22 14:05:44 +01:00
Andreas Kling
3cda08b6bf LibWeb: Numerically interpolate rotation angles for same-axis rotations
Per the CSS Transforms spec, when interpolating rotate3d() functions
with equal normalized direction vectors (or when one angle is zero),
the rotation angle should be interpolated numerically rather than
using quaternion slerp.

Previously we always used quaternion slerp, which cannot represent
rotations beyond 360 degrees. This meant that animating from
rotateY(0deg) to rotateY(3600deg) produced no visual animation, since
both quaternions are identical (3600 mod 360 = 0).

Now we detect when axes match and interpolate the angle directly,
correctly preserving multi-turn rotations. This fixes 168 WPT tests.
2026-03-21 23:16:32 -05:00
Andreas Kling
42bf301acd LibWeb: Apply animation-timing-function per keyframe interval
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
2026-03-21 23:16:32 -05:00
Andreas Kling
6d6e15f012 LibWeb: Look up @keyframes rules from shadow root stylesheets
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.
2026-03-21 23:16:32 -05:00
Andreas Kling
36e05884f4 LibWeb: Fall back to discrete interpolation for non-invertible matrices
Per the CSS Transforms spec, when one of the matrices for interpolation
is non-invertible (i.e. cannot be decomposed), the animation must fall
back to discrete interpolation.

Previously we would silently drop the transform, producing "none". Now
we correctly snap between the from/to values at the 50% progress mark,
matching the behavior required by the spec and other browsers.

Also propagate to_matrix() errors instead of silently using a partial
result, and use the previously-unused AllowDiscrete parameter.
2026-03-21 23:16:32 -05:00
Andreas Kling
63748758ea LibWeb: Compute float to none for absolutely positioned elements
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().
2026-03-21 21:42:44 -05:00
Andreas Kling
223ca14abc LibWeb: Support :host::part() selectors within shadow DOM stylesheets
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
2026-03-21 21:42:44 -05:00
Andreas Kling
d1acb6e157 LibWeb: Fix :host() descendant matching for nested shadow hosts
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.
2026-03-21 21:42:44 -05:00
Andreas Kling
6eeafd3d7a LibWeb: Support ::slotted() matching through nested slot chains
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.
2026-03-21 21:42:44 -05:00
Andreas Kling
0c58a6f322 LibWeb: Use slot's shadow host when matching ::slotted() selectors
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.
2026-03-21 21:42:44 -05:00
Andreas Kling
717f18909b LibWeb: Handle trailing whitespace in display value parsing
When a var() fallback value contained trailing whitespace (e.g.
`var(--foo, flex )`), parse_display_value() miscounted the tokens.
The remaining_token_count() check included whitespace tokens, routing
single-keyword values like "flex" to the multi-component path. The
multi-component parser then failed when encountering the trailing
whitespace token.

Fix this by counting only non-whitespace tokens for the single vs
multi-component routing decision, and by skipping whitespace in the
multi-component parsing loop.
2026-03-21 21:42:44 -05:00
Andreas Kling
bc3bd28378 LibWeb: Use Newton-Raphson for cubic-bezier easing evaluation
Replace the previous caching/binary-search approach with
Newton-Raphson iteration and bisection fallback. This is the
same algorithm used by WebKit, Chromium, and Firefox.

The old code had a broken binary search comparator that could never
return 0 (the second condition was always true when the first was
false), leading to out-of-bounds vector accesses and crashes.

Fixes #3628.
2026-03-21 18:21:31 -05:00
Andreas Kling
eb789e790e Everywhere: Use AK::SaturatingMath and remove Checked saturating APIs
Port all callers of Checked<T>::saturating_add/sub/mul to the new
standalone functions in AK/SaturatingMath.h, and remove the old
APIs from Checked.
2026-03-21 18:20:09 -05:00
Andreas Kling
be56df8d4f LibWeb: Handle guaranteed-invalid values in animation keyframes
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.
2026-03-21 08:41:13 -05:00
Jelle Raaijmakers
7fed3f9801 LibWeb: Start fetching CSS image resources before document load event
Both Chromium and Gecko delay the document's load event for CSS image
resource requests (background-image, mask-image, etc). We now start
fetching CSS image resources as soon as their stylesheet is associated
with a document, rather than deferring until layout. This is done by
collecting ImageStyleValues during stylesheet parsing and initiating
their fetches when the stylesheet is added to the document.

Fixes #3448
2026-03-21 10:29:54 +01:00
Rob Ryan
d9f909c911 LibWeb: Add label cursor default CSS to match Chromium and Gecko 2026-03-21 07:40:25 +00:00
Rob Ryan
1b508c4e42 LibWeb: Remove label default inline-block style
Let labels fall back to their default inline display so empty
labels do not create extra line height above following
block content.

Add a regression for the empty-label case and update the
file input layout expectation for the UA shadow label.
2026-03-21 07:40:25 +00:00
mikiubo
71c4bb1aa8 LibWeb: Support border-radius in clip-path: inset()
Implement support for the 'round' radii in 'clip-path: inset()'
by resolving and normalizing corner radii and generating a path
with elliptical arcs.

Add a screenshot test.
2026-03-21 02:44:49 +00:00
mikiubo
b5e90e0350 LibWeb: Remove outdated FIXME about path() in basic-shape parser
Remove a FIXME stating that path() was not implemented in
basic-shape parsing, as it is now supported.
2026-03-21 02:44:49 +00:00
Callum Law
5b07dcbd5d LibWeb: Respect @font-feature-values rules 2026-03-20 16:08:32 -05:00
Callum Law
4086e053ae LibWeb: Use FlyString for key in to_shape_features
This fixes an issue where we couldn't use `String::formatted` within
lambdas
2026-03-20 16:08:32 -05:00
Callum Law
31b5307bec LibWeb: Respect font-variant-alternates: historical-forms 2026-03-20 16:08:32 -05:00
Callum Law
8fb55a31a9 LibWeb: Pass @font-feature-values data when resolving shape features 2026-03-20 16:08:32 -05:00
Callum Law
02c572c21e LibWeb: Clear font cache when @font-feature-values rule modified
We now clear the computed font cache whenever a `@font-feature-values`
rule is added, removed, or has one of it's descriptors modified.

This isn't observable yet since we don't actually respect
`@font-feature-values` rules, but that will come in a later commit
2026-03-20 16:08:32 -05:00
Callum Law
dae2f4df0d LibWeb: Clear rule caches before setting parent rule/stylesheet
In a later commit the caches to clear will be stored against a document
so the rule will require still having reference to that document to
clear it.

Also take this opportunity to mark `set_parent_style_sheet` as
`MUST_UPCALL`
2026-03-20 16:08:32 -05:00
Callum Law
cc5d1a9b4d LibWeb: Rename did_load_font to clear_computed_font_cache
This will be used in cases other than font loading in the future (i.e.
changes to `@font-feature-values` rules)
2026-03-20 16:08:32 -05:00
Callum Law
2d0144f265 LibWeb: Include font-variant-alternates functions in FontFeatureData 2026-03-20 16:08:32 -05:00
Johan Dahlin
dfe5d009d0 LibWeb: Fix CSS style computation crashes on detached documents
Replace VERIFY assertions with fallbacks in Length::for_element()
when computed_properties or root element is null. Guard
inheritance_parent->computed_properties() in StyleComputer.
2026-03-20 15:56:50 -05:00
Jelle Raaijmakers
5bffb5e003 LibWeb: Prevent CascadedProperties churn for pseudo elements
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.
2026-03-20 19:33:06 +01:00
Callum Law
6f226f3d2e LibWeb: Add missing AD-HOC comment to parse_family_name_value 2026-03-20 14:06:39 +00:00
Tim Ledbetter
c173a66754 LibWeb: Avoid style computation if document is detached 2026-03-19 14:17:46 +01:00
Callum Law
915fc4602b LibWeb: Implement CSS inherit() function
The remaining failing imported tests are due to wider issues which are
covered by FIXMEs (both existing and added in this commit)
2026-03-19 10:25:37 +01:00
Callum Law
2300ba41fb LibWeb: Use generic <color-interpolation-method> parsing for color-mix
This also fixes an issue where we would allow arbitrary idents as color
spaces
2026-03-18 13:21:57 +00:00
Callum Law
9db607b1a7 LibWeb: Use generic <color-interpolation-method> parsing for gradients
See previous commit for details

We now support parsing of `display-p3-linear` (although it just falls
back to using Oklab since Skia doesn't support it)
2026-03-18 13:21:57 +00:00
Callum Law
d8c38a294c LibWeb: Add a generic <color-interpolation-method> parsing method
Previously we had two implementations for parsing
`<color-interpolation-method>`, one for gradients and one for
`color-mix()` - this commit adds another which will unify the existing
ones in following commits.

This implementation has a couple of advantages over the existing ones:
 - It is simpler in that it uses global CSS enums and their helper
   functions
 - It is spec compliant (unlike the `color-mix()` one which allows
   arbitrary idents)
 - It parses as a `StyleValue` which will be required once we support
   `<custom-color-space>` since that can be an `ident()` which isn't
   resolvable at parse time
2026-03-18 13:21:57 +00:00
Zaggy1024
44ed698d4f LibWeb: Separate the active element and the element being activated
We were conflating elements being the active element and elements being
activated. The :active pseudo class is supposed to be based on whether
an element will have its activation behavior run upon a button being
released.

Store whether an element is being activated as a flag that is set/reset
by EventHandler.

Doing this allows label elements to visually activate their control
without doing a weird paintable hack, so the Labelable classes have
been yeeted.
2026-03-17 04:01:29 -05:00
Psychpsyo
80a0a39b79 LibWeb: Add scroll-behavior CSS property
To quote the spec:
"User agents may ignore this property."
So that is what we do.
2026-03-16 08:44:50 +00:00
Shannon Booth
e555edd770 LibWeb/Bindings: Implement callback interface object bindings
Generate correct bindings for callback interfaces: only create an
interface object when the interface declares constants, and set up
the prototype correctly.

This also lets us tidy up some IDL for these callback interfaces.
2026-03-11 21:16:44 +01:00
Tim Ledbetter
8179efb38e LibWeb: Respect box-sizing value when getting width/height used value 2026-03-11 11:33:25 +01:00
Aliaksandr Kalenik
9d2ebe90ed LibWeb: Store visual context nodes in arena-based tree
Replace per-node heap-allocated AtomicRefCounted
AccumulatedVisualContext objects with a single contiguous Vector inside
AccumulatedVisualContextTree. All nodes for a frame are now stored in
one allocation, using type-safe VisualContextIndex instead of RefPtr
pointers.

This reduces allocation churn, improves cache locality, and opens the
door for future snapshotting of visual context state — similar to how
scroll offsets are snapshotted today.
2026-03-11 11:16:36 +01:00
Aliaksandr Kalenik
d8b8a7d3f3 LibWeb: Mark only font-using elements for style update on font load
Instead of calling invalidate_style(CSSFontLoaded) which marks the
entire subtree for style recomputation, use set_needs_style_update(true)
to mark only individual elements that reference the loaded font family.

This is correct because element_uses_font_family() checks the computed
(inherited) font-family value, so descendants inheriting the font will
match individually, while descendants that override font-family to a
different font are skipped entirely.
2026-03-11 02:10:15 +01:00