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
This patch introduces a cookie cache in the WebContent process to reduce
blocking IPC calls when JS accesses document.cookie. The UI process now
maintains a cookie version counter per-domain in shared memory. When JS
reads document.cookie, we check whether we have a valid cached cookie by
comparing the current shared version to the last used version. If they
match, the cached cookie is returned without IPC.
This optimization is based on Chromium's shared versioning, in which it
was observed that 87% of document.cookie accesses were redundant. See:
https://blog.chromium.org/2024/06/introducing-shared-memory-versioning-to.html
Note that this cache only supports document.cookie, not HTTP Cookie
headers. HTTP cookies are attached to requests with varying URLs and
paths. The cookies that match the document URL might not match the
request URL, which we wouldn't know from WebContent. So attaching the
cached document cookie would be incorrect.
On https://twinings.co.uk, we see approximately 600 document.cookie
requests while the page loads. This patch reduces the time spent in
the document.cookie getter from ~45ms to 2-3ms.
When `elementFromPoint()` or `elementsFromPoint()` returns an element
that is inside a UA internal shadow root, we now return the shadow host
for that element.
Previously, Document::has_focus() always returned true, which was
incorrect. This caused documents without a browsing context (such as
those created via document.implementation.createHTMLDocument()) to
incorrectly report that they had focus.
The implementation now follows the spec:
1. Return false if the document has no navigable
2. Return false if the top-level traversable doesn't have system focus
3. Walk the focus chain from the top-level document to verify this
document is actually focused
This fixes a WPT test: "The hasFocus() method must return false if the
Document has no browsing context"
This new API allows tests to inspect the stacking context tree structure
which is useful for verifying that stacking context invalidation and
rebuilding work correctly.
When a pseudo-class state changed, we always walked the entire
document (or shadow root) tree to find affected elements, even
though only the subtree rooted at the old/new common ancestor
can be affected.
Narrow the tree walk to start from old_new_common_ancestor
instead of the root. To ensure ancestor-dependent selectors are
still correctly evaluated, we seed the style computer's ancestor
filter by walking up from the common ancestor to the root before
the invalidation walk.
This reduces the work from O(total elements) to
O(subtree elements) + O(tree depth), which is a large improvement
on pages where pseudo-class changes (hover, focus, active, target)
occur deep in the DOM.
This was extremely hot (10%+) when hovering mailboxes on GMail.
When a parent element's display property changes (e.g., to flex or
grid), children may need to be blockified or un-blockified.
Previously, children only received a recompute_inherited_style() call
which doesn't run the blockification logic.
This patch adds a parent_display_changed flag to the recursive style
update that forces children to get a full style recompute when their
parent's display change triggers a layout tree rebuild.
Move the visual viewport (pinch-to-zoom) transform from a reserved slot
in DisplayList to the AccumulatedVisualContext tree as a root transform
node. Fixed position elements now correctly inherit from this context.
This requires rebuilding the context tree and display list on each zoom
change, but this overhead will be eliminated by future partial context
tree rebuilds.
AK/Random is already the same as SecureRandom. See PR for more details.
ProcessPrng is used on Windows for compatibility w/ sandboxing measures
See e.g. https://crbug.com/40277768
Integrate the AccumulatedVisualContext tree update into
update_paint_and_hit_testing_properties_if_needed() to consolidate
paint tree preparation into a single function.
This ensures that we are explicitly declaring the allocator to use when
allocating a cell(-inheriting) type, instead of silently falling back
to size-based allocation.
Since this is done in allocate_cell, this will only be detected for
types that are actively being allocated. However, since that means
they're _not_ being allocated, that means it's safe to not declare
an allocator to use for those. For example, the base TypedArray<T>,
which is never directly allocated and only the defined specializations
are ever allocated.
- Add WindowManagement to PolicyControlledFeature enum
- Add screen_count() virtual method to PageClient
- Store all screen rects in WebContent::PageClient, derive both
screen_rect() and screen_count() from stored data
- Implement screen_count() overrides in SVGPageClient and PageHost
- Replace FIXME stub in Screen.cpp with spec-compliant implementation
Remove the now-obsolete ClipFrame infrastructure:
- Delete ClipFrame.h and ClipFrame.cpp
- Remove assign_clip_frames() from ViewportPaintable
- Remove enclosing_clip_frame and own_clip_frame from PaintableBox
- Remove m_clip_state HashMap from ViewportPaintable
Clip handling is now fully managed through AccumulatedVisualContext
nodes with ClipData.
Ensure AccumulatedVisualContext stays synchronized when CSS transform
properties change.
AccumulatedVisualContext copies transform and perspective matrices from
the paintable tree at assignment time. When CSS properties that affect
these matrices change (transform, rotate, scale, translate, perspective,
transform-origin, perspective-origin), we must rebuild the
AccumulatedVisualContext tree to reflect the new values.
This adds a rebuild_accumulated_visual_contexts flag to style
invalidation that triggers a full rebuild during the next paint.
Note: The current invalidation strategy is inefficient - it rebuilds
the entire tree even for single-element transform changes. This could
be improved by patching the AccumulatedVisualContext node in-place with
updated matrices, but only when the transform doesn't transition
from/to none (which would change the tree structure). This optimization
is left for future work.
Introduce AccumulatedVisualContext, a tree structure that tracks the
cumulative visual state (scroll offsets, clip regions, transforms,
perspective) for each paintable box.
Motivation:
Before this change, visual state was fragmented across multiple
mechanisms:
- ClipFrame: Tracked clip rectangles, each storing its own
enclosing_scroll_frame_id to handle scroll offset adjustments
- scroll_frame_id: Passed separately to each display list command
- PushStackingContext: Stored transform matrices directly in the command
- Every display list command implemented translate_by() (45 methods
total) to allow scroll offset adjustment during playback
This fragmentation led to:
- Complex, error-prone coordinate transformation logic scattered
throughout the codebase
- Commands being mutated during playback to apply scroll offsets
- Duplicate logic between painting and hit testing for coordinate
transformations
Solution:
AccumulatedVisualContext builds a tree where each node represents a
single visual operation:
- ScrollData: A scroll frame with its ID
- ClipData: A clip rectangle with optional border radii
- TransformData: A 4x4 transform matrix with its origin
- PerspectiveData: A perspective projection matrix
Each PaintableBox stores a reference to its accumulated context node.
The tree structure naturally captures the parent-child relationships,
so traversing from any node to the root gives the complete chain of
visual transformations.
Benefits this enables (in subsequent commits):
- Display list commands become immutable - no more translate_by()
- Single RefPtr<AccumulatedVisualContext> replaces separate
scroll_frame_id and ClipFrame on commands
- LCA-based tree traversal during playback for efficient save/restore
- transform_point_for_hit_test() provides coordinate transformation for
hit testing using the same structure
This change is currently entirely undetectable because of what the
added FIXME talks about. Currently, the HTML element's overflow is
always set to visible in both axes, so it getting set to "clip" in
the imported test ends up not mattering at all.
Previously, setting designMode to "on" would only modify the selection
range if one already existed. Since selections start empty, this meant
no range was created and no selectionchange event was fired.
Use Selection::collapse() instead, which creates a new range if needed
and properly triggers the selectionchange event.
Add ElementResizeAction to Page (maybe there's a better place). It's
just a mousemove delegate that updates styles on the target element.
Add ChromeMetrics for zoom-invariant chrome like scrollbar thumb
thickness, resize gripper size, paddings, etc. It's not user-stylable
but separates basic concerns in a way that a visually gifted
designer unlike myself can adjust to taste.
These values are pre-divided by zoom factor so that PaintableBox can
continue using device_pixels_per_css_pixel calls as normal.
The adjusted metrics are computed on demand from Page multiple times
per paint cycle, which is not ideal but avoids lifetime management and
atomics. Maybe someone with more surety about the painting flow control
can improve this, but it won't be a huge win. If profiling shows
this slowing paints, then Ladybird is in good shape.
Update PaintableBox to draw the resize gripper and deconflict
the scrollbars. Set apropriate cursors for scrollbars and gripper in
mousemove. We override EventHandler's cursor handling because nothing
should ever come between a man and his resize gripper.
Chrome metrics use the CSSPixels class. This is good because it's
broadly compatible but bad because they're actually different units
when zoom is not 1.0. If that's a problem, we could make a new type
or just use double.
Previously, we registered `@property` rules during parsing, and treated
them the same as `CSS.registerProperty()` calls. This is not correct
for a couple of reasons: One, the spec wants us to distinguish between
those two sources of registered custom properties, with
`CSS.registerProperty()` calls taking precedence. Two, we never removed
the registered property when its `@property` was removed from the
document.
This commit deals with this by iterating active CSSPropertyRules to find
which ones currently apply, and storing those in a cache. This cache is
invalidated whenever the Document's style is invalidated, which happens
whenever a CSSRule is added or removed from the Document.
The attached test demonstrates this now working as it should.
A lot of our scrolling code is quite old, and doesn't match the spec,
but does use some similar names. This is quite confusing. In particular
`perform_scroll_of_viewport()` is not the same as the spec algorithm.
That algorithm is actually almost implemented in
`scroll_viewport_by_delta()`.
To clarify things, this commit makes a few changes:
- Rename perform_scroll_of_viewport() to
perform_scroll_of_viewport_scrolling_box(). This is a better match
for how we use this method, even if it's not actually a match for the
algorithm. (:yakbait:)
- Move `scroll_viewport_by_delta()`'s code into a new
`perform_a_scroll_of_the_viewport()` method, and make it take a
position like it should. `scroll_viewport_by_delta()` now calls it
with a calculated position.
I've avoided reusing the original `perform_scroll_of_viewport()` name to
avoid accidents.
When content changes inside a layout node, we now reset intrinsic size
caches only up to the nearest absolutely positioned ancestor, rather
than all the way to the document root.
This optimization is safe because absolutely positioned elements don't
contribute to their ancestors' intrinsic sizes - they are skipped in
min/max content width calculations.
The needs_layout_update flag still propagates to all ancestors so the
document knows layout is needed. Only the cache reset is bounded.