Stacked on https://github.com/facebook/react/pull/30566 and whats under
it. See [this
commit](374fd737e4).
It is mostly copying code from one place to another and updating tests.
With these changes, for every console method that we patch, there is
going to be a single applied patch:
- For `error`, `warn`, and `trace` we are patching when hook is
installed. This guarantees that component stacks are going to be
appended even if browser DevTools are not opened. We pay some price for
it, though: if user has browser DevTools closed and if at this point
some warning or error is emitted (logged), the next time user opens
browser DevTools, they are going to see `hook.js` as the source frame.
Unfortunately, ignore listing from source maps is not applied
retroactively, and I don't know if its a bug or just a design
limitations. Once browser DevTools are opened, source maps will be
loaded and ignore listing will be applied for all emitted logs in the
future.
- For `log`, `info`, `group`, `groupCollapsed` we are only patching when
React notifies React DevTools about running in StrictMode. We unpatch
the methods right after it.
Right now we are patching console 2 times: when hook is installed
(before page is loaded) and when backend is connected. Because of this,
even if user had `appendComponentStack` setting enabled, all emitted
error and warning logs are not going to have component stacks appended.
They also won't have component stacks appended retroactively when user
opens browser DevTools (this is when frontend is initialized and
connects to backend).
This behavior adds potential race conditions with LogBox in React
Native, and also unpredictable to the user, because in order to get
component stacks logged you have to open browser DevTools, but by the
time you do it, error or warning log was already emitted.
To solve this, we are going to only patch console in the hook object,
because it is guaranteed to load even before React. Settings are going
to be synchronized with the hook via Bridge, and React DevTools Backend
Host (React Native or browser extension shell) will be responsible for
persisting these settings across the session, this is going to be
implemented in a separate PR.
This fixes printing Error objects in Chrome DevTools.
I've observed that Chrome DevTools is not source mapping and linkifying
URLs, when was running this on larger apps. Chrome DevTools talks to V8
via Chrome DevTools protocol, every object has a corresponding
[`RemoteObject`](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-RemoteObject).
When Chrome DevTools sees that Error object is printed in the console,
it will try to prettify it. `description` field of the corresponding
`RemoteObject` for the `Error` JavaScript object is a combination of
`Error` `name`, `message`, `stack` fields. This is not just a raw
`stack` field, so our prefix for this field just doesn't work. [V8 is
actually filtering out first line of the `stack` field, it only keeps
the stack frames as a string, and then this gets prefixed by `name` and
`message` fields, if they are
available](https://source.chromium.org/chromium/chromium/src/+/main:v8/src/inspector/value-mirror.cc;l=252-311;drc=bdc48d1b1312cc40c00282efb1c9c5f41dcdca9a?fbclid=IwZXh0bgNhZW0CMTEAAR1tMm5YC4jqowObad1qXFT98X4RO76CMkCGNSxZ8rVsg6k2RrdvkVFL0i4_aem_e2fRrqotKdkYIeWlJnk0RA).
As an illustration, this:
```
const fakeError = new Error('');
fakeError.name = 'Stack';
fakeError.stack = 'Error Stack:' + stack;
```
will be formatted by `V8` as this `RemoteObject`:
```
{
...
description: 'Stack: ...',
...
}
```
Notice that there is no `Error` prefix, that was previously added.
Because of this, [Chrome DevTools won't even try to symbolicate the
stack](ee4729d2cc/front_end/panels/console/ErrorStackParser.ts (L33-L35)),
because it doesn't have such prefix.
This flag will be used to gate a new timeline profiler that's integrate
with the Performance Tab and the new performance.measure extensions in
Chrome.
It replaces the existing DevTools feature so this disables
enableSchedulingProfiler when it is enabled since they can interplay in
weird ways potentially.
This means that experimental React now disable scheduling profiler and
enables this new approach.
The current state is that `rendererInterface`, which contains all the
backend logic, like generating component stack or attaching errors to
fibers, or traversing the Fiber tree, ..., is only mounted after the
Frontend is created.
For browser extension, this means that we don't patch console or track
errors and warnings before Chrome DevTools is opened.
With these changes, `rendererInterface` is created right after
`renderer` is injected from React via global hook object (e. g.
`__REACT_DEVTOOLS_GLOBAL_HOOK__.inject(...)`.
Because of the current implementation, in case of multiple Reacts on the
page, all of them will patch the console independently. This will be
fixed in one of the next PRs, where I am moving console patching to the
global Hook.
This change of course makes `hook.js` script bigger, but I think it is a
reasonable trade-off for better DevX. We later can add more heuristics
to optimize the performance (if necessary) of `rendererInterface` for
cases when Frontend was connected late and Backend is attempting to
flush out too many recorded operations.
This essentially reverts https://github.com/facebook/react/pull/26563.
When a component suspends and is replaced by a fallback, we should start
prerendering the fallback immediately, even before any new data is
received. During the retry, we can enter prerender mode directly if
we're sure that no new data was received since we last attempted to
render the boundary.
To do this, when completing the fallback, we leave behind a pending
retry lane on the Suspense boundary. Previously we only did this once a
promise resolved, but by assigning a lane during the complete phase, we
will know that there's speculative work to be done.
Then, upon committing the fallback, we mark the retry lane as suspended
— but only if nothing was pinged or updated in the meantime. That allows
us to immediately enter prerender mode (i.e. render without skipping any
siblings) when performing the retry.
Both for browser extension, and for React Native (as part of
`react-devtools-core`) `Store` is initialized before the Backend (and
`Agent` as a part of it):
bac33d1f82/packages/react-devtools-extensions/src/main/index.js (L111-L113)
Any messages that we send from `Store`'s constructor are ignored,
because there is nothing on the other end yet. With these changes,
`Agent` will send `backendInitialized` message to `Store`, after which
`getBackendVersion` and other events will be sent.
Note that `isBackendStorageAPISupported` and `isSynchronousXHRSupported`
are still sent from `Agent`'s constructor, because we don't explicitly
ask for it from `Store`, but these are used.
This the pre-requisite for fetching settings and unsupported renderers
reliably from the Frontend.
Make `onErrorOrWarning` and `getComponentStack` part of
`rendererInterface`. By doing this, they will be available from the
global hook `rendererInterfaces` Map. This makes them available to be
used by Hook, which soon will be the only one who is doing console
patching.
This is also a pre-requisite for removing `registerRenderer`:
d160aa0fbb/packages/react-devtools-shared/src/backend/console.js (L113-L121)
This adds owner stacks to replayed Server Component logs in environments
that don't support native console.createTask.
<img width="521" alt="Screenshot 2024-09-09 at 8 55 21 PM"
src="https://github.com/user-attachments/assets/261cfaee-ea65-4044-abf0-c41abf358fea">
It also tracks the logs in the global componentInfoToComponentLogsMap
which lets us associate those logs with Server Components when they
later commit into the fiber tree.
<img width="1280" alt="Screenshot 2024-09-09 at 9 31 16 PM"
src="https://github.com/user-attachments/assets/436312a6-f9f4-4add-8129-0fb9b9eb18ee">
I tried to create unit tests for this since it's now wired up
end-to-end. Unfortunately, the complicated testing set up for Flight
requires a complex set of resetting modules which are incompatible with
the complicated test setup in getVersionedRenderImplementation for
DevTools tests.
This reverts #19603.
Before:
<img width="724" alt="Screenshot 2024-08-28 at 12 07 29 AM"
src="https://github.com/user-attachments/assets/0613088f-c013-4f1c-92c3-fbdae8c1f109">
After:
<img width="771" alt="Screenshot 2024-08-28 at 12 08 13 AM"
src="https://github.com/user-attachments/assets/eef21bee-d11f-4f0a-9147-053a163f720f">
Consensus seems to be that while the purple on is a bit clearer and
easier to read. The purple is not on brand so it doesn't look like
React. It looks ugly. It's distracting (too eye catching). Taking away
attention from other tabs in an unfair way.
It also gets worse with more tabs added. We plan on both adding another
tab and panes inside other tabs (elements/sources) soon. Each needs to
be marked somehow as part of React but spelling it out is too long.
Putting inside a second tab means two clicks and takes away real-estate
from our extension and doesn't solve the problem with extension panes in
other tabs. We also plan on adding multiple different tracks to the
Performance tab which also needs a name other than just React and
spelling out React as a prefix is too long. The Emoji is too
distracting. So it seems best to uniformly apply the symbol - albeit it
might just look like a dot to many.
Dark mode looks close to on brand:
<img width="1089" alt="Screenshot 2024-08-28 at 12 32 50 AM"
src="https://github.com/user-attachments/assets/7175a540-4241-4c26-9e4d-4d367873af57">
The console instrumentation should not know about things like Fibers.
Only the renderer bindings should know about that stuff. We can improve
the layering by just moving all that stuff behind a `getComponentStack`
helper that gets injected by the renderer.
This sets us up for the Flight renderer #30906 to have its own
implementation of this function.
Stacked on #30899.
This adds another map to store Server Components logs. When they're
replayed with an owner we can associate them with a DevToolsInstance.
The replaying should happen before they can mount in Fiber so they'll
always have all logs when they mount. There can be more than one
Instance associated with any particular ReactComponentInfo. It can also
be unmounted and restored later.
One thing that's interesting about these is that when a Server Component
tree refreshes a new set of ReactComponentInfo will update through the
tree and the VirtualInstances will update with new instances. This means
that the old errors/warnings are no longer associated with the
VirtualInstance. I.e. it's not continually appended like updates do for
Fiber backed instances. On the client we dedupe errors/warnings for the
life time of the page. On the server that doesn't work well because it
would mean that when you refresh the page, you miss out on warnings so
we dedupe them per request instead. If we just appended on refresh it
would keep adding them.
If ever add a deduping mechanism that spans longer than a request, we
might need to do more of a merge when these updates.
Nothing actually adds logs to this map yet. That will need an
integration with Flight in a follow up.
This represents a virtual renderer that connects to the Flight Client.
It's virtual in the sense that the actual rendering has already happened
on the server. The Flight Client parses the result. Most of the result
then end up in objects that render into another renderer and that's how
we see most Server Components in DevTools. As part of the client's tree.
However, some things are side-effects that don't really connect to any
particular client renderer. For example preloads() and logs. For those
we need to treat the Flight Client as if it was its own renderer just
like a Fiber renderer or even legacy renderer. We really could support
Fizz and Flight Server as DevTools targets too for example to connect it
to the backend but there's just not much demand for that.
This will initially only be used to track the owners of replayed console
logs but could be expanded to more. For example to send controls to
start profiling on the server. It could also be expanded to build an RSC
payload inspector that is automatically connected.
We can simplify this tracking by not having a separate pending set of
logs and the logs tracked per instance and instead we just track the
logs per Fiber. This avoids the need to move it back into the pending
set after unmounts in case a Fiber is reparented.
The main motivation for this is to unify with an upcoming tracking of
logs for Server Components. For those it doesn't make sense to move them
into a per instance set and because the same Server Component - and its
logs - may appear more than once. So no particular instance should steal
it.
The second part of this change is that instead of looking up the
instance from fiber, which requires the fiberToFiberInstanceMap, we
instead look up if a component has any new logs when we traverse it in
the commit phase. After all for a component to have had a log it must
have updated. This is a similar technique to #30897. This technique also
works for Server Components without having to maintain a one to many
relationship from ComponentInfo to VirtualInstance. So it unifies them.
Normally this look up would be fast since the `fiberToComponentsLogs`
set would be empty and so doesn't add any significant weight to the
commit phase. If there's a ton of logs on many different components then
it's not great since it would slow down the commit phase but that's not
what we expect to see so in typical usage, this is better.
There is an unfortunate consequence though which is that
`console.warn/error` in passive effects (i.e. `useEffect`) wouldn't be
picked up because currently we traverse the logs in
`handleCommitFiberRoot` which is too early. If we moved that to
`handlePostCommitFiberRoot` this wouldn't be a problem. In the meantime,
I just detect this and do a brute force flush by walking all mounted
instances if there's a `console.warn/error` inside a passive effect.
If we ever add "owners" to event handlers that are triggered outside the
render/commit phases (like `<div onClick={...}>`) and we want to
associate error/warnings in those, we'd need a different technique to
ensure those get flushed in time.
## Summary
This PR bumps Flow all the way to the latest 0.245.2.
Most of the suppressions comes from Flow v0.239.0's change to include
undefined in the return of `Array.pop`.
I also enabled `react.custom_jsx_typing=true` and added custom jsx
typing to match the old behavior that `React.createElement` is
effectively any typed. This is necessary since various builtin
components like `React.Fragment` is actually symbol in the React repo
instead of `React.AbstractComponent<...>`. It can be made more accurate
by customizing the `React$CustomJSXFactory` type, but I will leave it to
the React team to decide.
## How did you test this change?
`yarn flow` for all the renderers
Stacked on #30896.
The problem with the `getUpdatersList` function is that it iterates over
Fibers and then looks up each of those Fibers in the
fiberToFiberInstanceMap which we ideally could get rid of.
However, every time an updater comes into play for a commit it must mean
that something below the updater itself updated and so the updater will
also be cloned which means we'll pass it on the way down when traversing
the tree in the commit.
When we do this traversal, we can just look if the Fiber is in the
updater set and if so add it to the updater list as we go.
When Context change tracking was added to support modern Context it
relied on the "memoizedValue" to read the current value. This only works
in React 18+ when it was added to support Lazy Context Propagation.
However, the backend stored the old value the same way it used to work
for legacy Context in a global map. This was unnecessary since we *also*
have the old value on the previous Fiber.
This removes all the costly tracking of previous values for every Fiber
that uses Contexts slowing down profiling. Instead, we just compare the
Contexts from
The downside is that this no longer supports detecting changes due to
legacy Context because it doesn't have a similar "previous" value.
However, legacy Context has long been deprecated and is completely
removed in 19. So I don't think it's worth supporting since you have to
be on an old version *and* actually use legacy Context *and* trying to
profile something that updates it. Which btw, updating legacy contexts
only worked at all from 16 something when we made updates work. So it
was unusual even in the slight gap where you could and before you had
migrated to modern Context introduced in 16.3.
Ideally we shouldn't use the `.alternate` to access previous state
because ideally Fibers shouldn't have alternates.
The only case it's ok to use it is when it is used to identity the
stateful part of a component's identity. In a non-alternate Fiber model
there would instead be another object that represents instance but in
the current model it's modeled by the pair.
It's not ok is to get the previous state of the tree since that would
not live on the stateful part.
We don't generally need this though because we have the previous state
on instance.data before updating it, or passed from above.
While looking at the Context tracking implementation for other reasons I
noticed this bug.
Originally it wasn't allowed to have conditional `useContext(context)`
(although we did because it's technically possible). With `use(context)`
it is officially allowed to be conditional as long as it is within a
Hook/Component and not within a try/catch.
This means that this loop comparing previous and next contexts need to
consider that the Context objects might not line up and so it's possibly
comparing apples to oranges. We already bailed if one was longer than
the other.
If the order of contexts changes later in the component that means
something else must have already changed earlier so the reason for the
rerender isn't the context so we can just return false in that case.
First, this basically reverts
1f3892ef8c
to use a Map/Set to track what is forced to suspend/error again instead
of flags on the Instance. The difference is that now the key in the
Fiber itself instead of the ID. Critically this avoids the
fiberToFiberInstance map to look up whether or not a Fiber should be
forced to suspend when asked by the renderer.
This also allows us to force suspend/error on filtered instances. It's a
bit unclear what should happen when you try to Suspend or Error a child
but its parent boundary is filtered. It was also inconsistent between
Suspense and Error due to how they were implemented.
I think conceptually you're trying to simulate what would happen if that
Component errored or suspended so it would be misleading if we triggered
a different boundary than would happen in real life. So I think we
should trigger the nearest unfiltered Fiber, not the nearest Instance.
The consequence of this however is that if this instance was filtered,
there's no way to undo it without refreshing or removing the filter.
This is an edge case though since it's unusual you'd filter these in the
first place.
It used to be that Suspense walked the store in the frontend and Error
walked the Fibers in the backend. They also did this somewhat eagerly.
This simplifies and unifies the model by passing the id of what you
clicked in the frontend and then we walk the Fiber tree from there in
the backend to lazily find the boundary. However I also eagerly walk the
tree at first to find whether we have any Suspense or Error boundary
parents at all so we can hide the buttons if not.
This also implements it to work with VirtualInstances using #30865. I
find the nearest Fiber Instance downwards filtered or otherwise. Then
from its parent we find the nearest Error or Suspense boundary. That's
because VirtualInstance will always have their inner Fiber as an
Instance but they might not have their parent since it might be
filtered. Which would potentially cause us to skip over a filtered
parent Suspense boundary.
When we filter Fiber Instances where have no way to recover our position
in the Fiber tree. The extreme form of this is if you filter out all the
Fibers and keep only Server Components.
This affects operations that are performed against fibers such as
collecting Host Instances for highlighting or emulating
suspending/erroring.
Conceptually we don't need to add this into the DevToolsInstance tree
because we only need to get to some Fibers from a VirtualInstance. A
Virtual Instance can contain more than one conceptual child Fiber. It
would be easier if we didn't include them in the tree on one hand
because we could just traverse the tree and assume it looks like the one
on the frontend. But it's also tricky to manage the lifetime. So I went
with a special FilteredFiberInstance node in the tree.
Currently I only add it if its parent would've been a VirtualInstance
since we don't need it in any other cases. If the parent was another
FiberInstance it already has a Fiber.
There might be need for always tracking all Instances whether they're
filtered or not or just moving filtering to the frontend but for now I'm
keeping the general architecture as is.
This lets us get from a HostInstance to the nearest DevToolsInstance
without relying on `findFiberByHostInstance` and
`fiberToDevToolsInstanceMap`. We already did the equivalent of this for
Resources in HostHoistables.
One issue before was that we'd ideally get away from the
`fiberToDevToolsInstanceMap` map in general since we should ideally not
treat Fibers as stateful but they could be replaced by something else
stateful in principle.
This PR also addresses Virtual Instances. Now you can select a DOM node
and have it select a Virtual Instance if that's the nearest parent since
the parent doesn't have to be a Fiber anymore.
However, the other reason for this change is that I'd like to get rid of
the need for the `findFiberByHostInstance` from being injected. A
renderer should not need to store a reference back from its instance to
a Fiber. Without the Synthetic Event system this wouldn't be needed by
the renderer so we should be able to remove it. We also don't really
need it since we have all the information by just walking the commit to
collect the nodes if we just maintain our own Map.
There's one subtle nuance that the different renderers do. Typically a
HostInstance is the same thing as a PublicInstance in React but
technically in Fabric they're not the same. So we need to translate
between PublicInstance and HostInstance. I just hardcoded the Fabric
implementation of this since it's the only known one that does this but
could feature detect other ones too if necessary. On one hand it's more
resilient to refactors to not rely on injected helpers and on hand it
doesn't follow changes to things like this.
For the conflict resolution I added in #30494 I had to make that
specific to DOM so we can move the DOM traversal to the backend instead
of the injected helper.
This lets us highlight Server Components.
However, there is a problem with this because if the actual nearest
Fiber is filtered, there's no FiberInstance and so we might skip past it
and maybe never find a child while walking the whole tree. This is very
common in the case where you have just Server Components and Host
Components which are filtered by default.
Note how the DOM nodes that are just plain host instances without client
component wrappers are not highlighted here:
<img width="1102" alt="Screenshot 2024-08-30 at 4 33 55 PM"
src="https://github.com/user-attachments/assets/c9a7b91e-5faf-4c60-99a8-1195539ff8b5">
Fixing that needs a separate refactor though and related to several
other features that already have a similar issue without
VirtualInstances like Suspense/Error Boundaries (triggering
suspense/error on a filtered Suspense/ErrorBoundary doesn't work
correctly). So this first PR just adds the feature for the common case
where there's at least some Fibers.
Stacked on #30842.
This adds a filter to be able to exclude Components from a certain
environment. Default to Client or Server.
The available options are computed into a dropdown based on the names
that are currently used on the page (or an option that were previously
used). In addition to the hardcoded "Client". Meaning that if you have
Server Components on the page you see "Server" or "Client" as possible
options but it can be anything if there are multiple RSC environments on
the page.
"Client" in this case means Function and Class Components in Fiber -
excluding built-ins.
If a Server Component has two environments (primary and secondary) then
both have to be filtered to exclude it.
We don't show the option at all if there are no Server Components used
in the page to avoid confusing existing users that are just using Client
Components and wouldn't know the difference between Server vs Client.
<img width="815" alt="Screenshot 2024-08-30 at 12 56 42 AM"
src="https://github.com/user-attachments/assets/e06b225a-e85d-4cdc-8707-d4630fede19e">
Support filtering Virtual Instances with existing filters.
Server Components are considered "Functions".
In a follow up I'll a new filter for "Environment" which will let you
filter by Client vs Server (and more).
This appends a (filtered) virtual instance path at the end of the fiber
path. If a virtual instance is selected inside the fiber.
The main part of the path is still just the fiber path since that's the
semantically stateful part. Then we just tack on a few virtual path
frames at the end if we're currently selecting a specific Server
Component within the nearest Fiber.
I also took the opportunity to fix a bug which caused selections inside
Suspense boundaries to not be tracked.
Firefox [finally supports
`ExecutionWorld.MAIN`](https://bugzilla.mozilla.org/show_bug.cgi?id=1736575)
in content scripts, which means we can migrate the browser extension to
Manifest V3.
This PR also removes a bunch of no longer required explicit branching
for Firefox case, when we are using Manifest V3-only APIs.
We are also removing XMLHttpRequest injection, which is no longer needed
and restricted in Manifest V3. The new standardized approach (same as in
Chromium) doesn't violate CSP rules, which means that extension can
finally be used for apps running in production mode.
This loops over the remainingReconcilingChildren to find existing
FiberInstances that match the updated Fiber. This is the same thing we
already do for virtual instances. This avoids the need for a
`fiberToFiberInstanceMap`.
This loop is fast but there is a downside when the children set is very
large and gets reordered with keys since we might have to loop over the
set multiple times to get to the instances in the bottom. If that
becomes a problem we can optimize it the same way ReactChildFiber does
which is to create a temporary Map only when the children don't line up
properly. That way everything except the first pass can use the Map but
there's no need to create it eagerly.
Now that we have the loop we don't need the previousSibling field so we
can save some memory there.
These don't have their own time since they don't take up any time to
render but they show up in the tree for context. However they never
render themselves. Their base tree time is the base time of their
children. This way they take up the same space as their combined
children in the Profiler tree. (Instead of leaving a blank line which
they did before this PR.)
The frontend doesn't track the difference between a virtual instance and
a Fiber that didn't render this update. This might be a bit confusing as
to why it didn't render. I add the word "client" to make it a bit
clearer and works for both. We should probably have different verbiage
here based on it is a Server Component or something else.
<img width="1103" alt="Screenshot 2024-08-26 at 5 00 47 PM"
src="https://github.com/user-attachments/assets/87b811d4-7024-466a-845d-542493ed3ca2">
I also took the opportunity to remove idToTreeBaseDurationMap and
idToRootMap maps. Cloning the Map isn't really all that super fast
anyway and it means we have to maintain the map continuously as we
render. Instead, we can track it on the instances and then walk the
instances to create a snapshot when starting to profile. This isn't as
fast but really fast too and requires less bookkeeping while rendering
instead which is more sensitive than that one snapshot in the beginning.
We don't have the source location of Server Components on the client
because we don't want to eagerly do the throw trick for all Server
Components just in case. Unfortunately Node.js doesn't expose V8's API
to get a source location of a function.
We do have the owner stacks of the JSX that created it though and at
some point we'll also show that location in DevTools.
However, the realization is that if a Server Component is the owner of
any child. The owner stack of that child will have the owner component's
source location as its bottom stack frame.
The technique I'm implementing here is to track whenever a child mounts
we already have its owner. We track the first discovered owned child's
stack on the owner. Then when we ask for a Source location of the owner
do we parse that stack and extract the location of the bottom frame.
This doesn't give us a location necessarily in the top of the function
but somewhere in the function.
In this case the first owned child is the Container:
<img width="1107" alt="Screenshot 2024-08-22 at 10 24 42 PM"
src="https://github.com/user-attachments/assets/95f32850-24a5-4151-8ce6-b7b89db68aee">
<img width="648" alt="Screenshot 2024-08-22 at 10 24 20 PM"
src="https://github.com/user-attachments/assets/4bcba033-866f-4684-9beb-de09d189deff">
We can even use this technique for Fibers too. Currently I use this as a
fallback in case the error technique didn't work. This covers a case
where nothing errors but you still render a child. This case is actually
quite common:
```
function Foo() {
return <Bar />;
}
```
However, for Fibers we could really just use the `inspect(function)`
technique which works for all cases. At least in Chrome.
Unfortunately, this technique doesn't work if a Component doesn't create
any new JSX but just renders its children. It also currently doesn't
work if the child is filtered since I only look up the owner if an
instance is not filtered. This means that the container in the fixture
can't view source by default since the host component is filtered:
```
export default function Container({children}) {
return <div>{children}</div>;
}
```
<img width="1107" alt="Screenshot 2024-08-22 at 10 24 35 PM"
src="https://github.com/user-attachments/assets/c3f8f9c5-5add-4d35-9290-3a5079e82adc">
I noticed that there is a delay due to the inspection being split into
one part that gets the attribute and another eval that does the
inspection. This is a bit hacky and uses temporary global names that are
leaky. The timeout was presumably to ensure that the first step had
fully propagated but it's slow. As we've learned, it can be throttled,
and it isn't a guarantee either way.
Instead, we can just consolidate these into a single operation that
by-passes the bridge and goes straight to the renderer interface from
the eval.
I did the same for the viewElementSource helper even though that's not
currently in use since #28471 but I think we probably should return to
that technique when it's available since it's more reliable than the
throw - at least in Chrome. I'm not sure about the status of React
Native here. In Firefox, inspecting a function with source maps doesn't
seem to work. It doesn't jump to original code.
Currently you can jump to definition of a function by right clicking
through the context menu. However, it's pretty difficult to discover.
This makes the functions clickable to jump to definition - like links.
This uses the same styling as we do for links (which are btw only
clickable if they're not editable). Including cursor: pointer.
I added a background on hover which follows the same pattern as the
owners list.
I also dropped the ƒ prefix when displaying functions. This is a cute
short cut and there's precedence in how Chrome prints functions in the
console *if* the function's toString would've had a function prefix like
if it was a function declaration or expression. It does not do this for
arrow functions or object methods.
Elsewhere in the JS ecosystem this isn't really used anywhere. It
invites more questions than it answers.
The parenthesis and curlies are enough. There's no ambiguity here since
strings have quotations. It looks better with just its object method
form. Keeping it simple seems best. To my eyes this flows better because
I'm used to looking at function syntax but not weird "f"s.
Before:
<img width="433" alt="Screenshot 2024-08-20 at 11 55 09 PM"
src="https://github.com/user-attachments/assets/9dd50da6-598f-4291-9e24-1cdc7200dc9e">
After:
<img width="388" alt="Screenshot 2024-08-20 at 11 46 01 PM"
src="https://github.com/user-attachments/assets/dd988e14-412e-4deb-8c8c-26a54be8331f">
After (Hover):
<img width="389" alt="Screenshot 2024-08-20 at 11 46 31 PM"
src="https://github.com/user-attachments/assets/6fb4ebed-5dc1-448a-8e4d-b6d4f3903329">
DevTools shouldn't use react-is since that's versioned to one version of
React. We don't need to since we use all the symbols from
shared/ReactSymbols anyway and have a fork of typeOf that can cover
both.
Now JSX of old React versions show up with proper JSX formatting when
inspecting.
This enables finding Server Components on the owner path. Server
Components aren't stateful so there's not actually one specific owner
that it necessarily matches. So it can't be a global look up. E.g. the
same Server Component can be rendered in two places or even nested
inside each other.
Therefore we need to find an appropriate instance using a heuristic. We
can do that by traversing the parent path since the owner is likely also
a parent. Not always but almost always.
To simplify things we can also do the same for Fibers. That brings us
one step closer to being able to get rid of the global
fiberToFiberInstance map since we can just use the shadow tree to find
this information.
This does mean that we can't find owners that aren't parents which is
usually ok. However, there is a test case that's interesting where you
have a React ART tree inside a DOM tree. In that case the owners
actually span multiple renderers and roots so the owner is not on the
parent stack. Usually this is fine since you'd just care about the
owners within React ART but ideally we'd support this. However, I think
that really the fix to this is that the React ART tree itself should
actually show up inside the DOM tree in DevTools and in the virtual
shadow tree because that's conceptually where it belongs. That would
then solve this particular issue. We'd just need some way to associate
the root with a DOM parent when it gets mounted.
Supports showing the key in DevTools on the Server Component that the
key was applied to. We can also use this to reconcile to preserve
instance equality when they're reordered.
One thing that's a bit weird about this is that if you provide an
explicit key on a Server Component that alone doesn't have any
semantics. It's because we pass the key down and let the nearest child
inherit the key or get prefixed by the key.
So you might see the same key as a prefix on the child of the Server
Component too which might be a bit confusing. We could remove the prefix
from children but that might also be a bit confusing if they collide.
The div in this case doesn't have a key explicitly specified. It gets it
from the Server Component parent.
<img width="1107" alt="Screenshot 2024-08-14 at 10 06 36 PM"
src="https://github.com/user-attachments/assets/cfc517cc-e737-44c3-a1be-050049267ee2">
Overall keys get a bit confusing when you apply filter. Especially since
it's so common to actually apply the key on a Host Instance. So you
often don't see the key.
This adds VirtualInstances to the tree. Each Fiber has a list of its
parent Server Components in `_debugInfo`. The algorithm is that when we
enter a set of fibers, we actually traverse level 0 of all the
`_debugInfo` in each fiber. Then level 1 of each `_debugInfo` and so on.
It would be simpler if `_debugInfo` only contained Server Component
since then we could just look at the index in the array but it actually
contains other data as well which leads to multiple passes but we don't
expect it to have a lot of levels before hitting a reified fiber.
Finally when we hit the end a traverse the fiber itself.
This lets us match consecutive `ReactComponentInfo` that are all the
same at the same level. This creates a single VirtualInstance for each
sequence. This lets the same Server Component instance that's a parent
to multiple children appear as a single Instance instead of one per
Fiber.
Since a Server Component's result can be rendered in more than one place
there's not a 1:1 mapping though. If it is in different parents or if
the sequence is interrupted, then it gets split into two different
instances with the same `ReactComponentInfo` data.
The real interesting case is what happens during updates because this
algorithm means that a Fiber can become reparented during an update to
end up in a different VirtualInstance. The ideal would maybe be that the
frontend could deal with this reparenting but instead I basically just
unmount the previous instance (and its children) and mount a new
instance which leads to some interesting scenarios. This is inline with
the strategy I was intending to pursue anyway where instances are
reconciled against the previous children of the same parent instead of
the `fiberToFiberInstance` map - which would let us get rid of that map.
In that case the model is resilient to Fiber being in more than one
place at a time.
However this unmount/remount does mean that we can lose selection when
this happens. We could maybe do something like using the tracked path
like I did for component filters. Ideally it's a weird edge case though
because you'd typically not have it. The main case that it happens now
is for reorders of list of server components. In that case basically all
the children move between server components while the server components
themselves stay in place. We should really include the key in server
components so that we can reconcile them using the key to handle
reorders which would solve the common case anyway.
I convert the name to the `Env(Name)` pattern which allows the
Environment Name to be used as a badge.
<img width="1105" alt="Screenshot 2024-08-13 at 9 55 29 PM"
src="https://github.com/user-attachments/assets/323c20ba-b655-4ee8-84fa-8233f55d2999">
(Screenshot is with #30667. I haven't tried it with the alternative
fix.)
---------
Co-authored-by: Ruslan Lesiutin <rdlesyutin@gmail.com>
Alternative to https://github.com/facebook/react/pull/30667.
Basically wrap every section in a `div` with the same class, and only
apply `border-bottom` for every instance, except for the last child. We
are paying some cost by having more divs, but thats more explicit.
There was a comment that it's not safe to walk the unmounted fiber tree
which I'm not sure is correct or not but we need to walk the instance
tree to be able to clean up virtual instances anyway. We already walk
the instance tree to clean up "remaining instances".
This is also simpler because we don't need to special case Suspense
boundaries. We simply clean up whatever branch we had before.
The ultimate goal is to also walk the instance tree for updates so we
don't need a fiber to instance map.
Stacked on #30625 and #30657.
This ensures that we only create instances during the commit
reconciliation and that we don't create unnecessary instances for things
that are filtered or not mounted. This ensures that we also can rely on
the reconciliation to do all the clean up. Now everything is created and
deleted as a pair in the same pass.
Previously we were including unfiltered components in the owner stack
which probably doesn't make sense since you're intending to filter them
everywhere presumably. However, it also means that those links were
broken since you can't link into owners that don't exist in the parent
tree.
The main complication is the component filters. It relied on not
unmounting the old instances. I had to update some tests that asserted
on ids that are now shifted.
For warnings/errors tracking I now restore them back into the pending
set when they unmount. Basically it puts them back into their
"pre-commit" state. That way when they remount they’re still there.
For restoring the current selection I use the tracked path mechanism
instead of relying on the id being unchanged. This is better anyway
because if you filter out the currently selected item it's better to
select the nearest match instead of just losing the selection.