Commit Graph

352 Commits

Author SHA1 Message Date
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
ce5d0bdfc7 LibWeb: Narrow :has descendant invalidation fanout
When a :has() mutation is known to come from a specific subtree, use
that subtree as the mutation root while walking observed ancestors.

Before dirtying an anchor and its non-subject descendants, check whether
any cached :has() rule for that anchor can observe the changed subtree.
This keeps unrelated descendant mutations from invalidating every rule
that merely contains :has().
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
Andreas Kling
89e5deb7b9 LibWeb: Centralize style scope lookup for invalidation
Several invalidation paths need to consider not only a node's own root
scope, but also shadow scopes that can observe the node through :host(),
::slotted(), or ::part() selectors. Each caller open-coded that
traversal, which made the dir/lang and dir=auto fixes carry the same
shadow-boundary logic in multiple places.

Add Node helpers for resolving a node's style scope and for visiting
every style scope that may observe that node. Use them from the
property, child-list, dir/lang, and dir=auto invalidation paths, and
share the same style-scope lookup with DOM::AbstractElement and
Layout::NodeWithStyle.
2026-04-26 10:40:58 +02:00
Andreas Kling
5ff832081d LibWeb: Make :dir() and :lang() invalidation cross shadow boundaries
Three related fixes around how dir and lang attribute changes flow
through shadow boundaries:

* Element::lang() looked up the host through parent_element(), which
  is null for elements directly inside a shadow root. The shadow
  root -> host fallback in step 3 therefore never fired and
  shadow-tree elements never inherited their host document's
  language. Walk through parent() instead so the shadow root case is
  detected.

* The dir/lang attribute_changed handlers each marked descendants
  for style update, but neither one descended through shadow
  boundaries via for_each_shadow_including_inclusive_descendant the
  same way (and only one cleared the cached language). Merge both
  handlers so dir and lang share the same shadow-including descendant
  walk.

* :has(:dir(...)) and :has(:lang(...)) on ancestors aren't keyed on
  any property the regular invalidation plan tracks, so the :has()
  ancestor walk has to be scheduled explicitly. Schedule it on the
  document scope and on every shadow scope reachable from this
  element via parent_or_shadow_host.
2026-04-26 10:40:58 +02:00
Andreas Kling
43f2021484 LibWeb: Recompute shadow part style when exportparts changes
When a shadow host's exportparts attribute changes, elements with
part tokens inside its shadow tree may newly become or stop being
targets of ::part() rules in the outer scope. Mark every such
descendant for style update so the new exposure is reflected.
2026-04-26 10:40:58 +02:00
Andreas Kling
48c4a254a6 LibWeb: Recompute style when an element's part attribute changes
::part(...) rules in the outer scope target shadow descendants by
their part-name tokens. When an element's part attribute changed,
m_parts and the IDL part list were updated but the element itself
was never marked for style update, so previously-matched ::part()
rules kept applying or new matches stayed unevaluated.
2026-04-26 10:40:58 +02:00
Andreas Kling
7d6f77bdf3 LibWeb: Recompute style for descendants when dir/lang changes
The dir and lang attributes inherit, so descendants' :dir() and
:lang() matches and any direction-dependent layout/text depend on
ancestor values. Element::attribute_changed updated the m_dir field
and refreshed cached language values, but never marked descendants
for style recomputation, so existing styled descendants kept their
old :dir()/:lang() results.

Mark every descendant for style update when dir or lang changes.
2026-04-26 10:40:58 +02:00
Andreas Kling
5461b44d04 LibWeb: Invalidate shadow subtree on host attribute change
Rules in a shadow root that match :host(...) can apply different style
to shadow descendants when the host's attributes or classes change.
The host's own invalidation flow doesn't reach into the shadow tree,
so descendants kept their cascaded values from the previous host
state.

When the host's stylesheets contain :host()-style rules that may match
the shadow host, mark the entire shadow subtree dirty so descendant
style is recomputed against the new host state.
2026-04-26 10:40:58 +02:00
Andreas Kling
a870475f2b LibWeb: Expose style recomputation counters through Internals
We already had Internals counters for :has() invalidation work and
the high-level invalidation passes. Add four more that record how
often Element::recompute_style and recompute_inherited_style run,
and how often each bails out as a no-op. The new tests on this
branch use these counters to assert that mutations don't trigger
redundant style recomputation.
2026-04-26 10:40:58 +02:00
Tim Ledbetter
eef11001ec LibWeb: Avoid ref-count churn when diffing computed styles 2026-04-25 08:47:47 +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
InvalidUsernameException
06a7723ee3 LibWeb: Convert early return into assertions
When we are recomputing inherited styles, we should already have
computed style for that element at least once, so cascaded and computed
properties should exist at that point. So make the invariant explicit.
2026-04-21 18:39:00 +02:00
InvalidUsernameException
67e3b2d446 LibWeb: Recompute inherited style even if there is no layout node
`recompute_inherited_style()` assumed that there is no work to do if the
element has no layout node. This however is not necessarily true.

In particular, the following can happen:
1. The element in question is a descendant at least two layers below an
   element that has `display: none`, i.e. with at least one other
   element between them. (If it is a direct child, a full style
   recomputation will be forced for unrelated reasons).
2. TreeBuilder decides to skip creating layout nodes for the `display:
   none` element and all its descendants.
3. Due to some change on the ancestor (e.g. class added, id changed),
   the value of a property that can be inherited changes on the
   ancestor.
4. The property value of the descendant now also needs to change.

In that scenario, we won't compute the entire style of the descendant,
since that already happened. But we do need to update its inherited
properties because the old ones are now stale. At that point we still
don't have a layout node for the element since it will only be created
after this style update.

Instead of skipping the update for inherited properties, simply allow
`recompute_inherited_style()` to run even in absence of a layout node.
And then apply the style to the layout node only if one exists. If none
exists, the style will be applied later if and when a corresponding
layout node is created.

This partially fixes the blank main navigation menus on
https://bleepingcomputer.com.
2026-04-21 18:39:00 +02: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
d629cc379a LibWeb: Cache parsed selectors in Element.matches() and .closest()
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. :^)
2026-04-19 21:32:55 +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
Andreas Kling
e330d5b9ab LibWeb: Make Node::is_connected() O(1) via a cached flag
Previously this walked up the parent chain on every call, which shows
up as a 2.5% item in the profile while watching YouTube videos.

Cache an m_is_connected bit on Node instead, maintained by the DOM
insertion and removal steps.
2026-04-16 08:26:34 +02:00
Callum Law
8d2995c785 LibWeb: Apply correct invalidations when effective counter style changes
The counter style used for an element (in either the `content` or
`list-style-type`) may change despite the computed values of properties
on that element remaining the same (e.g. if a new rule is inserted with
higher cascade precedence).
2026-04-15 11:07:38 +01: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
f11207fee1 LibWeb/CSS: Pass AbstractElement to SelectorEngine::matches
...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.
2026-04-08 10:37:05 +01:00
Jelle Raaijmakers
00397b4808 LibWeb: Keep track of elements with an anchor-name set
We maintain a registry of elements with an anchor-name so once they are
referenced for anchor positioning, we can find them with an O(1) lookup
instead of traversing the entire DOM tree.
2026-04-01 19:41:46 +01: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
Callum Law
ed2909674f LibWeb: Add computationally independent check for custom properties
Registered custom properties only accept "computationally independent"
values for their initial value
2026-03-26 01:11:39 +00:00
Luke Wilde
102c0232ac LibWeb: Add FACEs to disabled form control checks 2026-03-25 13:18:15 +00:00
Luke Wilde
4cea5d43e6 LibWeb: Queue form{Associated,Disabled}Callback where appropriate 2026-03-25 13:18:15 +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
Jelle Raaijmakers
7fed3f9801 LibWeb: Start fetching CSS image resources before document load event
Both Chromium and Gecko delay the document's load event for CSS image
resource requests (background-image, mask-image, etc). We now start
fetching CSS image resources as soon as their stylesheet is associated
with a document, rather than deferring until layout. This is done by
collecting ImageStyleValues during stylesheet parsing and initiating
their fetches when the stylesheet is added to the document.

Fixes #3448
2026-03-21 10:29:54 +01:00
Jelle Raaijmakers
9aac876a71 LibWeb: Prevent pseudo element churn for cascaded/computed/custom props
When setting the cascaded, computed or custom properties on a pseudo
element, only ensure the pseudo element exists if there's actual data to
set. Similarly, we only remove the data if the pseudo element already
exists - no need to create one just to clear it of its data immediately
after.
2026-03-20 19:33:06 +01: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
Aliaksandr Kalenik
01a7c8e424 LibWeb: Add PaintableBox::transform_rect_to_viewport()
Extract the repeated pattern of transforming a rectangle from absolute
coordinates to viewport coordinates via the accumulated visual context
into a helper method.
2026-03-11 02:31:30 +01:00
sideshowbarker
7adadedf52 LibWeb: Make DOM::validate_and_extract() preserve entire local name
Change local-name computation in DOM::validate_and_extract() to preserve
everything after the first colon in a qualified name — matching a recent
spec change made in https://github.com/whatwg/dom/pull/1455 (and
replacing a previous spec requirement to use the “strictly split”
algorithm, which resulted in throwing away any other part of the name
after any second colon the name might have).

For the qualified name “f:o:o”, this change now gives localName=“o:o”.
Otherwise, without this change, it’d instead give localName=“o”.
2026-03-10 10:37:54 +01: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
Aliaksandr Kalenik
8f7bb7dd2e LibWeb: Skip layout update for disconnected elements querying metrics
Introduce Document::update_layout_if_needed_for_node() which only calls
update_layout() when the node is connected. Use it at all call sites
that query layout metrics (offsets, client dimensions, image size, SVG
bounding box, etc.) so disconnected elements no longer trigger an
unnecessary layout.
2026-03-05 14:17:20 +01:00
Aliaksandr Kalenik
d49809cba3 LibWeb: Remove paint-only properties resolution phase
With per-paintable display list command caching now in place, the
separate paint-only properties resolution phase is no longer needed.
Resolution now happens inline during painting and its cost is amortized
since it only runs on cache miss.

Move all property resolution to point of consumption:
- is_visible() and visible_for_hit_testing() compute on the fly
- Filter resolution moved to assign_accumulated_visual_contexts()
- Border radii, outlines computed on access
- Box shadows, backdrop filter resolved inline during painting
- Background resolution moved into paint_background()
- Mask resolution moved to StackingContext::paint()
- Text fragment and SVG stroke properties resolved during painting
2026-03-04 19:35:45 +01:00
Aliaksandr Kalenik
eae94a8a46 LibWeb: Route repaint requests through paintables, not Document
Rename Document::set_needs_display() to set_needs_repaint() and make it
private. External callers must now go through Node/Paintable which
route the request to the document internally.

Fix one existing misuse in AnimationEffect that was calling
document-level set_needs_display() instead of routing through the
target element's paintable.

This is preparation for per-paintable display list command caching:
repaint requests must go through specific paintables so their cached
command lists can be invalidated.
2026-03-04 19:35:45 +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
Jelle Raaijmakers
499ec26f97 LibWeb: Remove CSS pixel offsets from ScrollState
This was arguably put in a worse place by #8162; we mostly need the
device pixel offsets from the scroll state so keep track of those and
convert back to CSS pixels when necessary (i.e. scrollbar data).
2026-02-27 08:29:29 +01: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