[Fiber] Collect Host Singleton children of Fragments - #37063
Merged
Conversation
eps1lon
force-pushed
the
sebbie/fragment-ref-host-singleton
branch
from
July 19, 2026 10:53
e676855 to
be1d940
Compare
eps1lon
force-pushed
the
sebbie/fragment-ref-host-singleton
branch
from
July 19, 2026 10:57
be1d940 to
90d2a58
Compare
eps1lon
force-pushed
the
sebbie/fragment-ref-host-singleton
branch
2 times, most recently
from
July 19, 2026 11:18
e6639af to
07807e3
Compare
eps1lon
force-pushed
the
sebbie/fragment-ref-host-singleton
branch
3 times, most recently
from
July 19, 2026 12:18
1dd089d to
e666f04
Compare
eps1lon
force-pushed
the
sebbie/fragment-ref-host-singleton
branch
2 times, most recently
from
July 19, 2026 12:34
35d15d7 to
0c91646
Compare
eps1lon
force-pushed
the
sebbie/fragment-ref-host-singleton
branch
2 times, most recently
from
July 20, 2026 20:44
237fcf9 to
8b1cf08
Compare
… HostSingleton `getFragmentParentInstanceOrContainerFiber` only treats HostRoot and HostComponent fibers as host parents. In react-dom, `<html>`, `<head>`, and `<body>` are HostSingleton fibers, so a fragment mounted directly inside `<body>` of a root created on the document resolves its host parent to the HostRoot, whose instance is the `Document` container. `dispatchEvent()` then appends a temporary text node to that instance to retarget the event, and appending a text node to a `Document` throws with a `HierarchyRequestError`. The test pins down the current throwing behavior. The fix in the next commit treats HostSingleton fibers as host parents, so the temporary event target is appended to `<body>` and the registered listener fires. Co-Authored-By: Claude Fable 5 <[email protected]>
eps1lon
force-pushed
the
sebbie/fragment-ref-host-singleton
branch
from
July 31, 2026 13:49
8b1cf08 to
4c5be47
Compare
In react-dom, `<html>`, `<head>`, and `<body>` are HostSingleton fibers, but `getFragmentParentInstanceOrContainerFiber` only matched HostRoot and HostComponent. A fragment mounted directly inside `<body>` of a document root therefore resolved its host parent to the HostRoot and operated on the `Document` container, which crashed `dispatchEvent()` when it appended the temporary event target to the document. The parent lookup now matches HostSingleton, and `getInstanceFromHostFiber` returns the singleton's `stateNode`, which is an `Instance`, so no signatures or names change thanks to the stateNode-based naming. `fiberIsPortaledIntoHost` and `isFragmentContainedByFiber` compare fibers against the parent lookup's result, so they learn the same tag to keep the host parent definition consistent. Child and sibling traversal intentionally keep recursing through singletons without collecting them; whether a fragment wrapping `<html>` should anchor on the singleton itself is a separate decision. The test from the previous commit flips from asserting the throw to asserting that the registered listener fires. Co-Authored-By: Claude Fable 5 <[email protected]>
…tons The fragment child traversal only collects HostComponent fibers (plus HostText behind a flag) and recurses through HostSingleton fibers without collecting them. For a fragment wrapping the document shell, `addEventListener` therefore attaches listeners to the elements inside `<body>` rather than to `<html>`, and `getClientRects` measures those inner elements instead of the singleton. This is inconsistent with how HostComponent children behave, where the fragment anchors on its nearest host child and does not descend into it, and with the host parent lookup, which now treats singletons as host parents. These tests pin down the current transparent behavior. The fix in the next commit collects singletons like HostComponents and aligns the commit phase accordingly. Co-Authored-By: Claude Fable 5 <[email protected]>
eps1lon
force-pushed
the
sebbie/fragment-ref-host-singleton
branch
from
July 31, 2026 14:57
4c5be47 to
66dd3b9
Compare
eps1lon
marked this pull request as ready for review
July 31, 2026 15:25
Collaborator
|
What do you think about adding HostSingleton support to |
jackpope
approved these changes
Jul 31, 2026
Fragment child and sibling traversal only collected HostComponent fibers (plus HostText behind a flag) and recursed through HostSingleton fibers. A fragment wrapping the document shell therefore anchored on the elements inside `<body>` instead of on `<html>` itself, unlike how HostComponent children behave, and inconsistent with the host parent lookup, which treats singletons as host parents. The traversal and the sibling search now collect HostSingleton fibers and stop descending into them. The deep traversal used by `focus()` still descends into singletons and attempts to focus them like any other host child, with `setFocusIfFocusable` deciding whether they can receive focus. Note that `<body>` is usually already the activeElement, so a fragment wrapping the document shell reports it as focused rather than searching its descendants. The commit phase is updated to match, so singleton children receive the same insertion and deletion effects as HostComponent children: the placement functions commit a placed singleton to its parent fragment instances and stop attributing its own children to them, `commitNewChildToFragmentInstances` accepts singleton fibers, the deletion, disappear, and reappear branches run the fragment effects for singletons, and the fragment specific host parent walks stop at any singleton rather than only at singleton scopes via a new `isFragmentInstanceHostParent` that mirrors `getFragmentParentInstanceOrContainerFiber`. The shared `isHostParent` used for placement is unchanged. This follows the review guidance on the original fragment refs PR that host tags must be included consistently across these branches or not at all. HostHoistable remains excluded everywhere since hoistables are reparented and their fiber position does not reflect their DOM position. The tests from the previous commit flip from documenting the transparent traversal to asserting that the fragment anchors on the `<html>` singleton. Co-Authored-By: Claude Fable 5 <[email protected]>
Insertion effects must attach a fragment's listeners, observers, and handles to exactly the nodes that the traversal based methods operate on, and traversal stops at a collected HostSingleton child. These tests pin down the two commit phase guards that keep those sides aligned. The first test mounts a document shell into an existing fragment with a registered listener. The placed singleton receives the listener as a new fragment child while the content inside it is not attributed to the fragment, so a click inside the shell fires the listener exactly once when it bubbles to `<html>`. This covers the placement recursion that stops propagating `parentFragmentInstances` below a singleton. The second test mounts a new element inside a fragment nested in `<body>`, with another fragment wrapping the shell from above. The inner fragment owns the new child directly and attaches its listener on insertion, while the outer fragment's child is the `<html>` singleton, so its listener is not attached to the new element and fires only once via bubbling. This covers the `commitPlacement` walk that stops collecting fragment instances once it passes a fragment host parent boundary. Both tests fail with duplicated listener calls if either guard is removed. Co-Authored-By: Claude Fable 5 <[email protected]>
With `enableLegacyFBSupport`, every click dispatched through the plugin event system is deferred by attaching a one time listener to `targetContainer.ownerDocument`. When the root container is the document itself, for example with `hydrateRoot(document)`, its `ownerDocument` is null and the deferral crashed with "Cannot read properties of null (reading 'addEventListener')" before any listeners ran. The deferral target now resolves to the container itself when it is already a `Document`. This surfaced through the fragment ref singleton tests, which are the first to dispatch click events inside a root created on the document under the www channel. Co-Authored-By: Claude Fable 5 <[email protected]>
eps1lon
force-pushed
the
sebbie/fragment-ref-host-singleton
branch
from
July 31, 2026 18:45
66dd3b9 to
6685758
Compare
jackpope
approved these changes
Jul 31, 2026
github-actions Bot
pushed a commit
that referenced
this pull request
Jul 31, 2026
Co-authored-by: Claude Fable 5 <[email protected]> DiffTrain build for [3a717e4](3a717e4)
github-actions Bot
pushed a commit
that referenced
this pull request
Jul 31, 2026
Co-authored-by: Claude Fable 5 <[email protected]> DiffTrain build for [3a717e4](3a717e4)
github-actions Bot
pushed a commit
to code/lib-react
that referenced
this pull request
Aug 1, 2026
Co-authored-by: Claude Fable 5 <[email protected]> DiffTrain build for [3a717e4](react@3a717e4)
github-actions Bot
pushed a commit
to code/lib-react
that referenced
this pull request
Aug 1, 2026
Co-authored-by: Claude Fable 5 <[email protected]> DiffTrain build for [3a717e4](react@3a717e4)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Revisits the decision to exclude Host Singletons when collecting/traversing Fragment descendants for Fragment instance methods. Host Singletons were specifically excluded in #32465 (comment).
However, this leads to errors when calling
dispatchEventon empty singletons (e.g.<body>) as children of Fragment instances wheredispatchEventis called.From a type perspective, we can safely start collecting
HostSingletonin addition toHostComponent. Both have anInstancein theirstateNode. It should also be sound from a hierarchy perspective: React doesn't hoist these elements into a different place in the document as opposed toHostHoistablewhich would be moved to a different spot.