Commit Graph

190 Commits

Author SHA1 Message Date
Andreas Kling
4b3abc6958 LibWeb: Move invalidation set matching into a helper
Element.cpp still contained the CSS logic for deciding whether an
invalidation set references features present on an element. Move that
matcher into CSS::Invalidation::InvalidationSetMatcher.

The helper uses Element's public API for classes, id, attributes,
pseudo-class state, and removed-attribute tracking. This keeps Element
focused on DOM state while CSS::Invalidation owns selector feature
matching.
2026-04-29 15:47:23 +02:00
Andreas Kling
eeab3671c2 LibWeb: Move attribute style invalidation into a helper
Element.cpp still encoded the CSS consequences of attribute changes:
class/id invalidation keys, pseudo-class triggers, and shadow-host
stylesheet fallout. Move that policy into AttributeInvalidator.

Element now reports attribute changes to the helper and exposes a small
state hook to remember removed attributes while invalidation is pending.
2026-04-29 15:47:23 +02:00
Andreas Kling
85e33738f5 LibWeb: Move :has() element invalidation into the helper
Element exposed a small method that encoded how :has()-affected elements
are marked dirty. Move that policy into CSS::Invalidation alongside the
rest of the :has() mutation invalidation helpers.

This keeps Element focused on DOM state while preserving the existing
subject and non-subject :has() invalidation behavior.
2026-04-29 15:47:23 +02:00
Andreas Kling
356a369aa6 LibWeb: Avoid descendant recomputes for same-parent moves
Moving a node within the same parent changes sibling and positional
relationships, but it does not make every descendant of the moved node
need a fresh computed style. Handle this as a structural mutation at
the old and new sibling edges and dirty only the moved root and the
affected ancestors, instead of marking the entire moved subtree.

Factor the existing previous- and next-sibling structural invalidation
out of Node::invalidate_style() into invalidate_structurally_affected_-
siblings(), and pull the ancestor child-needs-style-update walk into
mark_ancestors_as_having_child_needing_style_update(). The new
invalidate_style_after_same_parent_move() reuses both helpers.

Whether the moved root itself needs its own style recomputed depends
on whether any selector matched against it (or against a descendant)
relied on its position in the sibling list. Track that via two new
sticky bits on Element, set during selector matching:

  - m_affected_by_structural_pseudo_class_in_non_subject_position
  - m_affected_by_sibling_combinator_in_non_subject_position

Both are write-once (sticky) because matching descendants can set them
while we're not currently re-matching this element's own selectors;
keeping them set is conservative and avoids stale descendant style.

When neither bit is set and the element only carries subject-position
positional/sibling/has() involvement, we just dirty the root and skip
its descendants.

Rebaseline same-parent-move-root-only and the structural-feature filter
counters to reflect the new path. Matching behavior is unchanged.
2026-04-28 15:34:49 +02:00
Andreas Kling
11d9d4a7a5 LibWeb: Match attribute invalidation by presence and recent removal
Element::includes_properties_from_invalidation_set() previously
short-circuited and returned true for the id and class attributes,
because the parsed id and class-name state was treated as the source of
truth and the attribute presence check could disagree with it. That
shortcut over-invalidated for any [class] or [id] selector even when
neither attribute had ever been touched on the element.

Drop the special case and answer attribute presence queries the same
way for every attribute: the element either currently has the attribute,
or has had it removed earlier in this style update batch.

To handle the just-removed case, track the set of attribute names that
were removed since the last invalidation pass on a new per-Element
Vector<FlyString, 1> m_removed_attributes_for_style_invalidation. The
StyleInvalidator clears the vector for each element it visits during
perform_pending_style_invalidations, so it only ever holds names from
the current batch.

Rebaseline the affected attribute-presence tests; counters drop because
elements that never had id/class no longer match [class] / [id]
invalidations.
2026-04-28 15:34:49 +02:00
Tim Ledbetter
6bb037aec7 LibWeb: Skip backward sibling invalidation when no child needs it
We now track when a parent has a child affected by a backward structural
pseudo-class. These are selectors whose match result for an element can
depend on siblings after that element, such as `:last-child`,
`:only-child`, `:last-of-type`, `:only-of-type`, `:nth-last-child`, and
`:nth-last-of-type`.

When inserting or removing a node, previous siblings only need style
invalidation if one of them was matched against such a selector. Use the
parent-level flag to skip the previous-sibling walk when no child under
that parent can be affected.

This saves a lot of invalidation work on sites that insert a lot of
nodes into the DOM via JS.
2026-04-26 16:14:43 +02:00
Sam Atkins
6d02296eb5 LibWeb: Pass better information to node moving/removing steps
Corresponds to:
73de9e5e1b
097be9feaa
2026-04-22 14:05:49 +01:00
Shannon Booth
fd44da6829 LibWeb/Bindings: Emit one bindings header and cpp per IDL
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.
2026-04-21 07:36:13 +02:00
Andreas Kling
7a5b1d9de1 LibWeb: Delay generic :has() sibling scans until sibling roots
Mark elements reached by stepping through sibling combinators inside
:has() and use that breadcrumb during generic invalidation walks.

Keep the existing conservative sibling scans for mutations outside
those marked subtrees so nested :is(), :not(), and nesting cases
continue to invalidate correctly.

Also keep :has() eager within compounds that contain ::part(). Those
selectors retarget the remaining simple selectors to the part host, so
deferring :has() there changes which element the pseudo-class runs
against and can make ::part(foo):has(.match) spuriously match.

Add a counter-based sibling-scan test and a regression test covering
the ::part()/ :has() selector orderings.
2026-04-20 13:20:41 +02:00
Andreas Kling
85ff13870f LibWeb: Stop :has() invalidation walk when out of :has() scope
A DOM mutation under a document that uses any :has() rule currently
walks every ancestor up to the root, invoking invalidate_style_if_
affected_by_has() on each. Most of those ancestors have nothing to
do with :has(), so the work scales linearly with DOM depth.

Introduce an in_has_scope flag on Element, set while evaluating :has()
arguments for invalidation metadata. StyleScope's upward invalidation
walk now terminates at the first element that is neither in :has()
scope nor a :has() anchor, so it only traverses the region where some
:has() rule might actually care about the change.

Keep the existing fast :has() matching paths for normal selector
matching, but bypass them while collecting per-element metadata so the
scope markers still get populated. Node insertion also schedules the
parent for the :has() walk so newly inserted nodes still reach the real
anchor.

The css-has-invalidation suite adds focused coverage for these shapes
and updates the expected counters to reflect the shorter walks.
2026-04-20 13:20:41 +02:00
Andreas Kling
8caca053a3 LibWeb: Avoid IntersectionObserver registration lookups
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.
2026-04-17 08:02:30 +02:00
Shannon Booth
8642801889 LibWeb: Set fragment scripting mode from the context document
This corresponds with the editorial change to the HTML standard
introducing the parsing mode enum of:

https://github.com/whatwg/html/commit/01c45cede

And a follow up normative change of:

https://github.com/whatwg/html/commit/508706c80

Making fragment parsing derive its scripting mode from the context
document.
2026-04-14 23:01:36 +02:00
Andreas Kling
0f4575e7d0 LibWeb: Clear stale layout state for inactive documents
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
2026-04-11 16:03:26 +02:00
Sam Atkins
ed6a5f25a0 LibWeb: Implement scoped custom element registries 2026-03-27 19:49:55 +00:00
Sam Atkins
96f84b1322 LibWeb/DOM: Give Element a CustomElementRegistry 2026-03-27 19:49:55 +00:00
Luke Wilde
df32da5e86 LibWeb: Make every HTMLElement potentially form-associated
This can be the case for form-associated custom elements, where any
HTML element can be form-associated.
2026-03-25 13:18:15 +00:00
Callum Law
915fc4602b LibWeb: Implement CSS inherit() function
The remaining failing imported tests are due to wider issues which are
covered by FIXMEs (both existing and added in this commit)
2026-03-19 10:25:37 +01:00
Shannon Booth
cc6536b527 LibWeb/HTML: Always provide ChildrenChangedMetadata to children changed 2026-03-19 09:46:54 +01:00
Zaggy1024
2e54c18fb3 LibWeb: Use a queue to process fullscreen request completions
Instead of immediately firing fullscreenchange, defer that until
WebContent's client has confirmed that it is in fullscreen for the
content. The fullscreenchange is fired by the viewport change, so in
cases where the fullscreen transition is instantaneous (i.e. the
fullscreen state is entered at the exact moment the viewport expands),
the resize event should precede the fullscreenchange event, as the spec
requires.

This fixes the WPT element-request-fullscreen-timing.html test, which
was previously succeeding by accident because we were immediately
fullscreenchange upon requestFullscreen() being called, instead of
following spec and doing the viewport (window) resize in parallel. The
WPT test was actually initially intended to assert that the
fullscreenchange event follows the resize event, but the WPT runner
didn't actually have a different resolution for normal vs fullscreen
viewports, so the resize event doesn't actually fire in their setup. In
our headless mode, the default viewport is 800x600, and the fullscreen
viewport is 1920x1080, so we do fire a resize event when entering
fullscreen. Therefore, that imported test is reverted to assert that
the resize precedes the fullscreenchange.
2026-03-17 18:58:37 -05:00
Zaggy1024
009e25a875 LibWeb: Move RequestFullscreenError out of Element.h
This will be needed across classes to handle a queue of fullscreen
requests.
2026-03-17 18:58:37 -05:00
Zaggy1024
44ed698d4f LibWeb: Separate the active element and the element being activated
We were conflating elements being the active element and elements being
activated. The :active pseudo class is supposed to be based on whether
an element will have its activation behavior run upon a button being
released.

Store whether an element is being activated as a flag that is set/reset
by EventHandler.

Doing this allows label elements to visually activate their control
without doing a weird paintable hack, so the Labelable classes have
been yeeted.
2026-03-17 04:01:29 -05:00
Callum Law
c47f226225 LibWeb: Support CSS if() function
We don't yet support style queries
2026-03-09 14:36:18 +00:00
Callum Law
0c847e2560 LibWeb: Reset whether element uses tree counting function on recompute
This can save us some unnecessary recomputations if an element's style
changes from depending on a tree counting function to not.

Also removes an incorrect FIXME
2026-03-09 14:36:18 +00:00
Aliaksandr Kalenik
d17b7fe70d LibWeb: Track structural invalidation dependencies by direction
Split the structural-change selector metadata into directional bits for
first/last-child and forward/backward positional selectors.

This gives sibling invalidation enough information to distinguish which
side of a mutation can affect an element, instead of treating all
structural selectors as bidirectional.
2026-03-07 00:34:00 +01:00
Timothy Flynn
aabf7ae8fb LibWeb: Skip fullscreen transient activation requests for WebDriver
A transient activation is rarely going to exist when invoked from
WebDriver.
2026-03-02 15:49:13 -05:00
Timothy Flynn
2282636f98 LibWeb: Clean up the element fullscreen APIs a bit
* Don't publicly expose methods that are only used internally.

* Modernize comment style (wrap at 120 chars, use NB for our notes).

* Change the stringifier for RequestFullscreenError to return a UTF-16
  string. We were allocating a String, then transcoding it to UTF-16.

* Give helper methods clearer names, e.g.:

  fullscreen_has_error_check -> is_element_allowed_to_enter_fullscreen
2026-03-01 15:41:43 -06:00
Andreas Kling
a146225331 LibWeb: Use unsafe layout/paintable accessors where appropriate
Add unsafe_layout_node(), unsafe_paintable(), and unsafe_paintable_box()
accessors that skip layout-staleness verification. These are for use in
contexts where accessing layout/paintable data is legitimate despite
layout not being up to date: tree construction, style recalculation,
painting, animation interpolation, DOM mutation, and invalidation
propagation.

Also add wrapper APIs on Node to centralize common patterns:
- set_needs_display() wraps if (unsafe_paintable()) ...set_needs_display
- set_needs_paint_only_properties_update() wraps similar
- set_needs_layout_update() wraps if (unsafe_layout_node()) ...

And add Document::layout_is_up_to_date() which checks whether layout
tree update flags are all clear.
2026-02-26 21:09:08 +01:00
Simon Farre
bc17805b2b LibWeb: Implement requestFullscreen algorithm
The required functionality to exit fullscreen will be in a followup
commit.
2026-02-23 18:44:26 +00:00
Simon Farre
db076bab92 LibWeb: Add Fullscreen event handlers to Document and Element
This also adds a stub to the Permissions Policy checks.
2026-02-23 18:44:26 +00:00
Andreas Kling
9e8e568b43 LibWeb: Use structural sharing for CSS custom properties
Replace per-element OrderedHashMap storage for custom properties with
a RefCounted chain (CustomPropertyData) that enables structural
sharing. Each chain node stores only the properties declared directly
on its element, with a parent pointer to the inherited chain.

Elements that don't override any custom properties share the parent's
data directly (just a RefPtr copy). During cascade, only entries that
actually differ from the parent are stored in own_values - the rest
are inherited through the chain. During var() resolution, resolved
values are compared against the parent's and matching entries are
dropped, enabling further sharing.

The chain uses a depth limit (max 32) with flattening, plus
absorption of small parent nodes (threshold 8) to keep lookups fast.

This reduces custom property memory from ~79 MB to ~5.7 MB on
cloudflare.com.
2026-02-13 14:57:15 +01: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
Callum Law
379db7a42c LibWeb: Support animation-timeline scroll() value 2026-02-11 10:49:34 +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
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
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
Luke Wilde
babfd70ca7 LibGC: Enforce that a Cell type must declare the allocator to use
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.
2026-01-20 12:00:11 +01:00
Psychpsyo
fe2bc2bfe7 LibWeb: Improve scrollingElement handling
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.
2026-01-13 11:47:13 +00:00
Tim Ledbetter
ba7b0c60f0 LibWeb: Move hyperlink navigation methods to DOM::Element
This allows us to use these methods from `SVGAElement` without
inheriting  `HTMLHyperlinkElementUtils`, which we can't do for
`SVGAElement` due to a naming conflict with the `href()` method in
`SVGURIReferenceMixin`.
2026-01-13 10:05:40 +01:00
Sam Atkins
d84a0d411c LibWeb/HTML: Return Promises from Element scroll methods
This re-applies 3a7fcde341 which was
reverted in cacadc8806. The issues it had
were bugs in the previous commit and have been resolved.
2026-01-08 14:50:09 +00:00
Tim Ledbetter
cacadc8806 Revert "LibWeb/HTML: Return Promises from Element scroll methods"
This reverts commit 3a7fcde341.
2025-12-26 19:33:51 +01:00
Sam Atkins
3a7fcde341 LibWeb/HTML: Return Promises from Element scroll methods
Corresponds to part of:
c548a9a1d4
2025-12-23 14:24:28 +01:00
Sam Atkins
bf57b18b9a LibWeb/DOM: Expose Element's parts list
The existing part_list() method used by the bindings lazily creates a
DOMTokenList, which we don't want to do just to check if an Element has
any parts defined.
2025-12-15 14:12:39 +00:00
Sam Atkins
17e59932f4 LibWeb/DOM: Stub out Element.requestPointerLock()
Does just enough to make classic.minecraft.net load and let you play.
Without actual pointer lock it's quite awkward though.
2025-12-09 12:11:21 +01:00
Sam Atkins
01b7800068 LibWeb/DOM: Add the Element.part attribute 2025-12-08 09:44:32 +00:00
Sam Atkins
f1f7f4fbbf LibWeb/DOM: Use GC::Ptr/Ref instead of raw pointers on DOM::Element APIs
No behaviour change, though this does clarify that class_list() always
returns a value.
2025-12-08 09:44:32 +00:00
Callum Law
12e8f503aa LibWeb: Support non-fixed <random-value-sharing>
This works by generating random values using XorShift128PlusRNG at
compute time and then caching them on the document using the relevant
random-caching-key
2025-12-01 11:00:33 +00:00
Lorenz A
7260159b8f LibWeb: Add loading event to style element 2025-11-30 19:22:02 +01:00
Lorenz A
3bc061d028 LibWeb: Allow all elements with tabindex attribute to be focusable 2025-11-12 13:57:05 +01:00
Tim Ledbetter
154e9db033 LibWeb: Cache the value of Element::lang()
This reduces the time spent in
`SelectorEngine::matches_lang_pseudo_class()` from 1.9% to 0.41% on
https://cloudflare.com
2025-11-12 12:36:16 +01:00
Jelle Raaijmakers
b6f5c91a35 LibWeb: Shave 168 bytes off of ARIAMixin
Instead of keeping the vectors inside ARIAMixin, point to the heap using
OwnPtrs.
2025-11-07 16:59:26 +01:00