Use GC::WeakHashSet for the registry instead of IGNORE_GC. The notify
and layout update paths now snapshot the live weak entries into
RootVector before iterating, preserving the existing protection against
mutation while avoiding an unvisited strong GC container field.
Request a rendering update after document visibility changes back to
visible. Hidden documents can leave animation frame callbacks or CSS
animation work pending after their last rendering update is skipped,
so showing the page needs to schedule a fresh rendering tick.
Async scrolling display-list items were still recorded by helper
functions in LibWeb/Compositor even though they are produced while
walking the paint tree. That kept paint-time knowledge about boxes,
viewport state, and wheel-event regions in the compositor-facing code.
Move viewport setup and final metadata emission to ViewportPaintable,
and move per-box scroll-node, hit-test, scrollbar, wheel-region, and
sticky-area item recording to PaintableBox. AsyncScrollingState now
keeps the compositor-side conversion and lookup logic for consuming the
recorded metadata.
Notify the UI process with the solid canvas background color recorded
for the top-level document. This is the Canvas system color with the
effective document background composited over it, matching the color
used before normal painting.
Store that color on the view and use it when AppKit, Qt, and Gtk need
to fill areas exposed while an older bitmap is still on screen during a
window resize.
The layout loop added for container queries intentionally caps repeated
style/layout passes, since each layout can enqueue more size-query style
work. That cap should still apply, but absence of post-layout style work
does not by itself mean the document is clean.
Only leave the loop when there is no pending post-layout style work and
layout is up to date. Otherwise spend another capped pass flushing the
layout-only invalidation instead of falling through to the final
layout_is_up_to_date() verification.
Add crash coverage for a clientHeight read after nested container query
invalidations, matching the synchronous layout flush path.
When an embedded document was render-blocked, the parent display list
recording skipped drawing that iframe's compositor surface. Once the
child document later unblocked, only the child surface was repainted, so
the parent could keep replaying a display list that had no surface draw
command until another invalidation, such as resize, forced a recording.
Invalidate the containing iframe when a document's render-blocking set
becomes empty so the parent display list records the child surface. Add
a deterministic reftest that gates a render-blocking stylesheet on a
test-server signal, forcing the parent to paint the blocked iframe
before unblocking it.
Preserve leading BOMs when parsing already-decoded HTML strings, since
those strings do not go through the encoded byte decoder path.
Decoded markup from JS strings can also contain WTF-8 for lone surrogate
code units. Keep the common scalar UTF-8 path to a single validation and
copy, but replace surrogates before handing bytes to the Rust tokenizer.
Add text coverage for DOMParser and innerHTML string parsing, including
leading BOMs, text and attributes, lone high and low surrogates, and a
valid surrogate pair.
Do not build content blocker cosmetic style sheets for decoded SVG
image documents. These documents are resource documents created for
image painting rather than ordinary navigable documents, and other
engines keep extension cosmetic CSS out of them.
Add text coverage that verifies a generic cosmetic rule still applies
to the embedding HTML document while a matching element inside an SVG
loaded through <img> keeps rendering.
Refresh content blocker styles when connected elements gain or change
class or id tokens that can affect generic cosmetic selectors. Cache the
class and id tokens covered by each document's content blocker
stylesheet, then ask the adblock engine whether newly-added tokens can
unlock selectors before invalidating user style for the whole page.
This keeps dynamic cosmetic hiding correct without refreshing blocker
CSS for unrelated class and id mutations.
Add text coverage for irrelevant class/id mutations that should preserve
author style invalidation without refreshing blocker CSS. Also prove
matching tokens still hide elements.
Replace PseudoClassInvalidator's subtree scan with targeted
invalidation for elements whose pseudo-class state changes. This scopes
state changes to affected selectors instead of rechecking a whole
common-ancestor subtree.
Use the ancestor chain that matches each pseudo-class. Hover walks the
shadow-including chain. :focus-within walks the flat-tree chain, so
slotted content invalidates its assigned slot and relevant shadow-tree
descendants. Focus, FocusVisible, and Target invalidate just the state
node.
Route each affected element through Element::invalidate_style with the
pseudo-class property. This uses the same invalidation-plan machinery as
Disabled, Checked, and other pseudo-class state changes.
Interaction state pseudo classes are not tracked in :has() metadata, so
schedule :has() ancestor invalidation explicitly when the state flips.
The callers no longer need cross-scope branching. The chain walk handles
shadow boundaries, and property invalidation already visits every
observer style scope.
Build the user stylesheet only for document style scopes, since user
rules are already considered relevant across shadow boundaries during
rule matching. This avoids regenerating and reparsing the same cosmetic
content blocker stylesheet for every shadow root in the document.
Keep the generated cosmetic stylesheet cached on the Document and clear
it whenever user style is invalidated, so content blocker changes still
produce fresh CSS for the next style update.
The base URL change handler checked whether any style scope contained
a `:local-link` rule before walking the document's links, this forced
a rule cache rebuild, which is generally slower than the link walk we
were trying to avoid.
On Speedometer2 the duplicated rule-cache and invalidation-set
construction accounted for roughly 4% of total samples. Removing the
gate lets the URL-unchanged early-exit handle the no-op case and runs
the link walk only when the URL actually changes, which the profile
showed to be inexpensive on its own.
Represent WebIDL C++ types with a single CppType model that tracks
nullability, optional presence, and contained storage.
GC-like values now use GC::Ref/GC::Ptr directly, while containers choose
"plain", "Root", or "Conservative" container types depending on what
they contain. For example, sequence<Element> becomes a RootVector of
GC::Ref values, while sequence<SomeDictionary> becomes a
ConservativeVector only when the dictionary contains GC-like values.
This moves the generated bindings away from wrapping GC values in
GC::Root by default.
This has broad fallout as the types passed to interfaces for GC
objects changes almost fully across the board.
Keep animated ImageStyleValue frame advancement owned by the
style value. The current frame and loop state live there, so a
separate document scheduler would duplicate ownership of that state.
Start the ImageStyleValue timer only while it has layout clients.
Stop it when the last client unregisters, or when a finite animation
completes. Expose a document-scoped active timer count through
internals for focused regression tests.
Clear image observers when layout nodes detach. Use current-node
cleanup for per-DOM-node clearing, and explicit subtree cleanup for
tree replacement, full tree teardown, and synthetic pseudo-elements.
This keeps large document clearing linear.
Unregister generated-content image providers during layout detach
instead of waiting for GC to finalize the provider.
Cover hidden animated background images, generated content images,
layout node replacement, full layout tree teardown, and document
scoping for the internals counter.
Previously, the fully active callback could fire when the document was
never actually deactivated, which could cause a verification failure in
HTMLMediaElement due to its assumption that the callbacks only fire
when a document's fully active state genuinely changed.
Add a targeted update_style_for_element() mode that resolves one
flat-tree inheritance chain. Normal callers still get computed style in
display:none subtrees. Focusability stops when that chain resolves to
display:none.
The targeted path falls back to normal document style traversal for
document invalidation, full rebuilds, and selector work. That keeps
global dirty state consumed atomically. Local recomputation preserves
layout, display-list, slot, visual-context, and stacking-context
invalidation. It marks children dirty when a recomputed element can
affect descendant style.
Use StopAtDisplayNone for focusable-area rendering checks so incidental
is_focusable() queries do not repeatedly force unrelated document style
work. Rebaseline style invalidation counters for the reduced
getComputedStyle() recomputation scope.
Factor the element-level and document-level side effects from style
recomputation into helpers used by normal document style traversal. This
keeps existing behavior unchanged while letting the later targeted style
update path reuse the same invalidation bookkeeping.
Follow the spec reuse of the initial same-origin about:blank window for
a browsing context's first real navigation. This fixes a crash in
promise-job-entry-different-function-realm.html by preserving the
correct iframe realm.
However, reusing the initial about:blank Window means
create-and-initialize can associate that Window with the pending
Document before session history activation has made the Document active.
Treating a browsing context's active document as its active Window's
associated Document therefore exposes the pending Document too early.
To fix this, add an explicit active document slot to BrowsingContext and
update it when a Document is made active.
A similar fix was attempted in 7fc7263a4d,
but that version still queued navigation tasks through the reused active
Window, tagging them with the pending Document before it was active.
This caused parser-created iframe loads to hang in encoding WPTs. This
commit instead queues those navigation-internal tasks against the
navigable's currently active Document.
The newly added iframe-initial-load-chunked-body.html mimics the same
type of failure seen by the WPT regressions mentioned above.
The spec doesn't say what time to use here but in other places where we
schedule animation events it says to use "the result of applying the
procedure to convert timeline time to origin-relative time to the
current time of the timeline with which animation is associated", so we
do that here as well.
This time is stable per animation frame per timeline so we now correctly
fall back to composite ordering of animation/transition events where we
previously wouldn't (since the scheduled time was so precise that it was
always unique), which causes the imported test to no longer be flakey.
Problem: An iframe whose content changes while it (or an ancestor)
has visibility:hidden isn’t painted once it becomes visible again.
The stale previous frame stays on screen until an unrelated
repaint (e.g., window resize) happens to occur.
Cause: set_needs_repaint() returns early for any document inside an
iframe with a visibility:hidden ancestor — discarding the request
entirely. The navigable’s needs_repaint flag is never set. Nothing
sets it again when the iframe becomes visible — so the rendering
loop keeps skipping it — and its display list stays stale.
Fix: Stop discarding the request in set_needs_repaint(). Instead,
skip painting hidden navigables in the rendering loop — while
leaving needs_repaint set. Once an ancestor iframe becomes
visible, the still-set flag makes the rendering loop paint the
navigable on the next frame.
Fixes https://github.com/LadybirdBrowser/ladybird/issues/9305
Track explicit inherit of non-inherited properties only on the direct
parent shadow root. A deeper descendant with margin-left: inherit still
inherits from its own parent, so a host margin change does not require
marking every ancestor as possibly affected.
Extend the shadow-root inherited style test to cover both the direct
child case that must still update and the deeper descendant case that
must not trigger broad inherited-style recomputation.
Make style updates reach a fixed point when slot invalidation dirties
assigned nodes after their traversal point. During inherited-style
cascades, only the topmost changed element scans for descendant slots.
Animation inherited-style updates now include the target slot and walk
shadow-including descendants, so host animations propagate inherited
values through shadow trees and assigned slottables. Animated inherited
longhands also carry the same inherited-style invalidation signal as
regular style changes.
Mark custom elements dirty when their :defined state flips, so upgraded
elements do not keep stale :not(:defined) computed style. Add coverage
for slotted menu invalidation, descendant-slot scan counts, target slot
animations, host shadow-tree animations, and explicit inherit from an
animated non-inherited longhand.
Track when style recomputation may require inherited-style work in a
shadow tree, and use that signal when crossing from a shadow host into
its shadow root. Shadow descendants can explicitly inherit normally
non-inherited host properties, so any host style change may need
inherited-style recomputation there.
Use the CSS property definition to tell whether changed longhands need
to propagate to shadow descendants. Do the same after recomputing
inheritance-dependent values, such as host font-size: 2em after a parent
font-size change.
The shadow DOM tests cover inline and class-driven inherited host style
changes, explicit inherit for non-inherited host properties, relative
units on hosts, and nested slotted inheritance updates.
Install the inherited about base URL before setting the document URL so
fallback-base URL consumers see the correct source during URL-dependent
style and content blocker work.
This is used whereever we use `unsafe_layout_node`. There's no
difference in behavior for `SyntheticPseudoElement` but once we
implement element-reference pseudo-elements it will call
`unsafe_layout_node` on the referenced element rather than `layout_node`
Most of this functionality was already implicitly disallowed for
element-reference pseudo-elements by the fact that we weren't creating
entries in `m_pseudo_element_data` for them, but we need to explicitly
limit it in preparation of creating those entries.
Due to the above this is mostly non-functional apart from a regression
where we no longer support custom properties on element-reference
pseudo-elements. Previously when setting custom properties for an
element-reference pseudo-element we would call `ensure_pseudo_element()`
which created a synthetic pseudo-element entry distinct from the
referenced element which only stored custom property data - this was
clearly wrong and will be implemented properly in a future commit.
When adopting an element, we now set the node document of each of its
attributes to the new document. Without this, `Attr` objects retained
document pointers back to a temporary document created during fragment
parsing, these documents were then kept alive after the parsed nodes
were moved into the real document.
Apply the focusing steps' get-the-focusable-area mapping before
rejecting a non-focusable target. This preserves documentElement.focus()
by mapping the non-focusable document element to the Document viewport.
Also map rendered navigable containers with content navigables to their
active document, while leaving hidden containers unfocused. Preserve
Window focus events for child document viewports reached through iframe
focus, while still suppressing the top-level viewport surrogate events.
Treat rendered object elements as focusable through their default
non-null tabindex, even when they show fallback or image content instead
of a child navigable.
Keep the spec focus-chain common-tail handling intact for viewport
focus. The Document object is only our surrogate for the viewport, so
designate viewport focus from the new focus target without dispatching
Window focus/focusin events for that top-level surrogate.
Pass that viewport surrogate as the fallback target for fragment
scrolling and NavigateEvent focus reset, so unfocusable body or fragment
targets still clear stale element focus.
Cover documentElement.focus() in both the activeElement and focus-chain
tests, including a tabindex document element that remains focused as an
element. Cover object focus with and without a child navigable, hidden
object focus attempts, iframe focus events, hidden iframe focus, and
blurring a focused iframe after it becomes hidden. Also cover viewport
fallback for intercepted navigation focus reset and fragment scrolling
to an unfocusable target.
Similar to GC::Root<T>, make GC::RootVector<T> constructible without
explicitly passing a Heap.
This is implemented by having RootVectorBase use GC::Heap::the() for
heap-free construction.
Partial SVG relayout builds a throwaway layout state rooted at the
nearest dirty SVG viewport. SVG graphics layout can still walk past
intermediate SVG viewports when looking for ancestor transforms, so
pre-populate every SVG graphics ancestor instead of stopping at the
first ancestor SVG viewport.
Fixes an issue where the typing indicator would crash Discord when
someone starts typing a message. :^)
e.g., `@container (width >= 300px) {}` and similar.
During style computation, flag any elements whose style depends on a
size container. Then re-evaluate their style after the initial layout
has been computed and size containers have a size. This may take
multiple passes, as these may have further descendants that depend on
their size, etc. We limit this to 8 passes currently.
SizeFeature itself is very similar to MediaFeature, but queries the
container element instead. There are only 6 size features specified, so
they're hard-coded instead of generated from JSON.
Also add a counter test for the narrower restyle path.
Avoid recomputing boundary event offsets after dispatch has started.
Boundary event listeners can invalidate layout while set_hovered_node()
is still synthesizing the rest of the event batch, and transformed
targets need the document paintable's visual context tree for offset
calculation.
Compute every target-specific offset before firing the first boundary
event, while the paint tree from the platform hit-test is still intact.
Keep a defensive fallback for a missing viewport paintable when applying
transforms.
Add a UIEvents text test that opens a popover from pointerout and then
continues into a transformed target.
We now apply first letter styles by splitting text with a first-letter
style applied into 2 `TextSliceNode` objects. The
`DOM::Text` layout node always points at the non first-letter slice
and the first-letter slice is reachable via
`TextSliceNode::first_letter_slice()`.
First letter splitting works by `TreeBuilder` walking a block
container's inline descendants to find the first typographic letter
unit per the pattern given in css-pseudo level 4, which is then
wrapped in an anonymous inline box styled with the `::first-letter`
computed properties.
Consumers that map between DOM offsets and layout geometry
are updated to visit all slices of a `DOM::Text` through
`TextOffsetMapping`.
Recompute offsetX and offsetY for each synthesized mouse and pointer
boundary event target instead of reusing the offset for the original hit
target. Boundary event listeners on ancestors now observe coordinates
relative to their own targets.
Add a UIEvents text test that covers bubbling over/out events, enter
and leave events across multiple nested targets, partial exits into an
ancestor, and full exits to the test output element.
Set platform pointer events as primary mouse pointer events. This lets
pages recognize real mouse input from pointermove before they install
mouse tracking behavior.
Also thread platform mouse coordinates through hover target changes.
This makes mouseover, mouseout, mouseenter, mouseleave, and matching
pointer boundary events trusted and gives them the coordinate data from
the originating platform mouse event.
Cover both paths with UIEvents text tests.
Descendants of a display:none element are not rendered and their
computed style is only observable through on-demand reads. Skip the
recursive style descent at display:none ancestors during the
top-down traversal in `update_style_recursively()`.
Keep the temporary document used by HTML fragment parsing from
running post-connection work while the parser is staging nodes there.
This lets scripts from Range.createContextualFragment() remain
unstarted until the returned fragment is inserted into the real
document, and removes the script-specific preparation guard.
Strengthen parser coverage so contextual fragment scripts must wait
until the fragment is applied before running.
document.close() can defer script-created parser cleanup while a
parser-blocking script is pending. If document.open() installs a new
parser before the old parser resumes, the deferred action must clean up
the parser that scheduled it instead of the document's current parser.
Capture that parser before installing the deferred action. This keeps
the parked cleanup from affecting a parser installed by a later
document.open() call.
Finish the Rust implementation of the spec tree-construction algorithms
needed by the LibWeb test suite. Add the remaining table modes, foster
parenting, scope helpers, adoption agency handling, ruby/list/form and
select cases, frameset state, foreign-content edge cases, and parser
host callbacks.
Preserve behavior that depends on the C++ DOM integration, including
parser-created custom element reactions, fragment quirks mode, arbitrary
fragment namespaces, template fragment mode, fragment form ownership,
MathML annotation-xml boundaries, contextual fragment scripts, parser
script source positions, document.close() parser state, void-element
insertion, and duplicate attribute tracking.
Add focused tests for the parser edge cases that are easy to regress at
the boundary between the Rust tree builder and the C++ DOM host.
Animation updates need a rendering opportunity so current time can
advance and effects can update computed properties. They should not mark
the document as needing paint before any animated value changed.
Request a frame for pending animation style updates. The existing
AnimationUpdateContext invalidation will decide whether layout or paint
work is needed. This avoids presenting unchanged frames for infinite
animations on pages like x.com.
Prune decoded image resources retained by active documents once they
grow beyond a recent working set. Route-heavy applications can load
many unique images through one Document, and the shared resource and
available-image caches would otherwise keep decoded image data alive
after the DOM stopped using those images.
Track cache touches and evict least recently used decoded images from
both caches. This keeps active documents from accumulating unbounded
decoded image resources while preserving a small hot cache for repeat
loads.
Display lists used to own the resource storage needed to replay their
command bytes. That kept the compositor tied to in-process object
ownership: sending a display list update also meant sharing the same
resource container with the recording side.
Move resource storage out of DisplayList and make display list updates
carry a transaction of resources to add and remove. Navigable now tracks
the resources referenced by the current display list, sends only the
delta to the compositor, and trims its recording-side storage to the
active set. The compositor applies those transactions to its own storage
before replacing the cached display list.
This still carries in-process resource objects, but it puts the
ownership boundary in the right place. Command bytes and resource
lifetime are now synchronized explicitly, which is the shape needed
before the compositor can receive serializable resource updates across a
process boundary.
Store ResizeObservation targets weakly, matching Blink and WebKit. A
ResizeObserver can be kept alive by the document while it has observed
targets, but the observation itself should not keep a removed target and
its subtree alive forever.
Prune dead observations before gathering active resize observations.
Snapshot the document observer list before pruning so unregistering an
observer cannot mutate the intrusive list being iterated. Keep gathered
active targets rooted while broadcasting callbacks, since an earlier
callback can remove a later target and trigger GC before delivery.
Expose an internals helper to force environment-bound test objects to be
treated as garbage. Add text coverage for both pruning a dead observer
during gather and GC during an earlier resize observer callback.
Remove every element from Document's potentially named element cache
when its id-bearing element is removed from the document. Dynamic id
changes add all elements to this cache so Window named properties can
observe them, but removal only pruned object and image elements. This
left ordinary removed elements, such as divs, strongly reachable from
the active document and kept their subtrees alive.
When an ancestor's custom property changes, intermediate elements that
don't themselves consume `var()`/`inherit()` were skipped during style
recomputation, leaving their `m_custom_property_data` pointing at the
ancestor's stale `CustomPropertyData`. Deeper descendants that did
consume `var()` then resolved against the out-of-date chain.
This change adds `Element::refresh_inherited_custom_property_data()` to
re-link an element's inherited chain to its parent's current data
without re-cascading. This is called during `update_style_recursively()`
for elements that don't need a full recompute.
Use compositor hit-test commands in the display list to rebuild async
wheel targets, and serialize the async scroll metadata needed to
reconstruct AsyncScrollingState from the same display-list snapshot.
Driving the async scroll tree off the display list rather than a
separately collected tree has a few benefits:
- No additional full paintable tree traversal is required, since the
information needed by the compositor is gathered while recording
the display list.
- The display list is already serializable, so the async scroll tree
no longer needs its own serialization path.
- It is more debuggable, as the existing display list dump now also
covers the data used to reconstruct the async scroll tree.
- In the future we will want to include other areas that can
interfere with hit-testing; recording them during display list
construction makes it straightforward to preserve a hit-testing
order that matches painting order.
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.
When nothing invalidates the display list between frames, push only an
updated scroll state snapshot to the rendering thread instead of
handing it the display list again.
This is preparation for moving rendering to a separate process, where
sending the display list across the process boundary on every frame
would be expensive.
Use GC::Timer instead of Core::Timer for the cursor blink timer so
that capturing [this] in the callback properly protects the Document
from garbage collection.
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.
These steps need to be run whenever update_current_time is called and in
a future commit that will be more than just the one place currently.
This also removes the early return in `set_current_time` if the new
`current time` is the same as the old one, since we want to update
animations regardless (e.g. to run pending tasks)
Replace Vector<Variant<...>> display-list storage with a contiguous byte
buffer of command headers, trivially-destructible payloads, and inline
data spans. Variable-size data such as glyphs, paths, dash arrays,
gradient stops, and nested command bytes is appended next to the command
that references it.
The flat representation is prep work for sending display lists over IPC
to a dedicated rasterization process, which the previous Variant-based
structure could not support directly. It also avoids walking command
destructors when a display list is discarded and reduces per-command
allocation and indirection, improving memory use and data locality.
Commands used to keep rendering resources directly in the variant:
image frames, external content and video sources, filters, SVG paint
styles, and nested display lists. That makes the command stream own its
dependencies and prevents it from becoming a POD-like byte buffer.
Add DisplayListResourceStorage and replace those command fields with
stable resource IDs. The storage deduplicates resources by their
existing IDs and can copy only the resources referenced by a captured
command sequence, giving the future IPC boundary a clear list of
resources that must be communicated to the rasterization process.
Follow the spec reuse of the initial same-origin about:blank window for
a browsing context's first real navigation. This fixes a crash in
promise-job-entry-different-function-realm.html by preserving the
correct iframe realm.
Making this change also requires another AD-HOC workaround to defer
updating the reused window's associated document until activation,
since doing so during document creation makes the browsing context's
active document change too early.
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.
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.
When the update intersection observations steps run (HTML rendering
update step 19), the algorithm calls Element::get_bounding_client_rect()
on each observed target and on element-typed roots. That path always
calls update_layout_if_needed_for_node() before reading the paintable
rect.
By the time step 19 runs, layout is already up to date: step 16 has
just laid out the document, and the preamble of step 19 itself flushes
any post-step-16 invalidation with a single update_layout call. So each
per-target update_layout call inside getBoundingClientRect is a
guaranteed no-op early-exit.
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.
Previously a fixed-rate paint refresh timer kept queueing rendering
update tasks at the maximum configured frame rate, regardless of whether
anything had actually changed. This wasted CPU on idle pages, which
spend most of their time in a steady state where no style, layout, or
paint work is needed.
Replace the repeating timer with a single-shot frame timer driven by
PageClient::request_frame(). A rendering update is now scheduled only
when something requires one. The configured maximum frame rate is
preserved as a ceiling on how closely consecutive frames can follow each
other.
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.
Document.cpp contained the CSS rule-cache matching used to decide which
elements need style updates when hover, focus, or target state changes.
Move that logic into CSS::Invalidation::PseudoClassInvalidator.
Document still owns the current state slots and chooses when a state
transition happens. The helper now owns the selector matching and
recursive invalidation pass for those pseudo-class transitions.
StyleInvalidator applies CSS invalidation plans and matches selector
features while walking DOM nodes. Move the class from DOM into the
CSS::Invalidation namespace alongside the other invalidation helpers.
Document still owns the invalidator and DOM nodes still expose the state
that gets marked, but the policy for applying invalidation plans now has
a home with the rest of the CSS invalidation code.
Previously, `Document::notify_css_background_image_loaded()` walked the
entire `PaintableBox` subtree and cleared each box's paintable cache
whenever any CSS image finished loading.
Replace this with per-image observers owned by the layout node. During
`apply_style`, each node registers as an `ImageStyleValue::Client` for
the images its style references. On load, only the affected layout
node's paintables are invalidated.
Add a ScriptCreatedParser flag plumbed through HTMLParser's constructor
and create_for_scripting(). Only document.open()'s parser sets it to
Yes. Document::close() step 3 now checks is_script_created() so it
correctly skips parsers that weren't created via document.open(),
matching the spec.
Previously the check was just `if (!m_parser)`, which incorrectly let
document.close() insert an EOF into a network-driven parser. The bug
was mostly latent because the network parser used to finish quickly,
but it matters once the network parser stays alive for the duration of
a streamed parse.
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.
Invalidate only the style scope whose media rules changed instead
of throwing away every shadow root rule cache whenever any active
stylesheet changes media query match state. Shadow-root stylesheet
changes still dirty the host side because :host and ::slotted
selectors can affect nodes outside the shadow tree.
When scoped invalidation leaves dirty descendants in a shadow root,
preserve the host ancestor chain so the document style update walk
reaches them before forced layout.
Add coverage that a matching media rule introduced in one shadow tree
does not broadly invalidate a page full of unrelated shadow roots,
and that a dirty shadow root is updated before layout is forced.
Replace the spin_until in SVGScriptElement::process_the_script_element
with an async fetch that mirrors HTMLScriptElement's mark_as_ready
pattern. External SVG scripts now fetch and execute asynchronously,
matching Chromium's behavior.
For HTML-embedded SVG scripts, the parser pauses via the existing
schedule_resume_check infrastructure, extended to support SVG scripts
through a new pending_parsing_blocking_svg_script slot on Document.
For top-level XML/SVG documents, scripts execute when their fetch
completes; the load event is delayed via DocumentLoadEventDelayer which
the existing XMLDocumentBuilder::document_end already waits on.
The HTML unload algorithm runs the old document's unloading document
cleanup steps before deciding whether the document can be destroyed.
Document::unload() still had that step as a FIXME, so cross-spec
cleanup could be skipped when test-web reused a view for navigation.
The flake depended on test order. The fullscreen sibling WPT requests
fullscreen for one element, then for its sibling, and finishes with that
second element still being the document's fullscreen element. test-web
then navigates the same view to the next test. Since unload skipped the
cleanup hook, the old document kept its fullscreen/top-layer state past
that document boundary.
form-reset-callback.html is deterministic when run by itself. The first
two subtests call the [CEReactions] form.reset() API and observe the
callback synchronously. The failing subtest calls resetButton.click(),
which reaches form reset through input activation behavior and expects
the formResetCallback reaction to run at the next microtask checkpoint.
With the reused view still polluted by the previous fullscreen document,
that checkpoint could run without observing the callback, so only that
subtest failed.
Run the unloading cleanup steps during unload so navigating away from a
fullscreen document clears that state before the next document starts.
When the HTML parser blocks on a synchronous external script, run a
separate tokenizer over the unparsed input and issue speculative fetches
for the resources it finds (script src, link rel=stylesheet|preload, img
src), with <base href> tracking and template/foreign-content skipping.
Also fills in the previously-stubbed "consume a preloaded resource"
algorithm and the document's "map of preloaded resources", so that
<link rel="preload"> followed by a matching consumer deduplicates to
a single fetch.
Spinning a nested event loop to wait for a parser-blocking script blocks
the calling thread, can deadlock, and creates reentrancy hazards. Switch
to an event-driven pause/resume model, mirroring the prior
HTMLParserEndState refactor (df96b69e7a).
Three WPT document.write tests flip from Fail to Pass and are
rebaselined: all write an external script via document.write() followed
by inline content. With spin_until, control did not return to the caller
of document.write() between writing the script and observing its effects
so the test's order assertions saw a different sequence than the spec
mandates.
Document::is_decoded_svg() was reached through two pointer hops and a
virtual call into PageClient on every invocation. It showed up at 1.9%
self time in a YouTube playback profile, and it's also called for every
document in the hot documents_in_this_event_loop_matching() loop that
runs on every rendering update.
The page's client is fixed for the lifetime of a document, so we can
cache the answer at construction time and serve future calls from a
plain member load.
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.
Previously, the LibWeb bindings generator would output multiple per
interface files like Prototype/Constructor/Namespace/GlobalMixin
depending on the contents of that IDL file.
This complicates the build system as it means that it does not know
what files will be generated without knowledge of the contents of that
IDL file.
Instead, for each IDL file only generate a single Bindings/<IDLFile>.h
and Bindings/<IDLFile>.cpp.
Element::matches() and Element::closest() were re-parsing the selector
string on every call. The document already maintains a parsed-selector
cache for querySelector/querySelectorAll.
This patch folds that cache's lookup, parse, namespace filtering and
insertion behind a Document::parse_or_cache_selector_list(string)
and calls it from all four entry points. We also bump the cache's
limit to get more hits.
Saves 100ms of main thread time when loading the "insights" view on
our GitHub repo on my Linux machine. :^)
IntersectionObserver updates already iterate over each observer and its
observation targets. We then looked the same target and observer pair up
again through Element's registered observer list just to read and write
previousThresholdIndex and previousIsIntersecting.
Store that mutable state with the observer-side observation target
instead. The element-side list now only keeps strong observer
references for lifetime management and unobserve/disconnect.
This deviates from the spec's storage model, so document the difference
next to the preserved spec comments.
This was a pretty straightforward change of storing registered counter
styles on the relevant `StyleScope`s and resolving by following the
process to dereference a global tree-scoped name, the only things of
note are:
- We only define predefined counter styles (e.g. decimal) on the
document's scope (since otherwise overrides in outer scopes would
themselves be overriden).
- When registering counter styles we don't have the full list of
extendable styles so we defer fallback to "decimal" for undefined
styles until `CounterStyle::from_counter_style_definition`.
IntersectionObserver can keep elements from a navigated iframe's old
document alive until a later rendering update. Once that document tears
down its layout tree, descendant nodes and pseudo-elements can still
retain stale layout and paintable pointers, and destruction can bypass
the usual inactive-document teardown entirely.
Clear per-node layout and paintable pointers across the inactive
document subtree before tearing down the layout tree, and do the same
from destroy() for documents that never go through
did_stop_being_active_document_in_navigable().
Add a crash test that observes an iframe target, navigates the iframe,
and waits for rendering updates without touching stale layout state.
Fixes#8670
...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.
This fixes a timeout in WPT's abort-in-initial-upgradeneeded.any.html
test. The timeout was a rare one, caused by idbfactory_open.any.html
leaving the second connection in the final test open, since support.js
only tracks the connection created by the first createdb call. By
leaving the connection open, the harness's deleteDatabase() call would
never take effect. This in turn meant that the upgradeneeded test would
fail an assertion on the number of databases. That assertion was also
uncaught by the harness, turning it into a timeout instead of a fail.
By closing the connections when a document is destroyed, we can ensure
that the connection doesn't leak over to the next test and cause the
exception to be thrown.
The visual context tree is expected to be non-null at all call sites.
Change the return type from raw pointer to reference with VERIFY(),
making the contract explicit and removing unnecessary null checks from
callers.
Previously, iframes were rasterized synchronously as nested display
lists inside their parent's display list: the parent's paint walk called
record_display_list() on each hosted iframe document and emitted a
PaintNestedDisplayList command that the player would recurse into. Only
the top-level traversable's RenderingThread was ever active, even though
every Navigable already owned one.
The motivation for splitting this apart:
- Work in the outer document no longer has to be re-recorded when only
an iframe changes. The parent's cached display list now references the
iframe's rasterized output live via an ExternalContentSource, so an
iframe invalidation just needs the parent's display list replayed, not
re-recorded.
- Each iframe now has a self-contained rasterization pipeline, which is
prep work for moving iframes into separate sandboxed processes.
Prep for rasterizing each Navigable independently, where children must
paint before their parents — the event loop needs to walk documents in
an order where every child comes after its container. The HTML spec
already mandates such an order for the "docs" list: each document
appears after its container, with siblings in shadow-including tree
order.
Maintain m_documents in that sorted order, re-sorting lazily when a
document is registered or its navigable is reassigned.
DOM pre-insertion validity allows processing instructions as
children of a document. However, Document::is_child_allowed()
still rejected them, so XML documents silently dropped valid
processing-instruction nodes and produced the wrong sibling
relationships.
Processing instructions that appear inside a DTD subset are not
document children and should not surface in the DOM tree. Ignore
those SAX callbacks while libxml is parsing the subset so the XML
parser builds the correct document structure.