Commit Graph

15952 Commits

Author SHA1 Message Date
Andrew Clark
95c9554bc7 useFormState: Compare action signatures when reusing form state (#27370)
During an MPA form submission, useFormState should only reuse the form
state if same action is passed both times. (We also compare the key
paths.)

We compare the identity of the inner closure function, disregarding the
value of the bound arguments. That way you can pass an inline Server
Action closure:

```js
function FormContainer({maxLength}) {
  function submitAction(prevState, formData) {
    'use server'
    if (formData.get('field').length > maxLength) {
      return { errorMsg: 'Too many characters' };
    }
    // ...
  }
  return <Form submitAction={submitAction} />
}
```
2023-09-13 20:46:22 -04:00
Andrew Clark
612b2b6601 useFormState: Reuse state from previous form submission (#27321)
If a Server Action is passed to useFormState, the action may be
submitted before it has hydrated. This will trigger a full page
(MPA-style) navigation. We can transfer the form state to the next page
by comparing the key path of the hook instance.

`ReactServerDOMServer.decodeFormState` is used by the server to extract
the form state from the submitted action. This value can then be passed
as an option when rendering the new page. It must be passed during both
SSR and hydration.

```js
const boundAction = await decodeAction(formData, serverManifest);
const result = await boundAction();
const formState = decodeFormState(result, formData, serverManifest);

// SSR
const response = createFromReadableStream(<App />);
const ssrStream = await renderToReadableStream(response, { formState })

// Hydration
hydrateRoot(container, <App />, { formState });
```

If the `formState` option is omitted, then the state won't be
transferred to the next page. However, it must be passed in both places,
or in neither; misconfiguring will result in a hydration mismatch.

(The `formState` option is currently prefixed with `experimental_`)
2023-09-13 18:30:40 -04:00
Sebastian Markbåge
e5205658f4 [Fizz] Various smaller refactors (#27368)
Back ported from a larger PR.
2023-09-13 00:16:44 -04:00
Andrew Clark
69be472c11 Fix: Initialize childIndex in Task constructor (#27367)
This field was not being initialized. Although the property is part of
the Flow type, the type error wasn't caught because the constructor
itself is not covered by Flow, which is unfortunate. (I assume this is
related to the dev-only componentStack property.)
2023-09-12 22:17:03 -04:00
Andrew Clark
d07921eeda Don't modify keyPath until right before recursive renderNode call (#27366)
Currently, if a component suspends, the keyPath has already been
modified to include the identity of the component itself; the path is
set before the component body is called (akin to the begin phase in
Fiber). An accidental consequence is that when the promise resolves and
component is retried, the identity gets appended to the keyPath again,
leading to a duplicate node in the path.

To address this, we should only modify contexts after any code that may
suspend. For maximum safety, this should occur as late as possible:
right before the recursive renderNode call, before the children are
rendered.

I did not add a test yet because there's no feature that currently
observes it, but I do have tests in my other WIP PR for useFormState:
#27321
2023-09-12 21:27:23 -04:00
Sebastian Markbåge
7a3cb8f9cf Track the key path difference between right before the first array (#27360)
There's a subtle difference if you suspend before the first array or
after. In Fiber, we don't deal with this because we just suspend the
parent and replay it if lazy() or Usable are used in its child slots. In
Fizz we try to optimize this a bit more and enable resuming inside the
component.

Semantically, it's different if you suspend/postpone before the first
child array or inside that child array. Because when you resume the
inner result might be another array and either that's part of the parent
path or part of the inner slot.

There might be more clever way of structuring this but I just use -1 to
indicate that we're not yet inside the array and is in the root child
position. If that renders an element, then that's just the same as the 0
slot.

We need to also encode this in the resuming. I called that resuming the
element or resuming the slot.
2023-09-12 12:30:42 -04:00
Josh Story
bbc8530ed7 [Float] Refactor public interface and internal HostDispatcher implementation (#27361)
When Float was first developed the internal implementation and external
interface were the same. This is problematic for a few reasons. One, the
public interface is typed but it is also untrusted and we should not
assume that it is actually respected. Two, the internal implementations
can get called from places other than the the public interface and
having to construct an options argument that ends up being destructured
to process the request is computationally wasteful and may limit JIT
optimizations to some degree. Lastly, the wire format was not as
compressed as it could be and it was untyped.

This refactor aims to address that by separating the public interface
from the internal implementations so we can solve these challenges and
also make it easier to change Float in the future

* The internal dispatcher method preinit is now preinitStyle and
preinitScript.
* The internal dispatcher method preinitModule is now
preinitModuleScript in anticipation of different implementations for
other module types in the future.
* The wire format is explicitly typed and only includes options if they
are actually used omitting undefined and nulls.
* Some function arguments are not options even if they are optional. For
instance precedence can be null/undefined because we deafult it to
'default' however we don't cosnider this an option because it is not
something we transparently apply as props to the underlying instance.
* Fixes a problem with keying images in flight where srcset and sizes
were not being taken into account.
* Moves argument validation into the ReactDOMFloat file where it is
shared with all runtimes that expose these methods
* Fixes crossOrigin serialization to use empty string except when
'use-credentials'
2023-09-12 08:09:10 -07:00
Ruslan Lesiutin
2eed132847 refactor[devtools/extension]: more stable element updates polling to avoid timed out errors (#27357)
Some context:
- When user selects an element in tree inspector, we display current
state of the component. In order to display really current state, we
start polling the backend to get available updates for the element.

Previously:
- Straight-forward sending an event to get element updates each second.
Potential race condition is not handled in any form.
- If user navigates from the page, timeout wouldn't be cleared and we
would potentially throw "Timed out ..." error.
- Bridge disconnection is not handled in any form, if it was shut down,
we could spam with "Timed out ..." errors.

With these changes:
- Requests are now chained, so there can be a single request at a time.
- Handling both navigation and shut down events.

This should reduce the number of "Timed out ..." errors that we see in
our logs for the extension. Other surfaces will also benefit from it,
but not to the full extent, as long as they utilize
"resumeElementPolling" and "pauseElementPolling" events.

Tested this on Chrome, running React DevTools on multiple tabs,
explicitly checked the case when service worker is in idle state and we
return back to the tab.
2023-09-12 15:05:39 +01:00
Sebastian Markbåge
bb1d8d1667 Encode lazy Node slots properly in key path and resumable paths (#27359)
It's possible to postpone a specific node and not using a wrapper
component. Therefore we encode the resumable slot as the index slot.
When it's a plain client component that postpones, it's encoded as the
child slot inside that component which is the one that's postponed
rather than the component itself.

Since it's possible for a child slot to suspend (e.g. React.lazy's
microtask in this case) retryTask might need to keep its index around
when it resolves.
2023-09-11 19:25:24 -04:00
Andrew Clark
a4aceafc63 Fix: Skip hidden inputs before text instance (#27358)
Found a hydration bug that happens when you pass a Server Action to
`formAction` and the next node is a text instance.

The HTML generated by Fizz is something like this:

```html
<button name="$ACTION_REF_5" formAction="" formEncType="multipart/form-data" formMethod="POST">
  <input type="hidden" name="$ACTION_5:0" value="..."/>
  <input type="hidden" name="$ACTION_5:1" value="..."/>
  <input type="hidden" name="$ACTION_KEY" value="..."/>Count: <!-- -->0
</button>
```

Fiber is supposed to skip over the extra hidden inputs, but it doesn't
handle this correctly if the next expected node isn't a host instance.
In this case, it's a text instance.

Not sure if the proper fix is to change the HTML that is generated, or
to change the hydration logic, but in this PR I've done the latter.
2023-09-11 18:22:37 -04:00
Pavel
627b7abd62 feat[devtools/extension]: add dark theme for popup (#27330)
## Summary

If the system/browser uses a dark theme, then the extension popup still
remains with a light background. And so that the eyes do not hurt in a
dark room, you need a dark theme.

## How did you test this change?

Make sure the dark theme is enabled on your system/browser.

**Before:**

![Screenshot 2023-09-02 at 22 20
56](https://github.com/facebook/react/assets/19418601/f7166ea7-f562-4d11-8851-be08fa9629a7)

**After:**

![Screenshot 2023-09-02 at 22 21
30](https://github.com/facebook/react/assets/19418601/e51ecd5f-3e71-4193-83ff-a548bce76bd4)
2023-09-11 14:02:54 +01:00
Josh Story
41f0e9dae3 [Float][Fizz] include preloadModule and preinitModule on react-dom/server-rendering-stub (#27347)
When implementing `preloadModule` and `preinitModule` these methods were
not exposed on the server rendering stub when they should have been.
This PR corrects that omission.
2023-09-07 14:48:22 -07:00
Andrew Clark
8b26f07a88 useFormState: Emit comment to mark whether state matches (#27307)
A planned feature of useFormState is that if the page load is the result
of an MPA-style form submission — i.e. a form was submitted before it
was hydrated, using Server Actions — the state of the hook should
transfer to the next page.

I haven't implemented that part yet, but as a prerequisite, we need some
way for Fizz to indicate whether a useFormState hook was rendered using
the "postback" state. That way we can do all state matching logic on the
server without having to replicate it on the client, too.

The approach here is to emit a comment node for each useFormState hook.
We use one of two comment types: `<!--F-->` for a normal useFormState
hook, and `<!--F!-->` for a hook that was rendered using the postback
state. React will read these markers during hydration. This is similar
to how we encode Suspense boundaries.

Again, the actual matching algorithm is not yet implemented — for now,
the "not matching" marker is always emitted.

We can optimize this further by not emitting any markers for a render
that is not the result of a form postback, which I'll do in subsequent
PRs.
2023-09-07 16:05:44 -04:00
Josh Story
3566de59e2 [Fizz][Float] <img> inside <picture> should not preload during SSR (#27346)
img tags inside picture tags should not automatically be preloaded
because usually the img is a fallback. We will consider a more
comprehensive way of preloading picture tags which may require a
technique like using an inline script to construct the image in the
browser but for now we simply omit the preloads to avoid harming load
times by loading fallbacks.
2023-09-07 12:48:40 -07:00
Sebastian Markbåge
953cb02f6d [Fizz] Split createRequest into createRequest, createPrerenderRequest and resumeRequest (#27342)
Just moving some internal code around again.

I originally encoded what type of work using startRender vs
startPrerender. I had intended to do more forking of the work loop but
we've decided not to go with that strategy. It also turns out that
forking when we start working is actually too late because of a subtle
thing where you can call abort before work begins. Therefore it's
important that starting the work comes later.
2023-09-07 00:18:30 -04:00
Andrew Clark
ee7f9c9351 useId: Remove unnecessary try/finally blocks (#27340)
To generate IDs for useId, we modify a context variable whenever
multiple siblings are rendered, or when a component includes a useId
hook.

When this happens, we must ensure that the context is reset properly on
unwind if something errors or suspends. When I originally implemented
this, I did this by wrapping the child's rendering with a try/finally
block. But a better way to do this is by using the non-destructive
renderNode path instead of renderNodeDestructive.
2023-09-06 16:30:29 -04:00
Josh Story
b9be4537c2 [Flight] provide property descriptors for client references (#27328)
Client reference proxy should implement getOwnPropertyDescriptor. One
practical place where this shows up is when consuming CJS module.exports
in ESM modules. Node creates named exports it statically infers from the
underlying source but it only sets the named export if the CJS exports
hasOwnProperty. This trap will allow the proxy to respond affirmatively.

I did not add unit tests because contriving the ESM <-> CJS scenario in
Jest is challenging. I did add new components to the flight fixture
which demonstrate that the named exports are properly constructed with
the client reference whereas they were not before.
2023-09-05 13:45:16 -07:00
Sebastian Markbåge
2c2bdd0ffe [Fizz] Move /static build into /server builds (#27327)
This joins the static (prerender) builds with the server builds but
doesn't change the public entry points.

The idea of two separate bundles is that we'd have a specialized build
for Fizz just for the prerender that could do a lot more. However, in
practice the code is implemented with a dynamic check so it's in both.
It's also not a lot of code.

At the same time if you do have a set up that includes both the
prerender and the render in the same build output, this just doubles the
server bundle size for no reason.

So we might as well merge them into one build. However, I don't expose
the `prerender` from `/server`. Instead it's just exposed from the
public `/static` entry point. This leaves us with the option to go back
to separate builds later if it diverges more in the future.
2023-09-05 15:55:20 -04:00
Sebastian Markbåge
3cc8a9347b [Fizz] Move formatContext tracking back to the task (#27325)
In https://github.com/facebook/react/pull/21113 I moved this over to the
segment from the task. This partially reverts this two use two fields
instead. I was just trying to micro-optimize by reusing a single field.

This is really conceptually two different values. Task is keeping track
of the working state of the currently executing context.

The segment just needs to keep track of which parent context it was
created in so that it can be wrapped correctly when a segment is
written. We just happened to rely on the working state returning to the
top before completing.

The main motivation is that there is no `segment` for replaying.
2023-09-05 15:55:07 -04:00
Ruslan Lesiutin
a374287fea React DevTools 4.28.2 -> 4.28.3 (#27337)
This is a patch version to fix some bugs in a previous internal release.
I am expecting this one also to be internal-only, need to make sure that
extension is stable in Chrome. Some changes and improvements are
expected for Firefox, though, before going public.

* refactor[devtools/extension]: handle ports disconnection, instead of
frequent reconnection ([hoxyq](https://github.com/hoxyq) in
[#27336](https://github.com/facebook/react/pull/27336))
* refactor[devtools/extension]: migrate from using setInterval for
polling if react is loaded ([hoxyq](https://github.com/hoxyq) in
[#27323](https://github.com/facebook/react/pull/27323))
* fix[devtools/extension]: fixed duplicating panels in firefox
([hoxyq](https://github.com/hoxyq) in
[#27320](https://github.com/facebook/react/pull/27320))
2023-09-05 18:58:27 +01:00
Ruslan Lesiutin
a27df56a5c refactor[devtools/extension]: handle ports disconnection, instead of frequent reconnection (#27336)
- Instead of reconnecting ports from devtools page and proxy content
script, now handling their disconnection properly
- `proxy.js` is now dynamically registered as a content script, which
loaded for each page. This will probably not work well for Firefox,
since we are still on manifest v2, I will try to fix this in the next
few PRs.
- Handling the case when devtools page port was reconnected and bridge
is still present. This could happen if user switches the tab and Chrome
decides to kill service worker, devtools page port gets disconnected,
and then user returns back to the tab. When port is reconnected, we
check if bridge message listener is present, connecting them if so.
- Added simple debounce when evaluating if page has react application
running. We start this check in `chrome.network.onNavigated` listener,
which is asynchronous. Also, this check itself is asynchronous, so
previously we could mount React DevTools multiple times if navigates
multiple times while `chrome.devtools.inspectedWindow.eval` (which is
also asynchronous) can be executed.
00b7c43318/packages/react-devtools-extensions/src/main/index.js (L575-L583)



https://github.com/facebook/react/assets/28902667/9d519a77-145e-413c-b142-b5063223d073
2023-09-05 18:41:39 +01:00
Ruslan Lesiutin
9b4f847d93 refactor[devtools/extension]: migrate from using setInterval for polling if react is loaded (#27323)
`chrome.devtools.inspectedWindow.eval` is asynchronous, so using it in
`setInterval` is a mistake.
Sometimes this results into mounting React DevTools twice, and user sees
errors about duplicated fibers in store.

With these changes, `executeIfReactHasLoaded` executed recursively with
a threshold (in case if page doesn't have react).

Although we minimize the risk of mounting DevTools twice here, this
approach is not the best way to have this problem solved. Dumping some
thoughts and ideas that I've tried, but which are out of the scope for
this release, because they can be too risky and time-consuming.
Potential changes:
- Have 2 content scripts:
  - One `prepareInjection` to notify service worker on renderer attached
- One which runs on `document_idle` to finalize check, in case if there
is no react
- Service worker will notify devtools page that it is ready to mount
React DevTools panels or should show that there is no React to be found
- Extension port from devtools page should be persistent and connected
when `main.js` is executed
- Might require refactoring the logic of how we connect devtools and
proxy ports
  
  
Some corner cases:
- Navigating to restricted pages, like `chrome://<something>` and back
- When react is lazily loaded, like in an attached iframe, or just
opened modal
- In-tab navigation with pre-cached pages, I think only Chrome does it
- Firefox is still on manifest v2 and it doesn't allow running content
scripts in ExecutionWorld.MAIN, so it requires a different approach
2023-09-01 16:23:04 +01:00
Ruslan Lesiutin
7022e8d6a3 fix[devtools/extension]: fixed duplicating panels in firefox (#27320)
Multiple `chrome.panels.create` calls result into having duplicate
panels created in Firefox, these changes fix that.

Now calling `chrome.panels.create` only if there are no panels created
yet.
2023-08-31 18:24:26 +01:00
Sebastian Markbåge
b70a0d7022 [Fizz] Track postponed holes in the prerender pass (#27317)
This is basically the implementation for the prerender pass.

Instead of forking basically the whole implementation for prerender, I
just add a conditional field on the request. If it's `null` it behaves
like before. If it's non-`null` then instead of triggering client
rendered boundaries it triggers those into a "postponed" state which is
basically just a variant of "pending". It's supposed to be filled in
later.

It also builds up a serializable tree of which path can be followed to
find the holes. This is basically a reverse `KeyPath` tree.

It is unfortunate that this approach adds more code to the regular Fizz
builds but in practice. It seems like this side is not going to add much
code and we might instead just want to merge the builds so that it's
smaller when you have `prerender` and `resume` in the same bundle -
which I think will be common in practice.

This just implements the prerender side, and not the resume side, which
is why the tests have a TODO. That's in a follow up PR.
2023-08-31 12:23:26 -04:00
Ruslan Lesiutin
3808b01b3a React DevTools 4.28.1 -> 4.28.2 (#27318)
List of changes:
* fix[devtools/extension]: handle tab navigation events before react is
loaded ([hoxyq](https://github.com/hoxyq) in
[#27316](https://github.com/facebook/react/pull/27316))
2023-08-30 19:47:08 +01:00
Ruslan Lesiutin
29b405b2de fix[devtools/extension]: handle tab navigation events before react is loaded (#27316)
This is mostly hotfix for https://github.com/facebook/react/pull/27215.

Contains 3 fixes:
- Handle cases when `react` is not loaded yet and user performs in-tab
navigation. Previously, because of the uncleared interval we would try
to mount DevTools twice, resulting into multiple errors.
- Handle case when extension port disconnected (probably by the browser
or just due to its lifetime)
- Removed duplicate `render()` call on line 327
2023-08-30 19:31:18 +01:00
Josh Story
d23b8b5dbf [Flight] use opaque config for flight in dom-legacy renderer (#27313)
`dom-legacy` does not make sense for Flight. we could still type check
the files but it adds maintenance burden in the inlinedHostConfigs
whenever things change there. Going to make these configs opaque mixed
types to quiet flow since no entrypoints use the flight code
2023-08-30 10:32:25 -07:00
Andrew Clark
2fba484cd0 useFormState fix: action -> target (#27309)
I mixed these attributes up in
https://github.com/facebook/react/pull/27302
2023-08-29 22:17:15 -04:00
Andrew Clark
ddff504695 useFormState's permalink option changes form target (#27302)
When the `permalink` option is passed to `useFormState`, and the form is
submitted before it has hydrated, the permalink will be used as the
target of the form action, enabling MPA-style form submissions.

(Note that submitting a form without hydration is a feature of Server
Actions; it doesn't work with regular client actions.)

It does not have any effect after the form has hydrated.
2023-08-29 11:58:44 -04:00
idango10
eaa696876e fix: devtools source field disappears after component remount (#27297)
## Summary

Fixes: https://github.com/facebook/react/issues/27296

On actions that cause a component to change its signature, and therefore
to remount, the `_debugSource` property of the fiber updates in delay
and causes the `devtools` source field to vanish.

This issue happens in
https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberBeginWork.js

```js
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  if (__DEV__) {
    if (workInProgress._debugNeedsRemount && current !== null) {
      // This will restart the begin phase with a new fiber.
      return remountFiber(
        current,
        workInProgress,
        createFiberFromTypeAndProps(
          workInProgress.type,
          workInProgress.key,
          workInProgress.pendingProps,
          workInProgress._debugOwner || null,
          workInProgress.mode,
          workInProgress.lanes,
        ),
      );
    }
  }

  // ...
```

`remountFiber` uses the 3rd parameter as the new fiber
(`createFiberFromTypeAndProps(...)`), but this parameter doesn’t contain
a `_debugSource`.

## How did you test this change?

Tested by monkey patching
`./node_modules/react-dom/cjs/react-dom.development.js`:
<img width="1749" alt="image"
src="https://github.com/facebook/react/assets/75563024/ccaf7fab-4cc9-4c05-a48b-64db6f55dc23">


https://github.com/facebook/react/assets/75563024/0650ae5c-b277-44d1-acbb-a08d98bd38e0
2023-08-29 16:42:35 +01:00
Noah Lemen
4129ea8c92 set priority on TaskController instead of on postTask/yield (#27295)
## Summary

passing both a signal and a priority to `postTask`/`yield` in chrome
causes memory to spike and potentially causes OOMs. a fix for this has
landed in chrome 118, but we can avoid the issue in earlier versions by
setting priority on just the TaskController instead.

https://bugs.chromium.org/p/chromium/issues/detail?id=1469367

## How did you test this change?
```
yarn test SchedulerPostTask
```
2023-08-29 09:06:25 -04:00
Ruslan Lesiutin
2f368725fb React DevTools 4.28.0 -> 4.28.1 (#27305)
List of changes:
* refactor: refactored devtools browser extension scripts to improve
port management and service worker lifetime
([hoxyq](https://github.com/hoxyq) in
[#27215](https://github.com/facebook/react/pull/27215))
* refactor[devtools/extension]: minify production builds to strip
comments ([hoxyq](https://github.com/hoxyq) in
[#27304](https://github.com/facebook/react/pull/27304))
* fix[devtools]: allow element updates polling only if bridge is alive
([hoxyq](https://github.com/hoxyq) in
[#27067](https://github.com/facebook/react/pull/27067))
* refactor: resolve browser via env variables based on build rather than
user agent ([hoxyq](https://github.com/hoxyq) in
[#27179](https://github.com/facebook/react/pull/27179))
* fix[devtools/updateFiberRecursively]: mount suspense fallback set in
timed out case ([hoxyq](https://github.com/hoxyq) in
[#27147](https://github.com/facebook/react/pull/27147))
* Feat:-Added open in editor to appear by default
([Biki-das](https://github.com/Biki-das) in
[#26949](https://github.com/facebook/react/pull/26949))
* fix[devtools/inspect]: null check memoized props before trying to call
hasOwnProperty ([hoxyq](https://github.com/hoxyq) in
[#27057](https://github.com/facebook/react/pull/27057))
* rename SuspenseList export to unstable_SuspenseList
([noahlemen](https://github.com/noahlemen) in
[#27061](https://github.com/facebook/react/pull/27061))
2023-08-29 13:37:35 +01:00
Ruslan Lesiutin
8fbd307942 refactor: refactored devtools browser extension scripts to improve port management and service worker lifetime (#27215)
Fixes https://github.com/facebook/react/issues/27119,
https://github.com/facebook/react/issues/27185.

Fixed:
- React DevTools now works as expected when user performs in-tab
navigation, previously it was just stuck.


https://github.com/facebook/react/assets/28902667/b11c5f84-7155-47a5-8b5a-7e90baca5347

- When user closes browser DevTools panel, we now do some cleanup to
disconnect ports and emit shutdown event for bridge. This should fix the
issue with registering duplicated fibers with the same id in Store.

Changed:
- We reconnect proxy port once in 25 seconds, in order to [keep service
worker
alive](https://developer.chrome.com/docs/extensions/whatsnew/#m110-sw-idle).
- Instead of unregistering dynamically injected content scripts, wen now
get list of already registered scripts and filter them out from scripts
that we want to inject again, see dynamicallyInjectContentScripts.js.
- Split `main.js` and `background.js` into multiple files.

Tested on Chromium and Firefox browsers.
2023-08-29 12:09:26 +01:00
Ruslan Lesiutin
f47956239f refactor[devtools/extension]: minify production builds to strip comments (#27304)
Currently, we are unable to publish a release to Firefox extensions
store, due to `parseHookNames` chunk size, which is ~5mb.

We were not minifying production builds on purpose, to have more
descriptive error messages. Now, Terser plugin will only:
- remove comments
- mangle, but keeping function names (for understandable bug reports)
2023-08-29 12:09:13 +01:00
Ruslan Lesiutin
41e9c17a69 fix[devtools]: allow element updates polling only if bridge is alive (#27067)
When some element is inspected in DevTools, we have a polling which
updates the data for this element.
Sometimes when service worker dies or bridge is getting shutdown, we
continue to poll this data and will spam with the same "timed out
error".

<img width="1728" alt="Screenshot 2023-07-28 at 17 39 23"
src="https://github.com/facebook/react/assets/28902667/220c4504-1ccc-4e87-9d78-bfff8b708230">


These changes add an explicit check that polling is allowed only while
bridge is alive.
2023-08-29 10:40:20 +01:00
Ruslan Lesiutin
2c4c847188 refactor: resolve browser via env variables based on build rather than user agent (#27179)
Fixes https://github.com/facebook/react/issues/26911,
https://github.com/facebook/react/issues/26860.

Currently, we are parsing user agent string to determine which browser
is running the extension. This doesn't work well with custom user
agents, and sometimes when user turns on mobile dev mode in Firefox, we
stop resolving that this is a Firefox browser, extension starts to use
Chrome API's and fails to inject.

Changes:
Since we are building different extensions for all supported browsers
(Chrome, Firefox, Edge), we predefine env variables for browser
resolution, which are populated in a build step.
2023-08-29 10:40:02 +01:00
Andrew Clark
456d153bb5 Client implementation of useFormState (#27278)
This implements useFormState in Fiber. (It does not include any
progressive enhancement features; those will be added later.)

useFormState is a hook for tracking state produced by async actions. It
has a signature similar to useReducer, but instead of a reducer, it
accepts an async action function.

```js
async function action(prevState, payload) {
  // ..
}
const [state, dispatch] = useFormState(action, initialState)
```

Calling dispatch runs the async action and updates the state to the
returned value.

Async actions run before React's render cycle, so unlike reducers, they
can contain arbitrary side effects.
2023-08-28 11:05:40 -04:00
Andrew Clark
9a01c8b54e Fix mount-or-update check in rerenderOptimistic (#27277)
I noticed this was wrong because it should call updateWorkInProgressHook
before it checks if currentHook is null.
2023-08-28 11:04:46 -04:00
Sebastian Markbåge
b798223a62 Override .bind on Server References on the Client (#27282)
That way when you bind arguments to a Server Reference, it's still a
server reference and works with progressive enhancement.

This already works on the Server (RSC) layer.
2023-08-25 21:39:55 -04:00
Andrew Clark
ab31a9ed28 Selective Hydration: Don't suspend if showing fallback (#27230)
A transition that flows into a dehydrated boundary should not suspend if
the boundary is showing a fallback.

This is related to another issue where Fizz streams in the initial HTML
after a client navigation has already happened. That issue is not fixed
by this commit, but it does make it less likely. Need to think more
about the larger issue.
2023-08-24 20:04:34 -04:00
Josh Story
9d4582dffd [Float][Fizz][Static] add importMap option to Fizz and Static server renderers (#27260)
Import maps need to be emitted before any scripts or preloads so the
browser can properly locate these resources.

Unlike most scripts, importmaps are singletons meaning you can only have
one per document and they must appear before any modules are loaded or
preloaded. In the future there may be a way to dynamically add more
mappings however the proposed API for this seems likely to be a
javascript API and not an html tag.

Given the unique constraints here this PR implements React's support of
importMaps as the following

1. an `importMap` option accepting a plain object mapping module
specifier to path is accepted in any API that renders a preamble (head
content). Notably this precludes resume rendering because in resume
cases the preamble should have already been produced as part of the
prerender step.
2. the importMap is stringified and emitted as a `<script
type="importmap">...</script>` in the preamble.
3. the importMap is escaped identically to how bootstrapScriptContent is
escaped, notably, isntances of `</script>` are escaped to avoid breaking
out of the script context

Users can still render importmap tags however with Float enabled this is
rather pointless as most modules will be hoisted above the importmap
that is rendered. In practice this means the only functional way to use
import maps with React is to use this config API.
2023-08-24 13:48:28 -07:00
Andrew Clark
b4cdd3e892 Scaffolding for useFormState (#27270)
This exposes, but does not yet implement, a new experimental API called
useFormState. It's gated behind the enableAsyncActions flag.

useFormState has a similar signature to useReducer, except instead of a
reducer it accepts an (async) action function. React will wait until the
promise resolves before updating the state:

```js
async function action(prevState, payload) {
  // ..
}
const [state, dispatch] = useFormState(action, initialState)
```

When used in combination with Server Actions, it will also support
progressive enhancement — a form that is submitted before it has
hydrated will have its state transferred to the next page. However, like
the other action-related hooks, it works with fully client-driven
actions, too.
2023-08-23 10:58:09 -04:00
Sebastian Markbåge
856dc5e433 Fix escaping in action error URL (#27273)
This URL is generated on the client (there's an equivalent but shorter
SSR version too) when a function is used as an action. It should never
happen but it'll be invoked if a form is manually submitted or event is
stopped early.

The `'` wasn't escaped so this yielded invalid syntax. Which is an error
too but much less helpful. `missing ) after argument list`. Added a test
that evals to make sure it's correct syntax.
2023-08-22 19:10:00 -04:00
Sebastian Markbåge
31034b6de7 [Fizz] Split ResponseState/Resources into RenderState/ResumableState (#27268)
This exposes a `resume()` API to go with the `prerender()` (only in
experimental). It doesn't work yet since we don't yet emit the postponed
state so not yet tested.

The main thing this does is rename ResponseState->RenderState and
Resources->ResumableState. We separated out resources into a separate
concept preemptively since it seemed like separate enough but probably
doesn't warrant being a separate concept. The result is that we have a
per RenderState in the Config which is really just temporary state and
things that must be flushed completely in the prerender. Most things
should be ResumableState.

Most options are specified in the `prerender()` and transferred into the
`resume()` but certain options that are unique per request can't be.
Notably `nonce` is special. This means that bootstrap scripts and
external runtime can't use `nonce` in this mode. They need to have a CSP
configured to deal with external scripts, but not inline.

We need to be able to restore state of things that we've already emitted
in the prerender. We could have separate snapshot/restore methods that
does this work when it happens but that means we have to explicitly do
that work. This design is trying to keep to the principle that we just
work with resumable data structures instead so that we're designing for
it with every feature. It also makes restoring faster since it's just
straight into the data structure.

This is not yet a serializable format. That can be done in a follow up.

We also need to vet that each step makes sense. Notably stylesToHoist is
a bit unclear how it'll work.
2023-08-22 15:21:36 -04:00
Josh Story
86198b9231 [Float][Fizz][Legacy] hoisted elements no longer emit before <html> in legacy apis such as renderToString() (#27269)
renderToString is a legacy server API which used a trick to avoid having
the DOCTYPE included when rendering full documents by setting the root
formatcontext to HTML_MODE rather than ROOT_HTML_MODE. Previously this
was of little consequence but with Float the Root mode started to be
used for things like determining if we could flush hoistable elements
yet. In issue #27177 we see that hoisted elements can appear before the
<html> tag when using a legacy API `renderToString`.

This change exports a DOCTYPE from FizzConfigDOM and FizzConfigDOMLegacy
respectively, using an empty chunk in the legacy case. The only runtime
perf cost here is that for legacy APIs there is an extra empty chunk to
write when rendering a top level <html> tag which is trivial enough

Fixes #27177
2023-08-22 10:54:33 -07:00
Andrew Clark
dd480ef923 Fix: Stylesheet in error UI suspends indefinitely (#27265)
This fixes the regression test added in the previous commit. The
"Suspensey commit" implementation relies on the
`shouldRemainOnPreviousScreen` function to determine whether to 1)
suspend the commit 2) activate a parent fallback and schedule a retry.
The issue was that we were sometimes attempting option 2 even when there
was no parent fallback.

Part of the reason this bug landed is due to how `throwException` is
structured. In the case of Suspensey commits, we pass a special "noop"
thenable to `throwException` as a way to trigger the Suspense path. This
special thenable must never have a listener attached to it. This is not
a great way to structure the logic, it's just a consequence of how the
code evolved over time. We should refactor it into multiple functions so
we can trigger a fallback directly without having to check the type. In
the meantime, I added an internal warning to help detect similar
mistakes in the future.
2023-08-22 11:22:30 -04:00
Jan Kassens
e76a5aca78 [easy] remove unused file MaxInts.js (#27259)
While poking around, I noticed this wasn't used.
2023-08-22 10:42:27 -04:00
Jan Kassens
29556a6b0a Flow: upgrade to 0.215.0 (#27264)
Simple bump of Flow and Hermes parser to keep us current.
2023-08-22 10:41:04 -04:00
Jan Kassens
3f5b8c214f Jest: remove haste config (#27257)
Since we're not using haste at all, we can just remove the config to
disable haste instead of enabling, just to inject an implementation that
blocks any haste modules from being recognized.

Test Plan:
Creating a module and required it to get the expected error that the
module doesn't exist.
2023-08-22 10:12:28 -04:00
Andrew Clark
7d1c3c1589 Regression test: Stylesheet suspends indefinitely when part of error boundary UI during initial hydration (#27258)
Adds a failing test for a case discovered by Next.js. An error boundary
is triggered during initial hydration, and the error fallback includes a
stylesheet. If the stylesheet has not yet been loaded, the commit
suspends, but never resolves even after the stylesheet finishes loading.

Triggering this bug depends on several very specific code paths being
triggered simultaneously. There are a few ways we could fix the bug;
I'll submit as one or more separate PRs to show that each one is
sufficient.
2023-08-21 19:14:56 -04:00