Commit Graph

20141 Commits

Author SHA1 Message Date
Lauren Tan 2b239d5ea7 [ci] Bump number of shards for test_build to 5
I noticed `test_build` can take a while so let's bump the number of shards to 5.
2025-03-20 17:24:18 -04:00
lauren b888986054 [ci] Rename other stale branch workflow (#32692)
Makes it easier to tell what is what
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32692).
* __->__ #32692
* #32691
2025-03-20 17:19:47 -04:00
lauren addce2f9f2 [ci] Add daily stale branch cache cleanup (#32691)
Cleans up stale non-main caches daily
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32691).
* #32692
* __->__ #32691
2025-03-20 17:17:44 -04:00
lauren 74bcf3d0d2 [ci] Don't fail on cache miss (#32690)
Partially reverts #32686.

PR caches inherit from caches generated in `main`. If it cannot find
that cache, it will create one scoped to just that PR (and PRs that
inherit from it).

There is an edge case where cache eviction can happen in the middle of a
test run. If cache eviction removes a `main` cache, child jobs that
depend on it will start failing because of the `fail-on-cache-miss`
setting.

This PR reverts the default behavior. If this happens, the workflow will
still continue in slow mode where it will `yarn install` child jobs
instead of reusing from cache. This is slower but will at least allow
workflows to continue.

Additionally I added restore keys so that we can fallback to other
caches if present so `yarn install` doesn't need to start over from
scratch.
2025-03-20 17:02:39 -04:00
Ricky b630219b13 [refactor] move isValidElementType to react-is (#32518) 2025-03-20 16:51:33 -04:00
Jack Pope 7943da1e81 Set accurate value for alwaysThrottleRetries on www (#32684)
This flag value was updated in
https://github.com/facebook/react/pull/28965 (seemingly unrelated, maybe
as part of unit testing). But its still controlled by a dynamic flag in
www. Let's update this to VARIANT to accurately represent the state of
the rollout.

Before:
<img width="1340" alt="Screenshot 2025-03-20 at 10 45 30 AM"
src="https://github.com/user-attachments/assets/d0405a36-eb71-4108-9e23-8d462cc68fb4"
/>

After:
<img width="1351" alt="Screenshot 2025-03-20 at 10 45 11 AM"
src="https://github.com/user-attachments/assets/459d260d-7a25-430b-95a6-d6a91d958417"
/>
2025-03-20 14:28:55 -04:00
Joe Savona e3c06424ae [compiler] Refactor validations to return Result and log where appropriate
Updates ~all of our validations to return a Result, and then updates callers to either unwrap() if they should bailout or else just log.

ghstack-source-id: 418b5f5aa2
Pull Request resolved: https://github.com/facebook/react/pull/32688
2025-03-20 11:02:02 -07:00
Joe Savona 5f4c5c920f [compiler] Validate static components
React uses function identity to determine whether a given JSX expression represents the same type of component and should reconcile (keep state, update props) or replace (teardown state, create a new instance). This PR adds off-by-default validation to check that developers are not dynamically creating components during render.

The check is local and intentionally conservative. We specifically look for the results of call expressions, new expressions, or function expressions that are then used directly (or aliased) as a JSX tag. This allows common sketchy but fine-in-practice cases like passing a reference to a component from a parent as props, but catches very obvious mistakes such as:

```js
function Example() {
  const Component = createComponent();
  return <Component />;
}
```

We could expand this to catch more cases, but this seems like a reasonable starting point. Note that I tried enabling the validation by default and the only fixtures that error are the new ones added here. I'll also test this internally. What i'm imagining is that we enable this in the linter but not the compiler.

ghstack-source-id: e7408c0a55
Pull Request resolved: https://github.com/facebook/react/pull/32683
2025-03-20 11:02:02 -07:00
lauren 112224d8d2 [ci] Also cache playground playwright browsers (#32687)
Following #32678, do the same for the playground e2e test since this
step can sometimes take many minutes to complete.
2025-03-20 13:53:31 -04:00
lauren 87d7e4c55b [ci] Fail on cache miss (#32686)
Since we use a centralized cache we should fail subsequent steps if the
child jobs are unable to restore the cache from the first 2 jobs.

Also fix some incorrect hashes used for the fixture tests.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32686).
* __->__ #32686
* #32685
2025-03-20 12:22:06 -04:00
lauren 3bcf8c23de [ci] Warm cache (#32685)
Try restoring from old caches as a base to speed up the case where
node_modules needs updating.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32685).
* #32686
* __->__ #32685
2025-03-20 12:21:50 -04:00
Sebastian Markbåge a4f9bd586b Enable Fragment refs in Experimental (#32670)
That we can test it out in Next.js router conditionally when
experimental is on for other reasons.
2025-03-19 20:38:27 -04:00
lauren ff8f6f21f7 [ci] Fix Will commit these changes www step (#32681)
Unlike the fbsource version of the step, www doesn't add any changes so
the `force` input doesn't actually work
2025-03-19 18:13:06 -04:00
lauren 19176e3c08 [ci] Use correct revision for Meta builds (#32680)
There was a bug previously in our commit artifacts step where the
emitted REVISION hash would reference the commit on the builds branch
rather than from `main`.

Given that our internal manual sync script also does this, let's align
them both to always reference the commit from `main` instead.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32680).
* __->__ #32680
* #32679
* #32678
2025-03-19 17:24:43 -04:00
lauren d16c26da40 [ci] Specify if-no-files-found on actions/upload-artifact@v4 (#32679)
Defaults to warn, but since some steps require these artifacts to be
uploaded we specify an error if its not found. Some other steps like
playwright test-results are only uploaded on failure so it's okay to
ignore.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32679).
* #32680
* __->__ #32679
* #32678
2025-03-19 17:22:40 -04:00
lauren a8c155cab9 [ci] Cache playwright browsers (#32678)
No reason to download them from scratch every time.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32678).
* #32680
* #32679
* __->__ #32678
2025-03-19 17:22:17 -04:00
lauren 995410463a [ci] Parameterize branch cleanup (#32677)
Allow a PR number to be passed as input
2025-03-19 16:39:01 -04:00
lauren 208905257f [ci] Add cache cleanup workflow (#32675)
> Caches have branch scope restriction in place. This means that if
caches for a specific branch are using a lot of storage quota, it may
result into more frequently used caches from default branch getting
thrashed. For example, if there are many pull requests happening on a
repo and are creating caches, these cannot be used in default branch
scope but will still occupy a lot of space till they get cleaned up by
eviction policy. But sometime we want to clean them up on a faster
cadence so as to ensure default branch is not thrashing.


https://github.com/actions/cache/blob/main/tips-and-workarounds.md#force-deletion-of-caches-overriding-default-cache-eviction-policy
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32675).
* __->__ #32675
* #32674
2025-03-19 15:42:04 -04:00
lauren 891a6332e3 [ci] Bump build_and_lint to 25 workers (#32674)
Increases number of workers for `build_and_lint`.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32674).
* #32675
* __->__ #32674
2025-03-19 15:41:48 -04:00
lauren a4842c92ea [ci] Centralize cache (#32672)
To avoid race conditions where multiple jobs try to write to the same
cache, we now centralize saving the cache and then reusing it in every
subsequent job.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32672).
* #32675
* #32674
* __->__ #32672
2025-03-19 15:25:07 -04:00
Niklas Holm ada8bbbd6a [eslint-plugin-react-compiler] Fix type error with recommended config (#32666)
## Summary

In the recommended configuration for `eslint-plugin-react-compiler`,
i.e. `reactCompiler.configs.recommended`, the rule is typed as `string`
rather than `eslint.Linter.RuleEntry` or anything assignable thereto,
which results in the following type error if you type check your eslint
configuration:
```
Property ''react-compiler/react-compiler'' is incompatible with index signature.
  Type 'string' is not assignable to type 'RuleEntry | undefined'.
```
Simply adding a const assertion fixes the error.

## How did you test this change?

I emitted declarations for the module and confirmed that the rule is now
typed as the string literal `'error'`
2025-03-19 14:58:08 -04:00
lauren e9c3b27b4b [ci] Bump all node_modules cache keys (#32671)
I'm seeing a lot of instances of

> Failed to save: Unable to reserve cache with key
runtime-and-compiler-node_modules-v5-X64-Linux-e454609794aae66da9909c77dd6efa073eceff7f44d6527611f8465e102578b4,
another job may be creating this cache.

which is adding ~20 seconds to every step. Let's try to bust the cache
following this
[comment](https://github.com/actions/cache/issues/485#issuecomment-744145040)
and see if that helps.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32671).
* #32672
* __->__ #32671
2025-03-19 14:42:45 -04:00
lauren b0446ff06a [ci] Properly format commit message take 2 (#32673)
We need to use the commit message from `main`, not the builds branch
2025-03-19 14:01:49 -04:00
lauren db7dfe0550 [eprh] Don't transpile to es5 (#32669)
Now that we've moved the sync location of the plugin, we no longer need
this since those restrictions no longer apply.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32669).
* __->__ #32669
* #32668
2025-03-19 11:49:42 -04:00
lauren 646835fb59 [ci] Properly format commit message (#32668)
Using the github variable for the commit message replaces the variable
inline. If the commit message contains quotes or other characters that
need to be escaped, this breaks the workflow.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32668).
* #32669
* __->__ #32668
2025-03-19 11:49:18 -04:00
Sebastian Markbåge c2a1961747 Minor Fixes to View Transition Fixture (#32664)
Follow up to #32656.

Remove touchAction from SwipeRecognizer. I was under the wrong
impression that this was only the touch-action applied to this
particular element, but that parents would still win but in fact this
blocks the parent from scrolling in the other direction. By specifying a
fixed direction it also blocked rage-swiping in the other direction
early on.

Disable pointer-events on view-transition so that the scroll can be hit.
This means that touches hit below the items animating above. This allows
swiping to happen again before momentum scroll has finished. Previously
they were ignored. This only works as long as the SwipeRecognizer is
itself not animating. This means you can now rage-swipe in both
directions quickly.
2025-03-18 19:20:34 -04:00
mofeiZ 6584a6eec4 [compiler] Hoist dependencies from functions more conservatively (#32616)
Alternative to facebook/react#31584 which sets
enableTreatFunctionDepsAsConditional:true` by default.

This PR changes dependency hoisting to be more conservative while trying
to preserve an optimal "happy path". We assume that a function "is
likely called" if we observe the following in the react function body.

- a direct callsite
- passed directly as a jsx attribute or child
- passed directly to a hook
- a direct return

A function is also "likely called" if it is directly called, passed to
jsx / hooks, or returned from another function that "is likely called".

Note that this approach marks the function definition site with its
hoistable properties (not its use site). I tried implementing use-site
hoisting semantics, but it felt both unpredictable (i.e. as a developer,
I can't trust that callbacks are well memoized) and not helpful (type +
null checks of a value are usually colocated with their use site)

In this fixture (copied here for easy reference), it should be safe to
use `a.value` and `b.value` as dependencies, even though these functions
are conditionally called.
```js
// inner-function/nullable-objects/assume-invoked/conditional-call-chain.tsx
function Component({a, b}) {
  const logA = () => {
    console.log(a.value);
  };
  const logB = () => {
    console.log(b.value);
  };
  const hasLogged = useRef(false);
  const log = () => {
    if (!hasLogged.current) {
      logA();
      logB();
      hasLogged.current = true;
    }
  };
  return <Stringify log={log} shouldInvokeFns={true} />;
}
```

On the other hand, this means that we produce invalid output for code
like manually implementing `Array.map`
```js
// inner-function/nullable-objects/bug-invalid-array-map-manual.js
function useFoo({arr1, arr2}) {
  const cb = e => arr2[0].value + e.value;
  const y = [];
  for (let i = 0; i < arr1.length; i++) {
    y.push(cb(arr1[i]));
  }
  return y;
}
```
2025-03-18 18:00:08 -04:00
dan 86d5ac0882 Revert "Fix:- Improve HOC support and state preservation in React Refresh" (#32214)
Reverts facebook/react#30660

I don’t feel confident in the approach. This part of code is supposed to
rely on the module bundler behaving as expected. _Maybe_ this is correct
but I need to review it closer — it was intentionally _not_ implemented
this way originally.

I’ll try to take a closer look some time this week. We don’t have to
merge this revert right now but just flagging that I don’t understand
the thinking behind the new approach and don’t have confidence in it.
2025-03-18 19:05:56 +00:00
Jack Pope 476f53879e Add getClientRects to fragment instances (#32660)
Adds `getClientRects()` to fragment instances with a fixture test case.
`Element.getClientRect` returns a collection of `DOMRect`s (see example
of multiline span returning two `DOMRect` boxes).
`fragmentInstance.getClientRects` here flattens those collections into
an array of rects.
2025-03-18 13:54:26 -04:00
Jack Pope c69a5fc53a Add blur() and focusLast() to fragment instances (#32654)
`focus()` was added in https://github.com/facebook/react/pull/32465.
Here we add `focusLast()` and `blur()`. I also extended `focus` to take
options.

`focus` will focus the first focusable element. `focusLast` will focus
the last focusable element. We could consider a `focusFirst` naming or
even the `focusWithin` used by test selector APIs as well.

`blur` will only have an effect if the current `document.activeElement`
is one of the fragment children.
2025-03-18 11:58:12 -04:00
Sebastian Markbåge a35aaf704c Update ViewTransition fixture to include bigger buttons/swipe (#32656)
I made the button a bit bigger and moved the swipe recognizer around the
whole screen. Typically these are used around the whole content without
any affordances and not as a standalone scrubber. Ideally the swipe
would be able to be inside the animating content but it can't yet due to
[this Safari bug](https://bugs.webkit.org/show_bug.cgi?id=288795).

Added back some paragraphs so that scrolling can be tested properly. It
appears it's possible to get the swipe to be a bit misaligned if you
scroll enough on iOS.

<img width="437" alt="Screenshot 2025-03-17 at 10 27 42 PM"
src="https://github.com/user-attachments/assets/589dc828-717e-420c-83dc-94ae6ad59791"
/>
2025-03-18 09:24:31 -04:00
Sebastian Markbåge 3c3696d554 Measure Updated ViewTransition Boundaries (#32653)
This does the same thing for `measureUpdateViewTransition` that we did
for `measureNestedViewTransitions` in
https://github.com/facebook/react/pull/32612/commits/e3cbaffef05c7b476c07f7495e06788a9503e636.
If a boundary hasn't mutated and didn't change in size, we mark it for
cancellation. Otherwise we add names to it. The different from the
CommitViewTransition path is that the "old" names are added to the
clones so this is the first time the "new" names.

Now we also cancel any boundaries that were unchanged. So now the root
no longer animates. We still have to clone them. There are other
optimizations that can avoid cloning but once we've done all the layouts
we can still cancel the running animation and let them just be the
regular content if they didn't change. Just like the regular
fire-and-forget path.

This also fixes the measurement so that we measure clones by adjusting
their position back into the viewport.

This actually surfaces a bug in Safari that was already in #32612. It
turns out that the old names aren't picked up for some reason and so in
Safari they looked more like a cross-fade than what #32612 was supposed
to fix. However, now that bug is even more apparent because they
actually just disappear in Safari. I'm not sure what that bug is but
it's unrelated to this PR so will fix that separately.
2025-03-17 21:38:13 -04:00
Dimitri POSTOLOV 90b511ec7a fix(react-compiler): implement NumericLiteral as ObjectPropertyKey (#31791) 2025-03-17 19:30:58 -04:00
Sebastian Markbåge 02372952e4 Don't auto-start browser in SSR fixtures (#32652)
I end up restarting these a lot and it's annoying to have it open
another tab each time.

The flight fixture already doesn't auto-start.
2025-03-17 17:26:00 -04:00
Sebastian Markbåge 9fde224a53 Materialize the tree ID when ViewTransition name=auto consumes one (#32651)
ViewTransition uses the `useId` algorithm to auto-assign names. This
ensures that we could animate between SSR content and client content by
ensuring that the names line up.

However, I missed that we need to bump the id (materialize it) when we
do that. This is what function components do if they use one or more
`useId()`. This caused duplicate names when two ViewTransitions were
nested without any siblings since they would share name.
2025-03-17 16:17:01 -04:00
lauren ca02c4bb40 [ci][ez] use yarn --cwd (#32650)
Run yarn install via `--cwd` instead of `working-directory` to make the
labels clearer
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32650).
* __->__ #32650
* #32649
* #32648
2025-03-17 14:13:27 -04:00
lauren c37d89827e [ci] Pin Discord webhook action to specific commit sha (#32649)
Pins the discord webhook action to
`86dc739f3f165f16dadc5666051c367efa1692f4`, which is what the v6 tag
points to.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32649).
* #32650
* __->__ #32649
* #32648
2025-03-17 14:13:17 -04:00
lauren 6c6699f3d2 [ci] Don't use third party action to push commits (#32648)
In light of recent third party actions being compromised, let's just
push the commit ourselves rather than use a third party action. We
already detect if changes are needed, so the step will only run if so.

I also added a `dry_run` option to the manual runs of this workflow for
testing.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32648).
* #32650
* #32649
* __->__ #32648
2025-03-17 14:13:02 -04:00
Andrew Clark 9320a0139d Fix COMMIT_SHA when generating PR artifacts (#32647)
Follow-up to #31850. We want to build using the original commit SHA, not
the merge commit that GitHub Actions creates behind the scenes. We were
already checking out the correct commit object, but the COMMIT_SHA
artifact was still pointing to the merge commit.

This should fix the sizebot links to point to working URLs, too.
2025-03-17 12:46:27 -04:00
Ricky fbcda19a23 [devtools] add filters for internal builds (#32646)
We don't have an experimental-only build of devtools, but we can at
least add these filters to the internal build.

A better way would be to use feature detection, but I'm not sure how and
this isn't a very heavily used feautre.
2025-03-17 12:14:19 -04:00
Jack Pope cd28a946d5 Add observer methods to fragment instances (#32619)
This implements `observeUsing(observer)` and `unobserverUsing(observer)`
on fragment instances. IntersectionObservers and ResizeObservers can be
passed to observe each host child of the fragment. This is the
equivalent to calling `observer.observe(child)` or
`observer.unobserve(child)` for each child target.

Just like the addEventListener, the observer is held on the fragment
instance and applied to any newly mounted child. So you can do things
like wrap a paginated list in a fragment and have each child
automatically observed as they commit in.

Unlike, the event listeners though, we don't `unobserve` when a child is
removed. If a removed child is currently intersecting, the observer
callback will be called when it is removed with an empty rect. This lets
you track all the currently intersecting elements by setting state from
the observer callback and either adding or removing them from your list
depending on the intersecting state. If you want to track the removal of
items offscreen, you'd have to maintain that state separately and append
intersecting data to it in the observer callback. This is what the
fixture example does.

There could be more convenient ways of managing the state of multiple
child intersections, but basic examples are able to be modeled with the
simple implementation. Let's see how the usage goes as we integrate this
with more advanced loggers and other features.

For now you can only attach one observer to an instance. This could
change based on usage but the fragments are composable and could be
stacked as one way to apply multiple observers to the same elements.

In practice, one pattern we expect to enable is more composable logging
such as

```javascript
function Feed({ items }) {
  return (
    <ImpressionLogger>
      {items.map((item) => (
        <FeedItem />
      ))}
    </ImpressionLogger>
  );
}
```

where `ImpressionLogger` would set up the IntersectionObserver using a
fragment ref with the required business logic and various components
could layer it wherever the logging is needed. Currently most callsites
use a hook form, which can require wiring up refs through the tree and
merging refs for multiple loggers.
2025-03-17 11:40:05 -04:00
Ricky 8243f3f063 [bug] Fix component name for Portal and add tests (#32640)
Based off: https://github.com/facebook/react/pull/32499

While looking into `React.lazy` issues for built-ins, I noticed we
already error for `lazy` with build-ins, but we don't have any tests for
`getComponentNameFromType` using all the built-ins. This may be
something we should handle, but for now we should at least have tests.

Here's why: while writing tests, I noticed we check `type` instead of
`$$typeof` for portals:


https://github.com/facebook/react/blob/9cdf8a99edcfd94d7420835ea663edca04237527/packages/react-reconciler/src/ReactPortal.js#L25-L32

This PR adds tests for all the built-ins and fixes the portal bug.

[Commit to
review](https://github.com/facebook/react/pull/32640/commits/e068c167d48d4df01e79db8f13276bb46d7ab439)
2025-03-17 11:23:28 -04:00
Ricky df31952275 Remove offscreen type (#32639)
Based off https://github.com/facebook/react/pull/32499

This is no longer used.

[Review
commit](https://github.com/facebook/react/commit/88c297d12f8b2562be3982fba867f03a137551cb)
2025-03-17 09:37:07 -04:00
Ricky 1a191701fe [refactor] Add element type for Activity (#32499)
This PR separates Activity to it's own element type separate from
Offscreen. The goal is to allow us to add Activity element boundary
semantics during hydration similar to Suspense semantics, without
impacting the Offscreen behavior in suspended children.
2025-03-17 09:17:00 -04:00
Ricky 99563e9173 Partially revert #32588 (#32621)
https://github.com/facebook/react/pull/32588 changed the babel config
impacting local tests, and I'm not able to run test:

<img width="1354" alt="Screenshot 2025-03-15 at 2 37 00 PM"
src="https://github.com/user-attachments/assets/2d4afe39-6ab6-4c83-87a9-ceb0ee5f8df5"
/>


This PR reverts those changes until we can re-land with a fix.
2025-03-15 15:21:57 -04:00
Sebastian Markbåge 17d274dc12 Remove Mutation Check Around commit/measureUpdateViewTransition (#32617)
There's two ways to find updated View Transitions.

One is the "commit/measureNestedViewTransitions" pass which is used to
find things in unchanged subtrees. This can only lead to the relayout
case since there's can't possibly be any mutations in the subtree. This
is only triggered when none of the direct siblings have any mutations at
all.

The other case is "commit/measureUpdateViewTransition" which is for a
ViewTransition that itself has mutations scheduled inside of it which
leads to the "update" case.

However, there's a case between these two cases. When a direct sibling
has a mutation but there's also a ViewTransition exactly at the same
level. In that case we can't bail out on the whole set of children so we
won't trigger the "nested" case. Previously we also didn't trigger the
"commit/measureUpdateViewTransition" case because we first checked if
that had any mutations inside of it at all. This leads to neither case
picking up this boundary.

We could check if the ViewTransition itself has any mutations inside and
if not trigger the nested path.

There's a simpler way though. Because
`commit/measureUpdateViewTransition` is actually just optimistic. The
flags are pessimistic and we don't know for sure if there will actually
be a mutation until we've traversed the tree. It can sometimes lead to
the "relayout" case. So we can just use that same path, knowing that
it'll just lead to the layout pass. Therefore it's safe to just remove
this check.
2025-03-14 17:38:53 -04:00
Sebastian Markbåge 6b5d9fd316 Move traverseFragmentInstanceChildren to internal ReactFiberTreeReflection (#32613)
This is a nit but a Config should not have to know anything about the
internals of Fibers. Ideally it shouldn't even access them but we have
some cases where we need pointers back in like for this fragment.

The way we've typically abstracted this is using the
`ReactFiberTreeReflection` helper that's in the `react-reconciler`. Such
as in the event system.
https://github.com/facebook/react/blob/f3c956006a90dc68210bd3e19497d10fb9b028d3/packages/react-dom-bindings/src/events/ReactDOMEventListener.js#L22-L26

We sometimes cheat but we really should clean this up such that a
`Fiber` is actually an opaque type to the Configs and it can never dot
into it without using a helper.

So this just moves `traverseFragmentInstanceChildren` to
ReactFiberTreeReflection so that the ConfigDOM doesn't ever dot into its
fields itself. It just passes the Fiber through back into the
react-reconciler. I had to add a wrapper to read the `.child` to avoid
that being assumed too. I also noticed that FragmentInstanceType is not
actually passed through so that argument is unnecessary.
2025-03-14 17:38:35 -04:00
Sebastian Markbåge 2c560374d6 Measure and apply names for the "new" phase (#32612)
Stacked on #32599 and #32611.

This is able to reuse the code from CommitViewTransitions for "enter",
"shared" and "layout". The difference is that for "enter"/"shared" in
the "new" phase we pass in the deletions.

For "layout" of nested boundaries we just need to measure the clones at
the same time we measure the original nodes since we haven't measured
them in a previous phase in the current approach.

With these updates, things move around more like expected in the fixture
because we're now applying the appropriate pairs to trigger individual
animations instead of just the full document cross-fade.

The "update" phase is a little more complicated and is coming soon.
2025-03-14 14:26:55 -04:00
Sebastian Markbåge 2e385738a4 Find Pairs and Apply View Transition Names to the Clones in the "old" Phase (#32599)
Stacked on #32578.

We need to apply view-transition-names to the clones that we create in
the "old" phase for the ViewTransition boundaries that should activate.

Finding pairs is a little trickier than in
ReactFiberCommitViewTransitions. Normally we collect all name
"insertions" in the `accumulateSuspenseyCommit` phase before we even
commit. Then in the snapshot do we visit all "deletions" and since we
already collected all the insertions we know immediately if the deletion
had a pair and should therefore get a "name" assigned to activate the
boundary. For ReactFiberApplyGesture we need to assign names to
"insertions" since it's in reverse but we don't already have a map of
deletions. Therefore we need to first visit all deletions.

Instead of doing that in a completely separate pass, we instead visit
deletions in the same pass to find pairs. Since this is in the same pass
we might visit insertions before deletions or vice versa depending on
document order. However, we can deal with this by applying the name to
the insertion when we find the deletion if we've already made the clones
at that point.

Applying names to pure exits, updates or nested (relayout) is a bit more
straight-forward.
2025-03-14 14:15:39 -04:00
Sebastian Markbåge c4a3b92e09 Add more phases to the ReactFiberApplyGesture (#32578)
Stacked on #32585 and #32605.

This adds more loops for the phases of "Apply Gesture". It doesn't
implement the interesting bit yet like adding view-transition-names and
measurements. I'll do that in a separate PR to keep reviewing easier.

The three phases of this approach is roughly:

- Clone and apply names to the "old" state.
- Inside startViewTransition: Apply names to the "new" state. Measure
both the "old" and "new" state to know whether to cancel some of them.
Delete the clones which will include all the "old" names.
- After startViewTransition: Restore "new" names back to no
view-transition-name.

Since we don't have any other Effects in these phases we have a bit more
flexibility and we can avoid extra phases that traverse the tree. I've
tried to avoid any additional passes.

An interesting consequence of this approach is that we could measure
both the "old" and "new" state before `startViewTransition`. This would
be more efficient because we wouldn't need to take View Transition
snapshots of parts of the tree that won't actually animate. However,
that would require an extra pass and force layout earlier. It would also
have different semantics from the fire-and-forget View Transitions
because we could optimize better which can be visible. It would also not
account for any late mutations. So I decided to instead let the layout
be computed by painting as usual and then measure both "old" and "new"
inside the startViewTransition instead. Then canceling anything that
doesn't animate to keep it consistent.

Unfortunately, though there's not a lot of code sharing possible in
these phases because the strategy is so different with the cloning and
because the animation is performed in reverse. The "finishedWork" Fiber
represents the "old" state and the "current" Fiber represents the "new"
state.

The most complicated phase is the cloning. I actually ended up having to
make a very different pattern from the other phases and CommitWork in
general. Because we have to clone as we go and also do other things like
apply names and finding pairs, it has more phases. I ended up with an
approach that uses three different loops. The outer one for updated
trees, one for inserted trees that don't need cloning (doesn't include
reappearing offscreen) and one for not updated trees that still need
cloning. Inside each loop it can also be in different phases which I
track with the `visitPhase` enum - this pattern is kind of new.

Additionally, we need to measure the cloned nodes after we've applied
mutations to them and we have to wait until the whole tree is inserted.
We don't have a reference to these DOM elements in the Fiber tree since
that still refers to the original ones. We need to store the cloned
elements somewhere. So I added a temporary field on the
ViewTransitionState to keep track of any clones owned by that
ViewTransition.

When we deep clone an unchanged subtree we don't have DOM element
instances. It wouldn't be quite safe to try to find them from the tree
structure. So we need to avoid the deep clones if we might need DOM
elements. Therefore we keep traversing in the case where we need to find
nested ViewTransition boundaries that are either potentially affected by
layout or a "pair".

For the other two phases the pattern there's a lot of code duplication
since it's slightly different from the commit ones but they at least
follow the same pattern. For the restore phase I was actually able to
reuse most of the code.

I don't love how much code this is.
2025-03-14 13:20:17 -04:00