## Summary
Experimentation has completed for this at Meta and we've observed
positive impact on key React Native surfaces.
## How did you test this change?
yarn flow fabric
As titled. This adds dev-only debugging information to Fizz / Flight
that could be used for tracking Promise's stack traces in "suspended by"
section of DevTools.
When we report an error we typically log the owner stack of the thing
that caught the error. Similarly we restore the `console.createTask`
scope of the catching component when we call `reportError` or
`console.error`.
We also have a special case if something throws during reconciliation
which uses the Server Component task as far as we got before we threw.
https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactChildFiber.js#L1952-L1960
Chrome has since fixed it (on our request) that the Error constructor
snapshots the Task at the time the constructor was created and logs that
in `reportError`. This is a good thing since it means we get a coherent
stack. Unfortunately, it means that the fake Errors that we create in
Flight Client gets a snapshot of the task where they were created so
when they're reported in the console they get the root Task instead of
the Task of the handler of the error.
Ideally we'd transfer the Task from the server and restore it. However,
since we don't instrument the Error object to snapshot the owner and we
can't read the native Task (if it's even enabled on the server) we don't
actually have a correct snapshot to transfer for a Server Component
Error. However, we can use the parent's task for where the error was
observed by Flight Server and then encode that as a pseudo owner of the
Error.
Then we use this owner as the Task which the Error is created within.
Now the client snapshots that Task which is reported by `reportError` so
now we have an async stack for Server Component errors again. (Note that
this owner may differ from the one observed by `captureOwnerStack` which
gets the nearest Server Component from where it was caught. We could
attach the owner to the Error object and use that owner when calling
`onCaughtError`/`onUncaughtError`).
Before:
<img width="911" height="57" alt="Screenshot 2025-09-10 at 10 57 54 AM"
src="https://github.com/user-attachments/assets/0446ef96-fad9-4e17-8a9a-d89c334233ec"
/>
After:
<img width="910" height="128" alt="Screenshot 2025-09-10 at 11 06 20 AM"
src="https://github.com/user-attachments/assets/b30e5892-cf40-4246-a588-0f309575439b"
/>
Similarly, there are Errors and warnings created by ChildFiber itself.
Those execute in the scope of the general render of the parent Fiber.
They used to get the scope of the nearest client component parent (e.g.
div in this case) but that's the parent of the Server Component. It
would be too expensive to run every level of reconciliation in its own
task optimistically, so this does it only when we know that we'll throw
or log an error that needs this context. Unfortunately this doesn't
cover user space errors (such as if an iterable errors).
Before:
<img width="903" height="298" alt="Screenshot 2025-09-10 at 11 31 55 AM"
src="https://github.com/user-attachments/assets/cffc94da-8c14-4d6e-9a5b-bf0833b8b762"
/>
After:
<img width="1216" height="252" alt="Screenshot 2025-09-10 at 11 50
54 AM"
src="https://github.com/user-attachments/assets/f85f93cf-ab73-4046-af3d-dd93b73b3552"
/>
<img width="412" height="115" alt="Screenshot 2025-09-10 at 11 52 46 AM"
src="https://github.com/user-attachments/assets/a76cef7b-b162-4ecf-9b0a-68bf34afc239"
/>
This adds `experimental_scrollIntoView(alignToTop)`. It doesn't yet
support `scrollIntoView(options)`.
Cases:
- No host children: Without host children, we represent the virtual
space of the Fragment by attempting to scroll to the nearest edge by
using its siblings. If the preferred sibling is not found, we'll try the
other side, and then the parent.
- 1 or more host children: In order to handle the case of children
spread between multiple scroll containers, we scroll to each child in
reverse order based on the `alignToTop` flag.
Due to the complexity of multiple scroll containers and dealing with
portals, I've added this under a separate feature flag with an
experimental prefix. We may stabilize it along with the other APIs, but
this allows us to not block the whole feature on it.
This PR was previously implementing a much more complex approach to
handling multiple scroll containers and portals. We're going to start
with the simple loop and see if we can find any concrete use cases where
that doesn't suffice. 01f31d4301 is the
diff between approaches here.
After an easy couple version with #34252, this version is less flexible
(and safer) on inferring exported types mainly.
We require to annotate some exported types to differentiate between
`boolean` and literal `true` types, etc.
This is intended to be used by various client side resources where the
transfer size is interesting to know how it'll perform in various
network conditions. Not intended to be added by the server.
For now it's only added internally by DevTools itself on img/css but
I'll add it from Flight Client too in a follow up.
This now shows this as the "transfer size" which is the encoded body
size + headers/overhead. Where as the "fileSize" that I add to images is
the decoded body size, like what you'd see on disk. This is what Chrome
shows so it's less confusing if you compare Network tab and this view.
Stacked on https://github.com/facebook/react/pull/34069
Same basic semantics as the react-dom for determining document position
of a Fragment compared to a given node. It's simpler here because we
don't have to deal with inserted nodes or portals. So we can skip a
bunch of the validation logic.
The logic for handling empty fragments is the same so I've split out
`compareDocumentPositionForEmptyFragment` into a shared module. There
doesn't seem to be a great place to put shared DOM logic between Fabric
and DOM configs at the moment. There may be more of this coming as we
add more and more DOM APIs to RN.
For testing I've written Fantom tests internally which pass the basic
cases on this build. The renderer we have configured for Fabric tests in
the repo doesn't support the Element APIs we need like
`compareDocumentPosition`.
Same as #34166 but for Suspensey images.
The trick here is to check the `SuspenseyImagesMode` since not all
versions of React and not all subtrees will have Suspensey images
enabled yet.
The other trick is to read back from `currentSrc` to get the image url
we actually resolved to in this case. Similar to how for Suspensey CSS
we check if the media query would've matched.
<img width="591" height="205" alt="Screenshot 2025-08-11 at 9 32 56 PM"
src="https://github.com/user-attachments/assets/ac98785c-d3e0-407c-84e0-c27f86c0ecac"
/>
We need to track that Suspensey CSS (Host Resources) can contribute to
the loading state. We can pick up the start/end time from the
Performance Observer API since we know which resource was loaded.
If DOM nodes are not filtered there's a link to the `<link>` instance.
The `"awaited by"` stack is the callsite of the JSX creating the
`<link>`.
<img width="591" height="447" alt="Screenshot 2025-08-11 at 1 35 21 AM"
src="https://github.com/user-attachments/assets/63af0ca9-de8d-4c74-a797-af0a009b5d73"
/>
Inspecting the link itself:
<img width="592" height="344" alt="Screenshot 2025-08-11 at 1 31 43 AM"
src="https://github.com/user-attachments/assets/89603dbc-6721-4bbf-8b58-6010719b29e3"
/>
In this approach I only include it if the page currently matches the
media query. It might contribute in some other scenario but we're not
showing every possible state but every possible scenario that might
suspend if timing changes in the current state.
This creates a debug info object for the React.lazy call when it's
called on the client. We have some additional information we can track
for these since they're created by React earlier.
We can track the stack trace where `React.lazy` was called to associate
it back to something useful. We can track the start time when we
initialized it for the first time and the end time when it resolves. The
name from the promise if available.
This data is currently only picked up in child position and not
component position. The component position is in a follow up.
<img width="592" height="451" alt="Screenshot 2025-08-08 at 2 49 33 PM"
src="https://github.com/user-attachments/assets/913d2629-6df5-40f6-b036-ae13631379b9"
/>
This begs for ignore listing in the front end since these stacks aren't
filtered on the server.
The name prop will be used in the Suspense tab to help identity a
boundary. Activity will also allow names. A custom component can be
identified by the name of the component but built-ins doesn't have that.
This PR adds it to the Components Tree View as well since otherwise you
only have the key to go on. Normally we don't add all the props to avoid
making this view too noisy but this is an exception along with key to
help identify a boundary quickly in the tree.
Unlike the SuspenseNode store, this wouldn't ever have a name inferred
by owner since that kind of context already exists in this view.
<img width="600" height="161" alt="Screenshot 2025-08-08 at 1 20 36 PM"
src="https://github.com/user-attachments/assets/fe50d624-887a-4b9d-9186-75f131f83195"
/>
I also made both the key and name prop searchable.
<img width="608" height="206" alt="Screenshot 2025-08-08 at 1 32 27 PM"
src="https://github.com/user-attachments/assets/d3502d9c-7614-45fc-b973-57f06dd9cddc"
/>
Stacked on #34016.
This is using the same thing we already do for the performance track to
provide a description of the I/O based on the content of the resolved
Promise. E.g. a Response's URL.
<img width="375" height="388" alt="Screenshot 2025-07-28 at 1 09 49 AM"
src="https://github.com/user-attachments/assets/f3fdc40f-4e21-4e83-b49e-21c7ec975137"
/>
There's a lot of overlap between `enableComponentPerformanceTrack` and
`enableAsyncDebugInfo` because they both rely on timing information. The
former is mainly emit timestamps for how long server components and
awaits took. The latter how long I/O took.
`enableAsyncDebugInfo` is currently primarily for the component
performance track but its meta data is useful for other debug tools too.
This promotes that flag to stable.
However, `enableComponentPerformanceTrack` needs more work due to
performance concerns with Chrome DevTools so I need to separate them.
This keeps doing most of the timing tracking on the server but doesn't
emit the per-server component time stamps when
`enableComponentPerformanceTrack` is false.
This resolves an outstanding issue where it was possible for debug info
and console logs to become out of order if they up blocked. E.g. by a
future reference or a client reference that hasn't loaded yet. Such as
if you console.log a client reference followed by one that doesn't. This
encodes the order similar to how the stream chunks work.
This also blocks the main chunk from resolving until the last debug info
has fully loaded, including future references and client references.
This also ensures that we could send some of that data in a different
stream, since then it can come out of order.
We use the stack of a Promise as the start of the I/O instead of the
actual I/O since that can symbolize the start of the operation even if
the actual I/O is batched, deduped or pooled. It can also group multiple
I/O operations into one.
We want the deepest possible Promise since otherwise it would just be
the Component's Promise.
However, we don't really need deeper than the boundary between first
party and third party. We can't just take the outer most that has third
party things on the stack though because third party can have callbacks
into first party and then we want the inner one. So we take the inner
most Promise that depends on I/O that has a first party stack on it.
The realization is that for the purposes of determining whether we have
a first party stack we need to ignore async stack frames. They can
appear on the stack when we resume third party code inside a resumption
frame of a first party stack.
<img width="832" alt="Screenshot 2025-07-08 at 6 34 25 PM"
src="https://github.com/user-attachments/assets/1636f980-be4c-4340-ad49-8d2b31953436"
/>
---------
Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>
Stacked on #33658 and #33659.
If we detect that a component is receiving only deeply equal objects,
then we highlight it as potentially problematic and worth looking into.
<img width="1055" alt="Screenshot 2025-06-27 at 4 15 28 PM"
src="https://github.com/user-attachments/assets/e96c6a05-7fff-4fd7-b59a-36ed79f8e609"
/>
It's fairly conservative and can bail out for a number of reasons:
- We only log it on the first parent that triggered this case since
other children could be indirect causes.
- If children has changed then we bail out since this component will
rerender anyway. This means that it won't warn for a lot of cases that
receive plain DOM children since the DOM children won't themselves get
logged.
- If the component's total render time including children is 100ms or
less then we skip warning because rerendering might not be a big deal.
- We don't warn if you have shallow equality but could memoize the JSX
element itself since we don't typically recommend that and React
Compiler doesn't do that. It only warns if you have nested objects too.
- If the depth of the objects is deeper than like the 3 levels that we
print diffs for then we wouldn't warn since we don't know if they were
equal (although we might still warn on a child).
- If the component had any updates scheduled on itself (e.g. setState)
then we don't warn since it would rerender anyway. This should really
consider Context updates too but we don't do that atm. Technically you
should still memoize the incoming props even if you also had unrelated
updates since it could apply to deeper bailouts.
<img width="634" alt="Screenshot 2025-06-27 at 1 13 20 PM"
src="https://github.com/user-attachments/assets/dc8c488b-4a23-453f-918f-36b245364934"
/>
We have to be careful with performance in DEV. It can slow down DX since
these are ran whether you're currently running a performance trace or
not. It can also show up as misleading since these add time to the
"Remaining Effects" entry.
I'm not adding all props to the entries. Instead, I'm only adding the
changed props after diffing and none for initial mount. I'm trying to as
much as possible pick a fast path when possible. I'm also only logging
this for the "render" entries and not the effects. If we did something
for effects, it would be more like checking with dep changed.
This could still have a negative effect on dev performance since we're
now also using the slower `performance.measure` API when there's a diff.
`react-stack-bottom-frame` -> `react_stack_bottom_frame`.
This survives `@babel/plugin-transform-function-name`, but now frames
will be displayed as `at Object.react_stack_bottom_frame (...)` in V8.
Checks that were relying on exact function name match were updated to
use either `.indexOf()` or `.includes()`
For backwards compatibility, both React DevTools and Flight Client will
look for both options. I am not so sure about the latter and if React
version is locked.
This is using the same trick as #30798 but for runtime code too. It's
essential zero cost.
This lets us include a source location for parent stacks of Server
Components when it has an owned child's location. Either from JSX or
I/O.
Ironically, a Component that throws an error will likely itself not get
the stack because it won't have any JSX rendered yet.
Stacked on #33588, #33589 and #33590.
This lets us automatically show the resolved value in the UI.
<img width="863" alt="Screenshot 2025-06-22 at 12 54 41 AM"
src="https://github.com/user-attachments/assets/a66d1d5e-0513-4767-910c-5c7169fc2df4"
/>
We can also show rejected I/O that may or may not have been handled with
the error message.
<img width="838" alt="Screenshot 2025-06-22 at 12 55 06 AM"
src="https://github.com/user-attachments/assets/e0a8b6ae-08ba-46d8-8cc5-efb60956a1d1"
/>
To get this working we need to keep the Promise around for longer so
that we can access it once we want to emit an async sequence. I do this
by storing the WeakRefs but to ensure that the Promise doesn't get
garbage collected, I keep a WeakMap of Promise to the Promise that it
depended on. This lets the VM still clean up any Promise chains that
have leaves that are cleaned up. So this makes Promises live until the
last Promise downstream is done. At that point we can go back up the
chain to read the values out of them.
Additionally, to get the best possible value we don't want to get a
Promise that's used by internals of a third-party function. We want the
value that the first party gets to observe. To do this I had to change
the logic for which "await" to use, to be the one that is the first
await that happened in user space. It's not enough that the await has
any first party at all on the stack - it has to be the very first frame.
This is a little sketchy because it relies on the `.then()` call or
`await` call not having any third party wrappers. But it gives the best
object since it hides all the internals. For example when you call
`fetch()` we now log that actual `Response` object.
This adds better support for serializing class instances as Debug
values.
It adds a new marker on the object `{ "": "$P...", ... }` which
indicates which constructor's prototype to use for this object's
prototype. It doesn't encode arbitrary prototypes and it doesn't encode
any of the properties on the prototype. It might get some of the
properties from the prototype by virtue of `toString` on a `class`
constructor will include the whole class's body.
This will ensure that the instance gets the right name in logs.
Additionally, this now also invokes getters if they're enumerable on the
prototype. This lets us reify values that can only be read from native
classes.
---------
Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
## Summary
Make this flag dynamic, so it can be controlled internally.
## How did you test this change?
Build, observe that `console.timeStamp` is only present in FB artifacts
and `enableComponentPerformanceTrack` is referenced.
## Summary
Enables the `enableEagerAlternateStateNodeCleanup` feature flag for all
variants, while maintaining the `__VARIANT__` for the internal React
Native flavor for backtesting reasons.
## How did you test this change?
```
$ yarn test
```
We want to change the defaults for `revealOrder` and `tail` on
SuspenseList. This is an intermediate step to allow experimental users
to upgrade.
To explicitly specify these options I added `revealOrder="independent"`
and `tail="visible"`.
I then added warnings if `undefined` or `null` is passed. You must now
always explicitly specify them. However, semantics are still preserved
for now until the next step.
We also want to change the rendering order of the `children` prop for
`revealOrder="backwards"`. As an intermediate step I first added
`revealOrder="unstable_legacy-backwards"` option. This will only be
temporary until all users can switch to the new `"backwards"` semantics
once we flip it in the next step.
I also clarified the types that the directional props requires iterable
children but not iterable inside of those. Rows with multiple items can
be modeled as explicit fragments.
Stacked on #33395.
This lets us keep track of which environment this was fetched and
awaited.
Currently the IO and await is in the same environment. It's just kept
when forwarded. Once we support forwarding information from a Promise
fetched from another environment and awaited in this environment then
the await can end up being in a different environment.
There's a question of when the await is inside Flight itself such as
when you return a promise fetched from another environment whether that
should mean that the await is in the current environment. I don't think
so since the original stack trace is the best stack trace. It's only if
you `await` it in user space in this environment first that this might
happen and even then it should only be considered if there wasn't a
better await earlier or if reading from the other environment was itself
I/O.
The timing of *when* we read `environmentName()` is a little interesting
here too.
Stacked on #33394.
This lets us create async stack traces to the owner that was in context
when the I/O was started or awaited.
<img width="615" alt="Screenshot 2025-06-01 at 12 31 52 AM"
src="https://github.com/user-attachments/assets/6ff5a146-33d6-4a4b-84af-1b57e73047d4"
/>
This owner might not be the immediate closest parent where the I/O was
awaited.
Stacked on #33390.
The stack trace doesn't include the thing you called when calling into
ignore listed content. We consider the ignore listed content
conceptually the abstraction that you called that's interesting.
This extracts the name of the first ignore listed function that was
called from user space. For example `"fetch"`. So we can know what kind
of request this is.
This could be enhanced and tweaked with heuristics in the future. For
example, when you create a Promise yourself and call I/O inside of it
like my `delay` examples, then we use that Promise as the I/O node but
its stack doesn't have the actual I/O performed. It might be better to
use the inner I/O node in that case. E.g. `setTimeout`. Currently I pick
the name from the first party code instead - in my example `delay`.
Another case that could be improved is the case where your whole
component is third-party. In that case we still log the I/O but it has
no context about what kind of I/O since the whole stack is ignored it
just gets the component name for example. We could for example look at
the first name that is in a different package than the package name of
the ignored listed component. So if
`node_modules/my-component-library/index.js` calls into
`node_modules/mysql/connection.js` then we could use the name from the
inner.
This lets us track what data each Server Component depended on. This
will be used by Performance Track and React DevTools.
We use Node.js `async_hooks`. This has a number of downside. It is
Node.js specific so this feature is not available in other runtimes
until something equivalent becomes available. It's [discouraged by
Node.js docs](https://nodejs.org/api/async_hooks.html#async-hooks). It's
also slow which makes this approach only really viable in development
mode. At least with stack traces. However, it's really the only solution
that gives us the data that we need.
The [Diagnostic
Channel](https://nodejs.org/api/diagnostics_channel.html) API is not
sufficient. Not only is many Node.js built-in APIs missing but all
libraries like databases are also missing. Were as `async_hooks` covers
pretty much anything async in the Node.js ecosystem.
However, even if coverage was wider it's not actually showing the
information we want. It's not enough to show the low level I/O that is
happening because that doesn't provide the context. We need the stack
trace in user space code where it was initiated and where it was
awaited. It's also not each low level socket operation that we want to
surface but some higher level concept which can span a sequence of I/O
operations but as far as user space is concerned.
Therefore this solution is anchored on stack traces and ignore listing
to determine what the interesting span is. It is somewhat
Promise-centric (and in particular async/await) because it allows us to
model an abstract span instead of just random I/O. Async/await points
are also especially useful because this allows Async Stacks to show the
full sequence which is not supported by random callbacks. However, if no
Promises are involved we still to our best to show the stack causing
plain I/O callbacks.
Additionally, we don't want to track all possible I/O. For example,
side-effects like logging that doesn't affect the rendering performance
doesn't need to be included. We only want to include things that
actually block the rendering output. We also need to track which data
blocks each component so that we can track which data caused a
particular subtree to suspend.
We can do this using `async_hooks` because we can track the graph of
what resolved what and then spawned what.
To track what suspended what, something has to resolve. Therefore it
needs to run to completion before we can show what it was suspended on.
So something that never resolves, won't be tracked for example.
We use the `async_hooks` in `ReactFlightServerConfigDebugNode` to build
up an `ReactFlightAsyncSequence` graph that collects the stack traces
for basically all I/O and Promises allocated in the whole app. This is
pretty heavy, especially the stack traces, but it's because we don't
know which ones we'll need until they resolve. We don't materialize the
stacks until we need them though.
Once they end up pinging the Flight runtime, we collect which current
executing task that pinged the runtime and then log the sequence that
led up until that runtime into the RSC protocol. Currently we only
include things that weren't already resolved before we started rendering
this task/component, so that we don't log the entire history each time.
Each operation is split into two parts. First a `ReactIOInfo` which
represents an I/O operation and its start/end time. Basically the start
point where it was start. This is basically represents where you called
`new Promise()` or when entering an `async function` which has an
implied Promise. It can be started in a different component than where
it's awaited and it can be awaited in multiple places. Therefore this is
global information and not associated with a specific Component.
The second part is `ReactAsyncInfo`. This represents where this I/O was
`await`:ed or `.then()` called. This is associated with a point in the
tree (usually the Promise that's a direct child of a Component). Since
you can have multiple different I/O awaited in a sequence technically it
forms a dependency graph but to simplify the model these awaits as
flattened into the `ReactDebugInfo` list. Basically it contains each
await in a sequence that affected this part from unblocking.
This means that the same `ReactAsyncInfo` can appear in mutliple
components if they all await the same `ReactIOInfo` but the same Promise
only appears once.
Promises that are only resolved by other Promises or immediately are not
considered here. Only if they're resolved by an I/O operation. We pick
the Promise basically on the border between user space code and ignored
listed code (`node_modules`) to pick the most specific span but abstract
enough to not give too much detail irrelevant to the current audience.
Similarly, the deepest `await` in user space is marked as the relevant
`await` point.
This feature is only available in the `node` builds of React. Not if you
use the `edge` builds inside of Node.js.
---------
Co-authored-by: Sebastian "Sebbie" Silbermann <silbermann.sebastian@gmail.com>
## Summary
We completed testing on these internally, so can cleanup the separate
fast and slow paths and remove the `enableShallowPropDiffing` flag which
we're not pursuing.
## How did you test this change?
```
yarn test ReactNativeAttributePayloadFabric
```
There seems to be some bugs still to work out in Chrome. See #33187.
Additionally, since you can't really rely on this function existing
across browsers, it's hard to depend on its behavior anyway. In fact,
you now have a source of inconsistent behaviors across browsers to deal
with.
Ideally it would also be more widely spread in fake DOM implementations
like JSDOM so that we can use it unconditionally. #33177.
We still want to enable this since it's a great feature but maybe not
until it's more widely available cross-browsers with fewer bugs.