Commit Graph

1365 Commits

Author SHA1 Message Date
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
Psychpsyo
44ef574902 LibWeb: Properly set visibility state for nested documents
This cannot happen inside the Make Active algorithm, since that gets
called during document creation, which commonly happens before the
document's navigable is created.

Aligns us with a recent spec change and rids us of some AD_HOC
behavior.
2026-04-01 17:26:46 +02:00
Psychpsyo
947fec9bcc LibWeb: Mark startViewTransition() as experimental 2026-04-01 13:41:36 +01:00
Aliaksandr Kalenik
4985dabf3d LibWeb: Replace cached navigable with Navigable-maintained back-pointer
Now that Navigable directly owns its active document (m_active_document)
we can have Navigable maintain a back-pointer on Document instead of
using the old cache-with-validation pattern that fell back to a linear
scan of all navigables via navigable_with_active_document().
2026-04-01 11:51:43 +02:00
Aliaksandr Kalenik
2645695fdd LibWeb: Make Navigable directly own its active document
Previously, the active document's lifecycle was bound to
SessionHistoryEntry via DocumentState. The ownership chain was:
  Navigable → SessionHistoryEntry → DocumentState → Document

This made it impossible to move SessionHistoryEntry to the UI process
(which cannot own DOM::Document). This commit decouples the two by
giving Navigable a direct m_active_document field that serves as the
authoritative source for active_document().

- Navigable owns m_active_document directly; active_document() reads
  from it instead of going through the active session history entry.

- DocumentState no longer holds a Document pointer. Instead, it stores
  a document_id for "same document?" checks. Same-document navigations
  share a DocumentState and thus the same document_id, while
  cross-document navigations create a new DocumentState with a new ID.

- A pending_document parameter is threaded through
  finalize_a_cross_document_navigation → apply_the_push_or_replace →
  apply_the_history_step so the newly created document reaches
  activation without being stored on DocumentState.

- For traversal, the population output delivers the document.
  A resolved_document is computed per continuation from either the
  pending document, the population output, or the current active
  document (for same-document traversals).
2026-04-01 11:51:43 +02:00
Aliaksandr Kalenik
f3ea882d6e LibWeb: Remove "signal to continue SHTQ" from document loading
This promise was previously used to signal the session history traversal
queue that it could continue processing, but is no longer needed.
2026-04-01 06:47:59 +02:00
Shannon Booth
0086a7899d LibWeb: Remove some uneeded navigation error propogation
We should not have any errors to propogate down these paths.
2026-04-01 04:41:11 +02:00
Psychpsyo
269aaeea21 LibWeb: Move standalone SVG document layout handling out of BFC
I'm simplifying the BFC code in preparation for multicol layout.
2026-03-31 10:49:15 +02:00
Aliaksandr Kalenik
2a69fd4c52 LibWeb: Replace spin_until in apply_the_history_step with state machine
Replace the blocking spin_processing_tasks_with_source_until calls
in apply_the_history_step_after_unload_check() with an event-driven
ApplyHistoryStepState GC cell that tracks 5 phases, following the
same pattern used by CheckUnloadingCanceledState.

Key changes:
- Introduce ApplyHistoryStepState with phases:
  WaitingForDocumentPopulation, ProcessingContinuations,
  WaitingForChangeJobCompletion, WaitingForNonChangingJobs and Completed
- Add on_complete callbacks to apply_the_push_or_replace_history_step,
  finalize_a_same_document_navigation,
  finalize_a_cross_document_navigation, and
  update_for_navigable_creation_or_destruction
- Remove spin_until from Document::open()
- Use null-document tasks for non-changing navigable updates and
  document unload/destroy to avoid stuck tasks when documents become
  non-fully-active
- Defer completely_finish_loading when document has no navigable yet,
  and re-trigger post-load steps in activate_history_entry for documents
  that completed loading before activation

Co-Authored-By: Shannon Booth <shannon@serenityos.org>
2026-03-31 09:47:59 +02:00
Adam Colvin
f79c3a4bfe LibWeb: Implement exportparts forwarding in part element map
Implement the forwarded part names step of the CSS Shadow Parts
spec in ShadowRoot::calculate_part_element_map(). When a shadow
host has an exportparts attribute, the inner shadow root's part
element map is consulted and matching parts are added to the
outer shadow root's map under the exported name.

This supports both shorthand same-name forwarding (exportparts=
"foo") and renamed forwarding (exportparts="foo: bar"), and
chains transitively through nested shadow boundaries via
recursive part_element_map() calls.

Fixes 4 WPT tests: simple-forward, simple-forward-shorthand,
double-forward, and precedence-part-vs-part.
2026-03-30 16:47:34 +01:00
Tim Ledbetter
f601c0cd4b LibWeb: Disable Navigation API after document.open() on about:blank
This matches the behavior of other engines.
2026-03-29 16:39:03 +02:00
Aliaksandr Kalenik
76d9cc4baf LibWeb: Replace spin_until in execute_script with deferred parser start
HTMLScriptElement::execute_script() and SVGScriptElement had spin_until
calls waiting for ready_to_run_scripts to become true. The race exists
because load_html_document() resolves the session history signal and
starts the parser in the same deferred_invoke — so the parser can hit a
<script> before update_for_history_step_application() sets the flag.

Instead of spinning, defer parser->run() until the document is ready.
Document gains a m_deferred_parser_start callback that is invoked when
set_ready_to_run_scripts() is called. The callback is cleared before
invocation to avoid reentrancy issues (parser->run() can synchronously
execute scripts). All three document loading paths (HTML, XML, text)
now check ready_to_run_scripts before starting the parser and defer if
needed.

create_document_for_inline_content() (used for error pages) now calls
set_ready_to_run_scripts() before mutating the document, ensuring the
invariant holds for all parser paths.

The spin_until calls are replaced with VERIFY assertions.
2026-03-29 01:05:35 +01:00
Aliaksandr Kalenik
df96b69e7a LibWeb: Replace spin_until in HTMLParser::the_end() with state machine
HTMLParser::the_end() had three spin_until calls that blocked the event
loop: step 5 (deferred scripts), step 7 (ASAP scripts), and step 8
(load event delay). This replaces them with an HTMLParserEndState state
machine that progresses asynchronously via callbacks.

The state machine has three phases matching the three spin_until calls:
- WaitingForDeferredScripts: loops executing ready deferred scripts
- WaitingForASAPScripts: waits for ASAP script lists to empty
- WaitingForLoadEventDelay: waits for nothing to delay the load event

Notification triggers re-evaluate the state machine when conditions
change: HTMLScriptElement::mark_as_ready, stylesheet unblocking in
StyleElementBase/HTMLLinkElement, did_stop_being_active_document, and
DocumentLoadEventDelayer decrements. NavigableContainer state changes
(session history readiness, content navigable cleared, lazy load flag)
also trigger re-evaluation of the load event delay check.

Key design decisions and why:

1. Microtask checkpoint in schedule_progress_check(): The old spin_until
   called perform_a_microtask_checkpoint() before checking conditions.
   This is critical because HTMLImageElement::update_the_image_data step
   8 queues a microtask that creates the DocumentLoadEventDelayer.
   Without the checkpoint, check_progress() would see zero delayers and
   complete before images start delaying the load event.

2. deferred_invoke in schedule_progress_check():
   I tried Core::Timer (0ms), queue_global_task, and synchronous calls.
   Timers caused non-deterministic ordering with the HTML event loop's
   task processing timer, leading to image layout tests failing (wrong
   subtest pass/fail patterns). Synchronous calls fired too early during
   image load processing before dimensions were set, causing 0-height
   images in layout tests. queue_global_task had task ordering issues
   with the session history traversal queue. deferred_invoke runs after
   the current callback returns but within the same event loop pump,
   giving the right balance.

3. Navigation load event guard (m_navigation_load_event_guard): During
   cross-document navigation, finalize_a_cross_document_navigation step
   2 calls set_delaying_load_events(false) before the session history
   traversal activates the new document. This creates a transient state
   where the parent's load event delay check sees the about:blank (which
   has ready_for_post_load_tasks=true) as the active document and
   completes prematurely.
2026-03-28 23:14:55 +01:00
Sam Atkins
ed6a5f25a0 LibWeb: Implement scoped custom element registries 2026-03-27 19:49:55 +00:00
Sam Atkins
90c39c214d LibWeb/DOM: Bring create_element() closer to spec
I believe the test regressions were previous false positives, that now
fail because we partially implement things instead of not implementing
them at all.
2026-03-27 19:49:55 +00:00
Sam Atkins
71626ac58c LibWeb: Update "look up a custom element definition" algorithm
This now takes a registry directly. As a temporary stop-gap, the old
method on Document is still here and calls the new one.

We do get a test regression:

"Creating an element in a cloned document and inserting into the
document must not enqueue a custom element upgrade reaction" in
"custom-elements/upgrading.html".

Possibly a spec bug?
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
Sam Atkins
ecbd846272 LibWeb/DOM: Implement DocumentOrShadowRoot.customElementRegistry
Step towards scoped custom element registries.
2026-03-27 19:49:55 +00:00
Andreas Kling
d7bf9d3898 LibRegex: Remove the legacy C++ ECMA-262 engine
Delete the old C++ ECMA-262 parser, optimizer, and matcher now that all
in-tree users compile and execute through `ECMAScriptRegex`.

Stop building the legacy engine, remove its source files and the
POSIX-only fuzzers that depended on it, and update the remaining
LibRegex tests to target the Rust-backed facade instead of the deleted
implementation. Clean up the last includes, comments, and helper paths
that only existed to support the old backend.

After this commit LibRegex has a single ECMAScript engine in-tree,
eliminating duplicated maintenance and unifying future regex work.
2026-03-27 17:32:19 +01:00
Callum Law
b86377b9dc LibWeb: Clamp CSS <integer> value to i32 at parse time
This matches the behavior of other browsers. Previously we implemented
this at used-value time for z-index specifically.
2026-03-26 12:30:01 +01: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
Psychpsyo
bd91567863 Meta: Ensure that idl files link to draft specs 2026-03-25 16:02:04 +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
Luke Wilde
cfd795f907 LibWeb+IDLGenerators: Support nullable union types 2026-03-25 13:18:15 +00:00
Jelle Raaijmakers
16dffe39a0 LibWeb: Invalidate pseudo-elements on hover even when not yet created
The hover invalidation code only tried matching ::before/::after
selectors when has_pseudo_element() returned true, which requires an
existing layout node. A pseudo-element that doesn't exist yet (because
its content is only set by a hover rule) has no layout node, so the
match was skipped and hovering never triggered a style recompute.
Always try ::before/::after selectors during hover invalidation.
2026-03-24 10:23:35 -04:00
Shannon Booth
45a87b1dbe LibWeb: Update document URL to the entry document URL in document.open()
When document.open() is called from another window, the opened
document's URL should be updated to match the calling document's URL.
2026-03-23 09:00:39 +01:00
Shannon Booth
84b41f6270 LibWeb: Use entry realm in Document.open
While they are aiming to remove entry realm in the HTML spec,
apparently it is still needed for Document.open for web
compatability at this stage.
2026-03-23 09:00:39 +01:00
Andreas Kling
522683cf91 LibWeb: Use binary search for IntersectionObserver threshold index 2026-03-22 14:09:22 -05:00
Andreas Kling
e2d5d72f55 LibWeb: Reduce allocations in IntersectionObserver hot path
Pre-compute per-observer values (root node, root paintable, root
bounds, is_implicit_root, root_is_element) once before the per-target
loop instead of recomputing them for each target.

Add intersection_root_node() which returns GC::Ref<DOM::Node> without
creating GC::Root wrappers. Previously, intersection_root() was called
multiple times per target (in the skip condition check, in
compute_intersection, and again in root_intersection_rectangle inside
compute_intersection), each call creating GC::Root objects that require
heap allocation via new RootImpl.

Pass the pre-computed root bounds and root paintable into
compute_intersection instead of having it call
root_intersection_rectangle() and intersection_root() internally.

For an observer watching N targets, this eliminates roughly 4N heap
allocations per frame.
2026-03-22 14:09:22 -05:00
Andreas Kling
9abb7e4517 LibWeb: Apply scroll margin in IntersectionObserver compute_intersection
Per the spec, the observer's [[scrollMargin]] should be applied to
each scroll container's scrollport when walking the containing block
chain. This expands the effective clip rect, allowing targets to be
detected as intersecting before they actually enter the visible area
of the scroll container.

Add a scroll_margin_values() accessor to IntersectionObserver so the
raw LengthPercentage values can be used during intersection computation.
The scroll margin is applied by inflating the scroll container's
padding box rect before clipping the intersection rect against it.
2026-03-22 14:09:22 -05:00
Andreas Kling
229eba9a06 LibWeb: Clip against scroll containers in IntersectionObserver
Implement the containing block chain traversal in compute_intersection
(steps 2-3 of the spec algorithm). Walk from the target's paintable box
up through the containing block chain, clipping the intersection rect
against each ancestor that has a content clip (overflow != visible).

This fixes the case where a target is inside an overflow:hidden or
overflow:clip container and pushed below the visible area. Previously,
the intersection ratio was incorrectly non-zero because we only
intersected with the root bounds (viewport), not intermediate
scroll containers.

Also update the scroll container clipping tests to verify the
intersection ratio (which is what compute_intersection affects)
rather than isIntersecting (which per spec is based on targetRect
vs rootBounds, not the clipped intersection rect).
2026-03-22 14:09:22 -05:00
Andreas Kling
bbadb38881 LibWeb: Fix IntersectionObserver skip condition for explicit roots
The boolean logic in steps 2-3 of "run the update intersection
observations steps" was incorrect. The code used || between the two
negated skip conditions instead of &&, and the first sub-expression
had its negation inverted.

This caused intersection geometry to be computed (and callbacks fired)
for targets that are not descendants of an explicit element root,
as long as they spatially overlapped the root's bounding rect.

The spec says:
- Step 2: Skip if root is not implicit AND target not in same document.
- Step 3: Skip if root is Element AND target not descendant of root.

The enter condition is NOT(skip2) AND NOT(skip3), but we had
NOT(skip2) OR NOT(skip3) with a wrong negation in the first part.
2026-03-22 14:09:22 -05: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
Andreas Kling
2ab80c9e47 LibWeb: Ensure hosted document layout is up to date before painting
When painting a navigable container (iframe/object), the hosted
document's layout may have been invalidated during the parent
document's layout pass via did_set_content_size -> set_viewport_size
-> set_needs_layout_update.

If the hosted document is not in the event loop's docs list (e.g.,
it was created during a rAF callback after the list was built), its
layout would never be re-updated, causing a VERIFY failure in
Node::paintable() which asserts layout_is_up_to_date().

Fix this by calling update_layout() on the hosted document in
NavigableContainerViewportPaintable::paint() before accessing its
paintable. This is a no-op when layout is already current.

Fixes #8297.
2026-03-20 22:55:44 -05:00
Andreas Kling
194f17928f LibWeb: Cache parsed selectors for querySelectorAll/querySelector
Add a per-Document cache that maps selector strings to their parsed
SelectorList results. This avoids re-tokenizing and re-parsing the
same selector strings on every call, which is a huge win for pages
that repeatedly call querySelectorAll with the same selectors.

On microsoft.com, where they spam querySelectorAll with the same two
selectors, profiling showed 62% of main thread time was spent
re-parsing selectors. This cache eliminates that entirely for
repeated queries.
2026-03-20 18:32:32 -05:00
Andreas Kling
d8fc4e9ee2 LibWeb: Avoid copying SimpleSelectors in contains_named_namespace()
The loop variable was being copied by value instead of by const
reference, causing unnecessary copies of every SimpleSelector during
querySelectorAll/querySelector calls.
2026-03-20 18:32:32 -05: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
Andreas Kling
3518efd71c LibJS+LibWeb: Port remaining callers to Rust pipeline
Port all remaining users of the C++ Parser/Lexer/Generator to
use the Rust pipeline instead:

- Intrinsics: Remove C++ fallback in parse_builtin_file()
- ECMAScriptFunctionObject: Remove C++ compile() fallback
- NativeJavaScriptBackedFunction: Remove C++ compile() fallback
- EventTarget: Port to compile_dynamic_function
- WebDriver/ExecuteScript: Port to compile_dynamic_function
- LibTest/JavaScriptTestRunner.h: Remove Parser/Lexer includes
- FuzzilliJs: Remove unused Parser/Lexer includes

Also remove the dead Statement-based template instantiation of
async_block_start/async_function_start.
2026-03-19 21:55:10 -05:00
Jelle Raaijmakers
c8baa6e179 LibWeb: Remove tasks for destroyed documents instead of running them
Previously, destroyed-document tasks were forced to be runnable to
prevent them from leaking in the task queue. Instead, discard them
during task selection so their callbacks never run with stale state.

This used to cause issues with a couple of `spin_until()`s in the past,
but since we've removed some of them that had to do with the document
lifecycle, let's see if we can stick closer to the spec now.
2026-03-19 15:24:46 -05: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
63820376ef LibWeb: Rename the doc variable in exit_fullscreen() to match spec 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
a1c9fbacc6 LibWeb: Only fire resize at Window if the viewport size changed
...and similarly, only fire resize at VisualViewport if its state has
changed.

Before, we would fire both if either changed.
2026-03-17 18:58:37 -05:00
Zaggy1024
7a45b50e8d LibWeb: Record the last viewport state in Document::make_active()
This ensures that we fire a resize event if the viewport size changes
between the document becoming active and the first rendering
opportunity.
2026-03-17 18:58:37 -05:00
Zaggy1024
ee45cdfb09 LibWeb: Refactor pointer event handling/dispatch to work closer to spec
Pieces of the down, move, and up handlers are moved to separate
functions. Some part actually have specs, so the ones I've found thus
far have been brought in to make things more spec-aligned. A lot of
FIXMEs are added for things that the spec mentions or implies.

Pointer events are intended to be handled per pointer device, but this
still treats them the same as legacy mouse events. However, the PREVENT
MOUSE EVENT flag is implemented to block legacy mouse events for the
duration of a drag.

Behavior changes should be minimal.

One notable change is that auxclick is now fired for all non-primary
buttons, which matches the spec and other browsers.
2026-03-17 04:01:29 -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