`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
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).
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.
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.
The #pragma once was placed after the #include directives instead of
immediately after the copyright comment, inconsistent with every other
header file
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.
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 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.
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 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.
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.
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.
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
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.
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.
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
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`
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.
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)
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
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.
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.
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.
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.