Stop converting between CSS and device pixels as part of rendering - the
display list should be as simple as possible, so convert to DevicePixels
once when constructing the display list.
During SVG subtree relayout, position:fixed elements inside
<foreignObject> use the viewport as their containing block. Since the
viewport is outside the SVG subtree, it was not pre-populated in the
LayoutState, causing a VERIFY failure in ensure_used_values_for().
Fix this by unconditionally pre-populating the viewport node from its
paintable in relayout_svg_root().
Replace the unsafe HashTable<GC::Weak<Animation>> with
GC::WeakHashSet<Animation>, and update all callers to use reference
syntax instead of pointer syntax since the iterator now yields T&.
In practice, the event loop queries Document::fonts(), so we don't gain
anything by delaying the allocation, apart from making it unclear when
it happens.
Previously, PaintNestedDisplayList was treated as an opaque command,
printing only its name and rect without showing the nested display
list's contents. This made it impossible to debug painting issues
involving SVG masks/clips, CSS background-clip: text, and iframe
content through display list dumps.
Refactor the command dump loop into a recursive lambda that expands
nested display lists inline with increased indentation.
relayout_svg_root() clears individual stacking contexts via
reset_for_relayout() but didn't call invalidate_stacking_context_tree().
The viewport's stacking context remained non-null, so
build_stacking_context_tree_if_needed() skipped the rebuild. This caused
foreignObject to lose its stacking context after relayout, breaking SVG
mask application.
There are some predefined counter styles (such as the longhand east
asian ones) which are too complex to be defined here and will need to be
implemented ad-hoc, this remains as a FIXME for now.
The tricky bit of this is resolving cycles in extending rules and
ensuring that counter styles are registered in the required order for
extension (i.e. for any pair of extended/extending rules the extended
one should be registered first).
Each NodeWithStyle is assigned a sequential layout index during the
pre-layout tree traversal. LayoutState stores UsedValues in a
PagedStore — a two-level page table indexed by layout_index that
gives O(1) lookup via two array accesses, with pages allocated
lazily on first write. UsedValues are stored directly in pages
(Optional<T>) rather than behind heap pointers, eliminating
per-entry malloc/free calls and improving cache locality.
This cuts ensure_used_values_for() from ~14% to ~7% in profiles
on https://www.nyan.cat/.
The cursor blink timer fires every 500ms and only needs to toggle
the blink state and mark the paintable as needing display. If the
paintable doesn't exist yet, we can simply skip the blink -- the
cursor will appear after the next natural rendering update.
This avoids a potentially expensive synchronous layout every 500ms
for what is a purely cosmetic operation.
This is a targeted version of update_style() that only performs a
style update if the given element (or its ancestors) actually have
dirty style. Useful when we only need up-to-date computed properties
for a specific element.
Eliminate O(n) tree-order sorted insert on every add() by simply
appending elements and deferring order resolution to lookup time. Cache
the first-in-tree-order element in get() so repeated getElementById()
calls avoid repeated subtree traversal.
Improves performance on YouTube where add() was hot in profiles while
scrolling through comments.
Add a cached m_in_editable_subtree flag to Node, updated on tree
mutations and contenteditable/designMode changes.
This replaces the recursive parent walk in is_editable() and
is_editing_host() with an O(1) flag check. The flag is recomputed
in inserted(), moved_from(), and cleared in removed_from(). Subtree
walks recompute the flag when contenteditable attributes change or
design mode is toggled.
This was 4% of CPU time while playing a YouTube video.
Absolutely positioned elements inside SVG foreignObject were being
positioned relative to an ancestor containing block outside the SVG,
instead of relative to the foreignObject itself. Per a W3C resolution
and the behavior of other browsers, foreignObject should establish a
containing block for absolutely and fixed positioned elements.
With this fix, the `has_abspos_with_external_containing_block` check
in `set_needs_layout_update()` and the abspos preservation loop in
`relayout_svg_root()` become dead code — remove both and simplify the
ancestor loops. Rename related tests to reflect the new behavior.
Fixes https://github.com/LadybirdBrowser/ladybird/issues/3241
Move the dispatch_events_for_animation_if_necessary() calls into step 1
of update_animations_and_send_events(), where the spec note says
updating timelines involves "Queueing animation events for any such
animations." Previously, these calls ran after step 7 (event dispatch),
causing newly queued events to be deferred by an extra rendering update.
This meant that e.g. a CSS transition triggered during an earlier
rendering step would not have its transitionrun event fired until the
next frame, instead of the current one.
Dispatching scroll events could cause new scroll events to get lined up
and added to `m_pending_scroll_events`. The spec then asks us to empty
out that list, removing those newly added events.
Prevent doing that by emptying out the list before iterating over the
events. Fixes part of the WPT test `html/webappapis/scripting/event-
loops/new-scroll-event-dispatched-at-next-updating-rendering-time.html`.
We were using a separately fired timer for auto scrolling ticks, but it
makes more sense to tie this into the rendering steps of which
`::run_the_scroll_steps()` is a part. Should fix the flaky
`Text/input/viewport-auto-scroll.html` test.
Fixes#7939.
Change the parameters types of the functions `coarsen_time` and
`coarsened_shared_current_time` from `bool` to
`CanUseCrossOriginIsolatedAPIs` for more coherence with the surrounding
code.
Two issues prevented slotted elements from correctly inheriting
styles from their assigned slot:
1. Element::element_to_inherit_style_from() was skipping the slot
element and returning the shadow host instead. This meant slotted
elements inherited from the host, completely ignoring any styles
on the slot itself.
2. When a slot element's style changed during the style tree walk,
its assigned (slotted) nodes were never marked for recomputation.
The tree walk follows the DOM tree, but slotted elements are DOM
children of the shadow host, not the slot, so they were missed.
Fix (1) by returning the slot directly as the inheritance parent.
Fix (2) by marking assigned nodes dirty in update_style_recursively
when a slot's style changes.
Before calling update_style() for a getComputedStyle property access,
we now check whether the target element actually needs a style update
by walking the flat tree ancestor chain. If neither the element nor any
of its ancestors have dirty style bits, and there are no document-level
reasons to recalculate style, we skip the update_style() call entirely.
We walk the flat tree (not the DOM tree) because style inheritance
follows slot assignment -- slotted elements inherit from their assigned
slot, not their DOM parent.
This avoids unnecessary work when scripts access computed style
properties on elements whose styles are already up-to-date, which is a
common pattern on the web.
Remove includes from Node.h that are only needed for forward
declarations (AccessibilityTreeNode.h, XMLSerializer.h,
JsonObjectSerializer.h). Extract StyleInvalidationReason and
FragmentSerializationMode enums into standalone lightweight
headers so downstream headers (CSSStyleSheet.h, CSSStyleProperties.h,
HTMLParser.h) can include just the enum they need instead of all of
Node.h. Replace Node.h with forward declarations in headers that only
use Node by pointer/reference.
This breaks the circular dependency between Node.h and
AccessibilityTreeNode.h, reducing AccessibilityTreeNode.h's
recompilation footprint from ~1399 to ~25 files.
Remove 11 heavy includes from Document.h that were only needed for
pointer/reference types (already forward-declared in Forward.h), and
extract the nested ViewportClient interface to a standalone header.
This reduces Document.h's recompilation cascade from ~1228 files to
~717 files (42% reduction). Headers like BrowsingContext.h that were
previously transitively included see even larger improvements (from
~1228 down to ~73 dependents).
If we want to test whether or not we're drawing the caret, we need to
prevent it from blinking or otherwise all tests we're going to write
that look at the display list will turn out to be flaky.
Replace the direct #include of StyleInvalidation.h in Element.h with a
forward declaration in Forward.h. Element.h only uses the type in
function declarations, so the complete type is not needed.
This reduces the recompilation impact of modifying StyleInvalidation.h
from ~1380 files to ~4 files, since Element.h is transitively included
by nearly every HTML and SVG element header.
Add a document-level boolean flag that tracks whether any :has()
invalidations have been scheduled. This avoids iterating over all
shadow roots just to check is_empty() on each style scope when no
:has() invalidations are pending, which is the common case during
scrolling on complex pages like Reddit.
Results in ~10% reduction of is_empty() calls in profiles when
scrolling on Reddit.
Replace AK::Function parameter with a template parameter so the
compiler can inline the lambda at each call site. This eliminates
type-erasure overhead (vtable indirection + ScopeGuard) that was
showing up in profiles during Reddit scrolling, where this function
is called repeatedly from update_style() for every shadow root on
every style update.
HTMLImageElement's "update the image data" algorithm checks
is_fully_active() at the start, but its async continuations
(microtasks, element tasks, batching dispatcher callbacks) skip
this check. When an iframe is removed or navigated, these
callbacks fire on an inactive document, causing crashes.
Fix this with two changes:
1) Add is_fully_active() bail-out checks at all async callback
entry points in HTMLImageElement. Each bail-out also clears
the DocumentLoadEventDelayer to prevent blocking the parent
document's load event forever.
2) Create the DocumentObserver eagerly in initialize() (like
HTMLMediaElement) with a document_became_inactive callback
that clears the load event delayer and stops the animation
timer. Fire document_became_inactive from Document::destroy()
in addition to did_stop_being_active_document_in_navigable(),
since iframe removal takes a different path than navigation.
A guard flag prevents duplicate firing.
When unload_a_document_and_its_descendants() iterates all_navigables()
to find descendant navigables, skip any that have been destroyed.
When an iframe is removed, destroy_the_child_navigable() marks its
navigable as destroyed synchronously, but removal from all_navigables()
happens later in an async callback from destroy_a_document_and_its_
descendants(). If we count these destroyed navigables as descendants and
queue unload tasks for them, Document::destroy() may run during the
subsequent spin_until and remove those tasks (since it clears all tasks
for its document). This causes number_unloaded to never reach
unloaded_documents_count, hanging the event loop permanently.
This was the root cause of intermittent hangs when running crash tests
in batch mode: the previous test's iframe cleanup would leave destroyed
navigables in all_navigables(), and the about:blank navigation between
tests would get stuck in the unload spin_until.
Merge the three consecutive for_each_in_inclusive_subtree traversals
into a single preorder walk. All three operations only depend on
ancestor state which is satisfied before descendants are visited in
preorder traversal.
Previously, any SVG geometry attribute change would mark the entire
document layout tree as dirty, triggering a full layout pass even though
only the SVG subtree was affected. This made SVG geometry animations
unnecessarily expensive.
Fix this by stopping `needs_layout_update` propagation at the SVGSVGBox
boundary and tracking dirty SVG roots separately on the Document. When
`update_layout()` finds that only SVG roots need relayout (and the
document layout root is clean), it runs SVGFormattingContext on each
dirty SVG root in a fresh LayoutState and commits the results directly,
bypassing the full document layout pass entirely.
This results in a substantial performance improvement on pages with
animated SVGs, such as https://www.cloudflare.com/,
https://www.duolingo.com/, and our GC graph explorer page.
This extends the null navigable check added in commit
b118c99c27 to include all ancestor and
descendant list lookups. Fixes a crash in the following WPT test:
/cookies/schemeful-same-site/schemeful-navigation.tentative.html