Commit Graph

1263 Commits

Author SHA1 Message Date
Andreas Kling
fb11732526 LibWeb: Fix style inheritance for slotted elements
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.
2026-02-13 10:22:30 +01:00
Andreas Kling
4a7ca32af0 LibWeb: Skip full document style update in getComputedStyle if possible
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.
2026-02-13 10:22:30 +01:00
Sam Atkins
873680a504 LibWeb: Delay the load event until critical style subresources load
Previously, `<link rel=stylesheet>` would delay the load event until its
style sheet loaded, but not care about its subresources. `<style>`
would not delay the load event at all. Instead, each `@import` would
delay the load event.

Now, both `<style>` and `<link>` delay the load event until their style
sheet and its critical subresources have loaded or failed. This means
that CSSImportRules no longer need to delay the load event themselves,
because they do so implicitly as a critical subresource of their parent
style sheet.

This doesn't directly affect behavior, but means that any other critical
style resources we add will automatically delay the load event.

One wrinkle here is that the spec for the `<link>` element requires that
we wait for the style sheet's critical subresources *before* we create
a CSSStyleSheet, which means we don't yet know what those are.
https://html.spec.whatwg.org/multipage/semantics.html#fetching-and-processing-a-resource-from-a-link-element:critical-subresources
For now we simply ignore this, as we did before. That means we continue
to not delay the `<link>`'s load event.
2026-02-12 16:23:12 +01:00
Sam Atkins
b21a05d290 LibWeb/CSS: Wait for resources to load to fire <style> load event
Previously, we fired the load event immediately, without waiting for
anything. This was good for not timing out, but bad for anything that
wanted to wait for the load to complete.

CSSStyleSheet now maintains a list of critical subresources, and waits
for all of them to complete before it then tells its owner that it is
ready. "Complete" here means the network request completed with or
without an error. This is done by having those subresources (just
`@import` for now) notify their style sheet when they complete. This
then propagates up as an `@import` tells its style sheet, which then
would tell its parent `@import` if it had one.

There are other subresources we should wait for (specifically fonts and
background images) but this commit just adds `@import` as a first step.
2026-02-12 16:23:12 +01:00
Sam Atkins
44cbdc34fa LibWeb: Make StyleElementUtils into a base class, StyleElementBase
This will allow us to cast an Element to a StyleElementBase without
having to know whether it's an HTML or SVG style element.
2026-02-12 16:23:12 +01:00
Psychpsyo
dab742ed84 Everywhere: Remove double // on comments 2026-02-11 13:28:01 -06:00
Aliaksandr Kalenik
fde2015846 LibWeb: Reduce recompilation impact of DOM/Element.h
Remove unused/redundant includes from Element.h:
- AK/IterationDecision.h (redundant)
- ARIA/AttributeNames.h (redundant via ARIAMixin.h)
- CSS/CascadedProperties.h (redundant via PseudoElement.h)
- CSS/StylePropertyMapReadOnly.h (pointer types only)
- HTML/LazyLoadingElement.h (unused in header)

Extract IntersectionObserverRegistration struct from
IntersectionObserver.h into its own lightweight header.
This breaks the heavy transitive include chain through
IntersectionObserverEntry.h and Geometry/DOMRect.h that
was pulled into every file including Element.h.

Indirect recompilation impact reductions:
- IntersectionObserver.h: ~1387 -> ~27 files
- LazyLoadingElement.h: ~1387 -> ~1002 files
2026-02-11 20:02:28 +01:00
Aliaksandr Kalenik
30e4779acb AK+LibWeb: Reduce recompilation impact of DOM/Node.h
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.
2026-02-11 20:02:28 +01:00
Aliaksandr Kalenik
901cc28272 LibWeb: Reduce recompilation impact of DOM/Document.h
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).
2026-02-11 20:02:28 +01:00
Praise-Garfield
ebd312689e LibWeb: Support :placeholder-shown pseudo-class for textarea elements
Previously only input elements were matched. Add placeholder_value()
to HTMLTextAreaElement mirroring the HTMLInputElement API and update
both selector matching code paths to handle textarea.
2026-02-11 16:11:11 +01:00
Jelle Raaijmakers
dbd09454c4 LibWeb: Reset cursor blink cycle when focus changes
This makes sure the caret starts blinking immediately on programmatic
focus changes to <input>s and <textarea>s, for example.
2026-02-11 11:17:27 +01:00
Jelle Raaijmakers
a69df4d25b LibWeb: Disable caret blinking in test mode
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.
2026-02-11 11:17:27 +01:00
Jelle Raaijmakers
6c2583eade LibWeb: Implement mouse auto scrolling of scrollable containers
When the mouse is dragged from inside a scrollable container to outside
of it, we now automatically scroll the container so the selection can be
extended. Scroll speed scales with the distance past the scrollport
edge, capped at a maximum. Edges close to the viewport boundary get a
wider activation zone so the speed ramp works predictably even when the
mouse has limited room to move.

The logic is encapsulated in AutoScrollHandler, which EventHandler
creates lazily on mouse selection start.
2026-02-11 11:04:53 +01:00
Jelle Raaijmakers
2d4728d353 LibWeb: Keep cursor in view for text controls
When editing or changing the selection inside an <input> or <textarea>,
we should scroll the container so the cursor is always visible. Note
that currently the cursor might still become invisible at the end of the
container since we do not reserve enough space for it to be made
visible.
2026-02-11 11:04:53 +01:00
Jelle Raaijmakers
87ada9e887 LibWeb: Do not mark ScrollHandled as [[nodiscard]]
There's only one place where we don't `(void)` the result of these
methods, so let's not be too pedantic about it.
2026-02-11 11:04:53 +01:00
Callum Law
379db7a42c LibWeb: Support animation-timeline scroll() value 2026-02-11 10:49:34 +01:00
Callum Law
2af57d6cb6 LibWeb: Store CSS defined animations as CSSAnimation
In a later commit we will be calling `CSSAnimation` specific methods on
these and this saves us casting to a `CSSAnimation` every time
2026-02-11 10:49:34 +01:00
Luke Wilde
e9f5df2131 LibWeb/SVG: Implement the feTurbulence filter 2026-02-11 09:39:39 +01:00
Aliaksandr Kalenik
40429292fe LibWeb: Forward-declare RequiredInvalidationAfterStyleChange in Element
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.
2026-02-11 06:52:11 +01:00
Aliaksandr Kalenik
eea9837438 LibWeb: Skip :has() invalidation in update_style when nothing is pending
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.
2026-02-11 00:28:42 +01:00
Aliaksandr Kalenik
38e53b5600 LibWeb: Templatize Document::for_each_shadow_root()
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.
2026-02-11 00:28:42 +01:00
Aliaksandr Kalenik
ad76ce6d90 LibWeb: Extract layout tree update check in Document::update_layout()
No behavior change.
2026-02-10 22:14:33 +01:00
Andreas Kling
bacd946721 LibWeb: Bail out of image callbacks when document becomes inactive
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.
2026-02-10 21:19:35 +01:00
Andreas Kling
6ca01e124d LibWeb: Skip destroyed navigables when unloading a document
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.
2026-02-10 21:19:35 +01:00
Timothy Flynn
d050206fc3 LibWeb: Remove cookie source parameter from document cookie APIs
The source of any document cookie must be non-HTTP.
2026-02-10 12:21:20 +01:00
Timothy Flynn
8d97389038 LibHTTP+Everywhere: Move the cookie implementation to LibHTTP
This will allow parsing cookies outside of LibWeb.

LibHTTP is basically becoming the home of HTTP WG specs.
2026-02-10 12:21:20 +01:00
Aliaksandr Kalenik
aa24da8a93 LibWeb: Only invalidate layout on SVG viewBox/preserveAspectRatio change
These attributes are consumed during layout in SVGFormattingContext to
compute the viewbox transform. They don't affect the layout tree
structure, so a layout-only invalidation is sufficient instead of a
full layout tree rebuild.
2026-02-09 19:49:10 +01:00
Aliaksandr Kalenik
ed0ce5c17f LibWeb: Unify three layout tree traversals into one in update_layout()
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.
2026-02-09 19:00:04 +01:00
Aliaksandr Kalenik
abecc746d7 LibWeb: Implement partial SVG relayout
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.
2026-02-09 03:02:49 +01:00
Timothy Flynn
4a8ef68b90 LibWeb: Protect against null navigables in lineage chain more thoroughly
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
2026-02-08 14:51:25 -05:00
Timothy Flynn
5cff8db44c LibWeb: Invoke Document::navigable() fewer times in a row
This is not necessarily a cheap accessor.
2026-02-08 14:51:25 -05:00
Aliaksandr Kalenik
2452680615 LibWeb: Remove Document.h include from DOMParser.h, DOMImplementation.h
...and WorkerEnvironmentSettingsObject.h

These headers only use Document via forward-declarable references and
smart pointers, so the full include is unnecessary.
2026-02-08 18:51:13 +01:00
Aliaksandr Kalenik
e76cf3e225 LibWeb: Remove Document.h include from Layout/Node.h
This reduces the recompilation cascade when Document.h is modified.
Add explicit includes to files that relied on the transitive dependency.
2026-02-08 18:51:13 +01:00
Aliaksandr Kalenik
c62996abd7 LibWeb: Avoid dynamic_cast in SlottableMixin::assigned_slot()
`SlottableMixin::assigned_slot()` was using `as<DOM::Node>(*this)` to
get a `Node` reference. Since `SlottableMixin` has no inheritance
relationship with `Node`, `as_if<>` can't use `static_cast` and falls
through to `dynamic_cast`, which is expensive. Replace this with a
virtual `slottable_as_node()` accessor overridden in `Element` and
`Text`.

This showed up as hot in profiles when loading the GC heap explorer
page.
2026-02-07 16:43:50 +01:00
Aliaksandr Kalenik
488123c75b LibWeb: Don't rebuild layout tree on SVG transform change
The SVG `transform` attribute is stored on the DOM element and read
directly during layout by
`SVGFormattingContext::layout_graphics_element()`. Since changing the
transform doesn't affect which DOM nodes produce layout boxes or how
they're structured, we only need to re-run layout on the existing tree
instead of rebuild it from scratch.
2026-02-07 15:47:17 +01:00
Jelle Raaijmakers
2a2f8ef90b LibWeb+UI: Support triple clicking and dragging paragraphs
When triple clicking on text, we should select the entire paragraph, or
entire line in <input>s and <textarea>s. If the mouse button is held
down and the user starts dragging, the selection expands with additional
paragraphs or lines.

This expands on the work of Kai Wildberger (PR #7681) but was adjusted
for the work that happened previously to support double click + drag
moves and includes triple click support for our Qt UI.

Co-authored-by: Kai Wildberger <kiawildberger@gmail.com>
2026-02-06 14:18:10 +00:00
Andreas Kling
5b26777904 LibWeb: Don't WEB_SET_PROTOTYPE_FOR_INTERFACE for ShadowRealm global
We weren't doing this before either, but through a slightly sneaky
mechanism: we had overridden Cell::initialize() in
ShadowRealmGlobalScope as a no-op.

Instead of that, do the same thing Window and Worker globals do and
make all of the globals that inherit UniversalGlobalScopeMixin opt
out of WEB_SET_PROTOTYPE_FOR_INTERFACE in EventTarget::initialize().
2026-02-06 13:50:54 +01:00
Andreas Kling
d45bda7b79 LibWeb: Mark Node::inserted() and Node::removed_from() as MUST_UPCALL
Node::inserted() sets needs_style_update, and Node::removed_from()
clears the layout node and paintable pointers. Forgetting to call
either base implementation from a derived class would be a bug.
2026-02-06 13:50:54 +01:00
Andreas Kling
8d2081d3ff LibWeb: Mark Element::attribute_changed() as MUST_UPCALL
The base implementation handles critical bookkeeping like element
ID/name registration and slot assignment. Forgetting to call it
from a derived class would be a correctness bug.
2026-02-06 13:50:54 +01:00
Jelle Raaijmakers
7714471aae LibWeb: Recompute style for ::selection pseudo element 2026-02-06 10:47:50 +00:00
Callum Law
665feb57ae LibWeb: Use computed values in Element::is_potentially_scrollable
`Layout::NodeWithStyle::computed_values()` actually holds used values
which may not be the same as computed values e.g. if they have been
modified by `Document::propagate_overflow_to_viewport()`
2026-02-05 16:45:34 +01:00
Callum Law
226b1ee46b LibWeb: Support percentage units for Animations::TimeValue
Non functional change for now since we don't yet have any progress-based
timelines which support percentage based time values
2026-02-05 16:45:34 +01:00
Timothy Flynn
0482b6bb57 LibWeb+LibWebView+WebContent: Implement versioning for document cookies
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.
2026-02-05 07:28:07 -05:00
Sam Atkins
78fcc7ed72 LibWeb/DOM: Add FIXME for flat tree descendant check 2026-02-05 11:21:08 +01:00
Sam Atkins
2994a7532d LibWeb: Make shadow_including_first_ancestor_of_type() use the flat tree
Every user of this actually wants an ancestor in the flat tree - taking
things like `<slot>` into account. So rename it and adjust its behavior
to use that.
2026-02-05 11:21:08 +01:00
Sam Atkins
bd753eafb6 LibWeb/DOM: Walk flat tree in Element::check_visibility() 2026-02-05 11:21:08 +01:00
Sam Atkins
ce13ab733d LibWeb/DOM: Walk the flat tree to propagate layout tree updates
This fixes an issue where we wouldn't propagate the layout update from a
slotted node to its slot.
2026-02-05 11:21:08 +01:00
Sam Atkins
bccf388110 LibWeb/DOM: Implement "flat tree parent" getter for DOM Node
Various parts of the spec ask us to walk the flat tree. In most cases
that's the same as getting its parent or shadow host, but `<slot>` in
particular breaks this rule, as a slotted element's flat tree parent is
the slot.
2026-02-05 11:21:08 +01:00
Tim Ledbetter
c44b30f0f1 LibWeb: Exclude UA internal shadow root elements in elementFromPoint()
When `elementFromPoint()` or `elementsFromPoint()` returns an element
that is inside a UA internal shadow root, we now return the shadow host
for that element.
2026-02-02 20:17:03 +00:00
Andreas Kling
5f434a442a LibWeb: Use targeted style invalidation when adding a new stylesheet
Instead of doing a full document style invalidation when a stylesheet is
dynamically added, we now analyze the new sheet's selectors to determine
which elements could potentially be affected, and only invalidate those.

This works by building an InvalidationSet from the rightmost compound
selector (the "subject") of each rule in the new stylesheet, extracting
class, ID, tag name, attribute, and pseudo-class features. We then walk
the DOM tree and only mark elements matching those features as needing a
style update.

If any selector has a rightmost compound that is purely universal (no
identifying features), or uses a pseudo-class not supported by the
invalidation set matching logic, we fall back to full invalidation.
2026-02-02 21:08:30 +01:00