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.
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.
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.
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.
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.
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.
@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.
@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.
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.
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.
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.
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
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.
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.
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.
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.
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()`.
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.
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.
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.
Previously, presentational hints bypassed the regular cascade pipeline
and wrote directly into `CascadedProperties` under
`CascadeOrigin::Author`. That meant `var()` substitution and the
invalid-at-computed-value-time fallback had to be duplicated in a
separate per-element pass, which in practice missed the IACVT step and
could leave a `GuaranteedInvalidStyleValue` in the cascaded
properties. This caused a crash in downstream code that assumed the
value had been resolved.
This introduces an `AuthorPresentationalHint` cascade origin and feeds
them through the cascade as normal declarations. This means that
`var()` resolution now happens in only one place.
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.
Avoid repeated :has() child-list scheduling when pending data already
covers every concrete feature bucket used by :has() selectors in a style
scope. Featureless-sensitive scopes record child-list mutations
conservatively instead.
That conservative path avoids scanning mutation subtrees for concrete
features when invalidation cannot rely on them anyway. When loading
the Intel ISA PDF in pdf.js, instrumented subtree feature-collection
visits at about 40k style invalidations dropped from around 71k to 1.6k,
saving ~650ms of main thread time on my Linux machine. :^)
Collect concrete features from pending :has() mutations and use them to
avoid re-invalidating non-subject :has() anchors whose matching rules
cannot be affected by the mutation.
Keep conservative behavior for shadow-boundary fanout, structural
sibling changes, and selectors whose relevant features cannot be proven.
For sibling-combinator relative selectors, avoid marking an anchor as
handled until it is actually invalidated, and make the sibling scan
respect anchors skipped by the feature filter for the same mutation.
Keep StyleScope responsible for storing pending :has() mutation state,
but move the invalidation walk and scheduling helpers into
CSS::Invalidation::HasMutationInvalidator. This keeps the style scope
from owning the :has() invalidation algorithm directly and gives later
changes a narrower place to optimize.
Add --dump-style-invalidation-counters=N to Ladybird and propagate it
to WebContent helper processes.
When enabled, WebContent dumps the current document style invalidation
counters with dbgln() after every N recorded style invalidations. This
makes it possible to collect the counters while browsing without adding
temporary C++ logging.
IFrame geometry changes and object representation changes directly
selected style invalidation reasons from their HTML element classes.
Move those mappings into a new
CSS::Invalidation::EmbeddedContentInvalidator.
The HTML elements continue to own their loading, representation, and
layout-tree side effects. CSS invalidation now owns the style dirtiness
associated with those embedded-content changes.
Element::set_shadow_root directly selected the style invalidation reason
used when a shadow root changes. Move that mapping into
CSS::Invalidation::ElementStateInvalidator.
Element still owns the shadow-root state transition and the required
layout-tree invalidation. CSS invalidation now owns the style dirtiness
for that state change.
Element::set_being_activated directly selected the style invalidation
reason used for :active changes. Move that mapping into
CSS::Invalidation::ElementStateInvalidator.
The element continues to own its activation state. CSS invalidation now
owns the style invalidation work associated with changing that state.
CustomStateSet directly selected the style invalidation reason used when
its JS-visible set is modified. Move that mapping into
CSS::Invalidation::CustomElementInvalidator.
This keeps the custom-state container focused on its set contents while
CSS invalidation owns the style work required by :state() selectors.
HTMLInputElement still mapped its picker open-state change directly to a
style invalidation reason. Move that mapping into
CSS::Invalidation::ElementStateInvalidator alongside the matching select
open-state helper.
This keeps another element-state invalidation decision out of the HTML
implementation without changing the invalidation behavior.
Several HTML element state changes directly selected style invalidation
reasons from their element implementations. Move those mappings into a
new CSS::Invalidation::ElementStateInvalidator helper.
This keeps details, dialog, option, and select code focused on their own
state changes while CSS invalidation owns the style work those changes
require. The existing invalidation breadth is preserved.
Document.cpp still flushed pending :has() invalidation by walking the
document and shadow-root style scopes directly. Move that CSS-specific
flush into CSS::Invalidation::HasMutationInvalidator.
Document continues to own the flag that says a :has() flush is needed.
The helper now owns the style-scope work needed to invalidate elements
affected by pending :has() mutations.
Document.cpp still handled CSS fallout from stylesheet media query match
changes directly. Move active stylesheet evaluation, rule-cache
invalidation, shadow-root fallout, and slot propagation into the CSS
invalidation helper.
Document continues to decide when media queries should be evaluated.
The helper now owns the style invalidation consequences when stylesheet
media queries change match state.
Document.cpp still knew how style changes on a slot propagate to
assigned light-DOM nodes. Move that flat-tree inheritance invalidation
into CSS::Invalidation::SlotInvalidator.
The style update walk continues to decide when an element's style
changed. The helper now owns the ::slotted() consequence of dirtying
assigned slottables for a changed slot.
AdoptedStyleSheets.cpp still handled CSS-side fallout from adopted sheet
list mutations directly. Move sheet attachment and media-query setup to
a CSS invalidation helper, along with rule-cache invalidation.
The observable array callbacks continue to validate JS values and
same-document construction rules. The helper now owns the CSS work for
when a constructed stylesheet becomes visible to, or is removed from, a
Document or ShadowRoot.
Slottable.cpp still handled the style invalidation fallout from slot
assignment changes directly. Move that ::slotted()-related policy into
CSS::Invalidation::SlotInvalidator.
Slot assignment remains DOM bookkeeping. The helper now owns the
choice to dirty element slottables when they gain or lose assignment
to a slot.