Commit Graph

402 Commits

Author SHA1 Message Date
Sebastian Markbåge
c825f03067 [DevTools] Hide State and Props in the Sidebar for Suspense (#34630)
We're showing too much noise in the side-panel when selecting a Suspense
boundary. The interesting thing to see directly is the "Suspended by".

The "props" are mostly useless because the `"name"` prop is already in
the tree. I'm now also showing it in the title bar of the selected
element panel. The "children" and "fallback" props are just the thing
that you can see in the tree view anyway.

The "state" is this weird section with just one field in it, which we
already have duplicated in the top toolbar as well. We can just delete
this. I make sure to show the icon and a "suspended..." section while
the boundary is still loading but now yet resuspended by force
suspending.

While still loading:

<img width="600" height="193" alt="Screenshot 2025-09-27 at 11 54 37 PM"
src="https://github.com/user-attachments/assets/1c3f3a96-46e0-4b11-806f-032569c7d5b5"
/>

After loading:

<img width="602" height="266" alt="Screenshot 2025-09-27 at 11 54 53 PM"
src="https://github.com/user-attachments/assets/c43cc4cb-036f-4ced-9b0d-226c6320cd76"
/>

Resuspended after loading:

<img width="602" height="300" alt="Screenshot 2025-09-27 at 11 55 07 PM"
src="https://github.com/user-attachments/assets/0be01735-48a7-47dc-b5cf-e72ec71e0148"
/>
2025-10-02 15:18:41 -04:00
Sebastian "Sebbie" Silbermann
4a28227960 [DevTools] Inspect the Initial Paint when inspecting a Root (#34454) 2025-10-02 19:18:15 +02:00
Sebastian "Sebbie" Silbermann
e4a27db283 [DevTools] Defer Suspense tab to 19.3.0-canary (#34688) 2025-10-02 19:13:52 +02:00
Sebastian "Sebbie" Silbermann
bc828bf6e3 [DevTools] Recommend React Performance tracks if supported when Timeline profiler is not supported (#34684) 2025-10-02 18:33:50 +02:00
Sebastian "Sebbie" Silbermann
1f460f31ee [DevTools] Fix host instance highlighting (#34661) 2025-10-01 17:32:34 +02:00
Sebastian Markbåge
554a373d7e [DevTools] Use the scrollWidth/Height for the root when the root is the documentElement (#34651)
If I can scroll the document due to it overflowing, I should be able to
scroll the suspense tab as much. The real rect for the root when it's
the document is really the full scroll height.

This doesn't fully eliminate the need to do recursive bounding boxes for
the root since it's still possible to have the rects overflow. E.g. if
they're currently resuspended or inside nested scrolls.

~However, maybe we should have the actual paintable root rect just be
this rectangle instead of including the recursive ones.~ Actually never
mind. The root really represents the Transition so it doesn't make sense
to give it any specific rectangle. It's rather the whole background.
2025-09-30 14:37:40 -04:00
Sebastian Markbåge
5dd163b49e [DevTools] Auto-scroll when stepping through the timeline (#34653)
This brings the Suspense boundary that's switching into view so that
when you play the loading sequence you can see how it plays out.
Otherwise it's really hard to find where things are changing.

This assumes we'll also scroll synchronize the suspense tab which will
bring it into view there too.
2025-09-30 14:37:14 -04:00
Sebastian "Sebbie" Silbermann
8674c3ba28 [DevTools] Enable Suspense tab for Canary releases (#34591) 2025-09-29 16:23:30 +02:00
Sebastian Markbåge
8309724cb4 [Fiber][DevTools] Add scheduleRetry to DevTools Hook (#34635)
When forcing suspense/error we're doing that by scheduling a sync update
on the fiber. Resuspending a Suspense boundary can only happen sync
update so that makes sense. Erroring also forces a sync commit. This
means that no View Transitions fire.

However, unsuspending (and dismissing an error dialog) can be async so
the reveal should be able to be async.

This adds another hook for scheduling using the Retry lane. That way
when you play through a reveal sequence of Suspense boundaries (like
playing through the timeline), it'll run the animations that would've
ran during a loading sequence.
2025-09-28 13:51:35 -04:00
Sebastian Markbåge
6a51a9fea6 [DevTools] Track Server Environment Names of Each SuspenseNode (#34605)
Tracks the environment names of the I/O in each SuspenseNode and sent it
to the front end when the suspenders change.

In the front end, every child boundary should really be treated as it
has all environment names of the parents too since they're blocked by
the parent too. We could do this tracking on backend but if there's ever
one added on the root would need to be send for every child.

This lets us highlight which subtrees are blocked by content on the
server.

---------

Co-authored-by: Sebastian "Sebbie" Silbermann <silbermann.sebastian@gmail.com>
2025-09-26 09:43:38 -04:00
Sebastian "Sebbie" Silbermann
c44fbf43b1 [DevTools] Fix instrumentation error when reconciling promise-as-a-child (#34587) 2025-09-24 22:50:12 +02:00
Sebastian "Sebbie" Silbermann
012b371cde [DevTools] Handle LegacyHidden Fibers like Offscreen Fibers. (#34564) 2025-09-23 20:14:53 +02:00
Sebastian "Sebbie" Silbermann
03a96c75db [DevTools] Record Suspense node for roots in legacy renderers (#34516) 2025-09-18 18:50:23 +02:00
Sebastian "Sebbie" Silbermann
a51f925217 [DevTools] Only check if we previously removed IO if its removal failed (#34506) 2025-09-16 19:55:03 +02:00
Sebastian "Sebbie" Silbermann
941cd803a7 [DevTools] Don't keep stale root instances we never mounted around (#34504) 2025-09-16 19:17:28 +02:00
Sebastian "Sebbie" Silbermann
851bad0c88 [DevTools] Ignore repeated removals of the same IO (#34495) 2025-09-16 18:54:52 +02:00
Sebastian "Sebbie" Silbermann
5502d85cc7 [DevTools] Unmount fallbacks in the context of the parent Suspense (#34475)
Co-authored-by: Ruslan Lesiutin <hoxy@meta.com>
2025-09-13 11:03:32 +02:00
Sebastian "Sebbie" Silbermann
a9ad64c852 [DevTools] Stop mounting empty roots (#34467) 2025-09-11 20:00:53 +02:00
Sebastian "Sebbie" Silbermann
7fc888dde2 [DevTools] Stop recording reorders in disconnected subtrees (#34464) 2025-09-11 19:13:14 +02:00
Sebastian "Sebbie" Silbermann
b1c519f3d4 [DevTools] Only show boundaries with unique suspenders by default in the timeline (#34397) 2025-09-11 11:33:05 +02:00
Sebastian Markbåge
886b3d36d7 [DevTools] Show suspended by subtree from Activity to next Suspense boundary (#34438)
Stacked on #34435.

This adds a method to get all suspended by filtered by a specific
Instance. The purpose of this is to power the feature when you filter by
Activity. This would show you the "root" within that Activity boundary.

This works by selecting the nearest Suspense boundary parent and then
filtering its data based on if all the instances for a given I/O info is
within the Activity instance. If something suspended within the Suspense
boundary but outside the Activity it's not included even if it's also
suspending inside the Activity since we assume it would've already been
loaded then.

Right now I wire this up to be a special case when you select an
Activity boundary same as when you select a Suspense boundary in the
Components tab but we could also only use this when you select the root
in the Suspense tab for example.
2025-09-10 09:44:51 -04:00
Sebastian Markbåge
288d428af1 [DevTools] Only show the highest end/byteSize I/O of RSC streams (#34435)
Stacked on #34425.

RSC stream info is split into one I/O entry per chunk. This means that
when a single instance or boundary depends on multiple chunks, it'll
show the same stream multiple times. This makes it so just the last one
is shown.

This is a special case for the name "RSC stream" but ideally we'd more
explicitly model the concept of awaiting only part of a stream.

<img width="667" height="427" alt="Screenshot 2025-09-09 at 2 09 43 PM"
src="https://github.com/user-attachments/assets/890f6f61-4657-4ca9-82fd-df55a696bacc"
/>

Another remaining issue is that it's possible for an intermediate chunk
to be depended on by just a child boundary. In that case that can be
considered a "unique suspender" even though the parent depends on a
later one. Ideally it would dedupe on everything below. Could also model
it as every Promise depends on its chunk and every previous chunk.
2025-09-10 09:08:36 -04:00
Sebastian "Sebbie" Silbermann
8943025358 [DevTools] Fix handling of host roots on mount (#34400) 2025-09-08 22:53:02 +02:00
Sebastian "Sebbie" Silbermann
ba6590dd7c [DevTools] Rerender boundaries when they unsuspend when advancing the timeline (#34359) 2025-09-04 10:49:16 +02:00
Sebastian "Sebbie" Silbermann
cb1e73be04 [DevTools] Batch Suspense toggles when advancing the Suspense timeline (#34251) 2025-08-26 17:22:30 +02:00
Sebastian "Sebbie" Silbermann
75dc0026d6 [DevTools] Initial version of Suspense timeline (#34233) 2025-08-25 17:47:29 +02:00
Jan Kassens
df10309e2b Update Flow to 0.279 (#34277)
Multiple of these version upgrades required minor additional
annotations.
2025-08-25 11:02:56 -04:00
Jan Kassens
4049cfeeab Update Flow to 0.273 (#34274)
This version introduces "Natural Inference" which requires a couple more
type annotations to make Flow pass.
2025-08-22 16:58:01 -04:00
Jan Kassens
06cfa99f37 Update Flow to 0.267 (#34272)
Changes to type inference require some more annotations.
2025-08-22 15:53:07 -04:00
Jan Kassens
d260b0d8b8 Update Flow to 0.265 (#34270)
Looks like this version removed `Object.prototype` although I didn't see
that in the changelog. This is fine for this code here.
2025-08-22 15:22:22 -04:00
Jan Kassens
6de32a5a07 Update Flow to 0.263 (#34269)
This update was a bit more involved.

- `React$Component` was removed, I replaced it with Flow component
types.
- Flow removed shipping the standard library. This adds the environment
libraries back from `flow-typed` which seemed to have changed slightly
(probably got more precise and less `any`s). Suppresses some new type
errors.
2025-08-22 12:10:13 -04:00
Jan Kassens
ec5dd0ab3a Update Flow to 0.257 (#34253)
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.
2025-08-21 13:30:01 -04:00
Sebastian "Sebbie" Silbermann
03fda05d2c [DevTools] Fix display of stack frames with anonymous sources (#34237) 2025-08-20 17:31:42 +02:00
Sebastian "Sebbie" Silbermann
ae5c2f82b3 [DevTools] Handle reorders when resuspending while fallback contains Suspense (#34225) 2025-08-19 20:22:54 +02:00
Sebastian Markbåge
42b1b33a24 [DevTools] Add byteSize field to ReactIOInfo and show this in the tooltip (#34221)
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.
2025-08-17 16:17:11 -04:00
Sebastian "Sebbie" Silbermann
546bac7281 [DevTools] Always attempt to mount dehydrated roots (#34209) 2025-08-16 10:45:39 +02:00
Sebastian "Sebbie" Silbermann
2cb8edbb05 [DevTools] Handle dehydrated Suspense boundaries (#34196) 2025-08-16 10:34:19 +02:00
Sebastian Markbåge
431bb0bddb [DevTools] Mark Unknown Reasons for Suspending with a Note (#34200)
We currently only track the reason something might suspend in
development mode through debug info but this excludes some cases. As a
result we can end up with boundary that suspends but has no cause. This
tries to detect that and show a notice for why that might be. I'm also
trying to make it work with old React versions to cover everything.

In production we don't track any of this meta data like `_debugInfo`,
`_debugThenable` etc. so after resolution there's no information to take
from. Except suspensey images / css which we can track in prod too. We
could track lazy component types already. We'd have to add something
that tracks after the fact if something used a lazy child, child as a
promise, hooks, etc. which doesn't exist today. So that's not backwards
compatible and might add some perf/memory cost. However, another
strategy is also to try to replay the components after the fact which
could be backwards compatible. That's tricky for child position since
there's so many rules for how to do that which would have to be
replicated.

If you're in development you get a different error. Given that we've
added instrumentation very recently. If you're on an older development
version of React, then you get a different error. Unfortunately I think
my feature test is not quite perfect because it's tricky to test for the
instrumentation I just added.
https://github.com/facebook/react/pull/34146 So I think for some
prereleases that has `_debugOwner` but doesn't have that you'll get a
misleading error.

Finally, if you're in a modern development environment, the only reason
we should have any gaps is because of throw-a-Promise. This will
highlight it as missing. We can detect that something threw if a
Suspense boundary commits with a RetryCache but since it's a WeakSet we
can't look into it to see anything about what it might have been. I
don't plan on doing anything to improve this since it would only apply
to new versions of React anyway and it's just inherently flawed. So just
deprecate it #34032.

Note that nothing in here can detect that we suspended Transition. So
throwing at the root or in an update won't show that anywhere.
2025-08-15 18:32:27 -04:00
Sebastian "Sebbie" Silbermann
8dba9311e5 [DevTools] Handle fallback unmount in Suspense update path (#34199) 2025-08-15 19:40:35 +02:00
Sebastian "Sebbie" Silbermann
2d98b45d92 [DevTools] Fix Suspense boundaries always being marked as not suspended (#34206) 2025-08-15 19:39:59 +02:00
Sebastian Markbåge
2ba7b07ce1 [DevTools] Compute a min and max range for the currently selected suspense boundary (#34201)
This computes a min and max range for the whole suspense boundary even
when selecting a single component so that each component in a boundary
has a consistent range.

The start of this range is the earliest start of I/O in that boundary or
the end of the previous suspense boundary, whatever is earlier. If the
end of the previous boundary would make the range large, then we cap it
since it's likely that the other boundary was just an independent
render.

The end of the range is the latest end of I/O in that boundary. If this
is smaller than the end of the previous boundary plus the 300ms
throttle, then we extend the end. This visualizes what throttling could
potentially do if the previous boundary committed right at its end. Ofc,
it might not have committed exactly at that time in this render. So this
is just showing a potential throttle that could happen. To see actual
throttle, you look in the Performance Track.

<img width="661" height="353" alt="Screenshot 2025-08-14 at 12 41 43 AM"
src="https://github.com/user-attachments/assets/b0155e5e-a83f-400c-a6b9-5c38a9d8a34f"
/>

We could come up with some annotation to highlight that this is eligible
to be throttled in this case. If the lines don't extend to the edge,
then it's likely it was throttled.
2025-08-15 13:34:07 -04:00
Sebastian "Sebbie" Silbermann
379a083b9a Include stack of cause in React instrumentation errors (#34198) 2025-08-13 19:18:02 +02:00
Sebastian Markbåge
db06f6b751 [DevTools] Track virtual debug info from suspensey images (#34181)
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"
/>
2025-08-13 09:26:21 -04:00
Sebastian Markbåge
47fd2f5e14 [DevTools] Fix index (#34187)
I used the wrong indexer and tested with one entry.
2025-08-12 13:57:35 -04:00
Sebastian "Sebbie" Silbermann
de06211dbe [DevTools] Send Suspense rects to frontend (#34170) 2025-08-12 16:48:35 +02:00
Sebastian Markbåge
3c67bbe5f9 [DevTools] Track suspensey CSS on "suspended by" (#34166)
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.
2025-08-11 12:28:32 -04:00
Sebastian Markbåge
2c9a42dfd7 [DevTools] If the await doesn't have a stack use the stack from use() if any (#34162)
Stacked on #34148.

This picks up the stack for the await from the `use()` Hook if one was
used to get this async info.

When you select a component that used hooks, we already collect this
information.

If you select a Suspense boundary, this lazily invokes the first
component that awaited this data to inspects its hooks and produce a
stack trace for the use().

When all we have for the name is "Promise" I also use the name of the
first callsite in the stack trace if there's more than one. Which in
practice will be the name of the custom Hook that called it. Ideally
we'd use source mapping and ignore listing for this but that would
require suspending the display. We could maybe make the SuspendedByRow
wrapped in a Suspense boundary for this case.

<img width="438" height="401" alt="Screenshot 2025-08-10 at 10 07 55 PM"
src="https://github.com/user-attachments/assets/2a68917d-c27b-4c00-84aa-0ceb51c4e541"
/>
2025-08-11 12:28:10 -04:00
Sebastian Markbåge
d587434c35 [DevTools] Pick up suspended by info from use() (#34148)
Similar to #34144 but for `use()`.

`use()` dependencies don't get added to the `fiber._debugInfo` set
because that just models the things blocking the children, and not the
Fiber component itself. This picks up any debug info from the thenable
state that we stashed onto `_debugThenableState` so that we know it used
`use()`.

<img width="593" height="425" alt="Screenshot 2025-08-09 at 4 03 40 PM"
src="https://github.com/user-attachments/assets/c7e06884-4efd-47fa-a76b-132935db6ddc"
/>

Without #34146 this doesn't pick up uninstrumented promises but after
it, it'll pick those up as well. An instrumented promise that doesn't
have anything in its debug info is not picked up. For example, if it
didn't depend on any I/O on the server.

This doesn't yet pick up the stack trace of the `use()` call. That
information is in the Hooks information but needs a follow up to extract
it.
2025-08-11 12:10:05 -04:00
Sebastian Markbåge
34ce3acafd [DevTools] Pick up suspended by info from React.lazy in type position (#34144)
Normally, we pick up debug info from instrumented Promise or React.Lazy
while we're reconciling in ReactChildFiber when they appear in the child
position. We add those to the `_debugInfo` of the Fiber.

However, we don't do that for for Lazy in the Component type position.
Instead, we have to pick up the debug info from it explicitly in
DevTools. Likely this is the info added by #34137. Older versions
wouldn't be covered by this particular mechanism but more generally from
throwing a Promise.


<img width="592" height="449" alt="Screenshot 2025-08-08 at 11 32 33 PM"
src="https://github.com/user-attachments/assets/87211c64-a7df-47b7-a784-5cdc7c5fae16"
/>
2025-08-11 11:42:59 -04:00
Sebastian Markbåge
ab5238d5a4 [DevTools] Show name prop of Suspense / Activity in the Components Tree view (#34135)
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"
/>
2025-08-11 11:41:46 -04:00