* Fix native event batching in concurrent mode
* Wrap DevTools test updates with act
These tests expect the `scheduleUpdate` DevTools hook to trigger a
synchronous re-render with legacy semantics, but flushing in a microtask
is fine. Wrapping the updates with `act` fixes it.
* Testing nits
* Nit: Check executionContext === NoContext first
In the common case it will be false and the binary expression will
short circuit.
Co-authored-by: Andrew Clark <git@andrewclark.io>
Some legacy environments can not encode non-strings. Those would specify
both as strings. They'll throw for binary data.
Some environments have to encode strings (like web streams). Those would
encode both as uint8array.
Some environments (like Node) can do either. It can be beneficial to leave
things as strings in case the native stream can do something smart with it.
* Move DOM/Native format configs to their respective packages
The streaming configs (Node/Browser) are different because they operate at
another dimension that exists in each package.
* Use escapeTextForBrowser to encode dynamic strings
We can now use local dependencies
* Destroy the stream with an error if the root throws
But not if the error happens inside a suspense boundary.
* Try rewriting the test to see if it works in other Node envs
* Copy some infra structure patterns from Flight
* Basic data structures
* Move structural nodes and instruction commands to host config
* Move instruction command to host config
In the DOM this is implemented as script tags. The first time it's emitted
it includes the function. Future calls invoke the same function.
The side of the complete boundary function in particular is unfortunately
large.
* Implement Fizz Noop host configs
This is implemented not as a serialized protocol but by-passing the
serialization when possible and instead it's like a live tree being
built.
* Implement React Native host config
This is not wired up. I just need something for the flow types since
Flight and Fizz are both handled by the isServerSupported flag.
Might as well add something though.
The principle of this format is the same structure as for HTML but a
simpler binary format.
Each entry is a tag followed by some data and terminated by null.
* Check in error codes
* Comment
Now that discrete updates are flushed synchronously in a microtask,
there's no need to track them in their on queue. They're already in
the queue we use for all sync work. So we can call that directly.
* DevTools flushes updated passive warning/error info after delay
Previously this information was not flushed until the next commit, but this provides a worse user experience if the next commit is really delayed. Instead, the backend now flushes only the warning/error counts after a delay. As a safety, if there are already any pending operations in the queue, we bail.
Co-authored-by: eps1lon <silbermann.sebastian@gmail.com>
* Improve DevTools Profiler commit-selector UX
1. Use natural log of durations (rather than linear) when calculating bar height. This reduces the impact of one (or few) outlier times on more common smaller durations. (Continue to use linear for bar color though.)
2. Decrease the minimum bar height to make the differences in height more noticeable.
3. Add a background hover highlight to increase contrast.
4. Add hover tooltip with commit duration and timestamp.
* Add failing regression test
Based on #20932
Co-Authored-By: Dan Abramov <dan.abramov@gmail.com>
* Reset `subtreeFlags` in `resetWorkInProgress`
Alternate fix to #20942
There was already a TODO to make this change, but at the time I left it,
I couldn't think of a way that it would actually cause a bug, and I was
hesistant to change something without fully understanding the
ramifications. This was during a time when we were hunting down a
different bug, so we were especially risk averse.
What I should have done in retrospect is put the change behind a flag
and tried rolling it out once the other bug had been flushed out.
OTOH, now we have a regression test, which wouldn't have otherwise, and
the bug it caused rarely fired in production.
Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
* Move context comparison to consumer
In the lazy context implementation, not all context changes are
propagated from the provider, so we can't rely on the propagation alone
to mark the consumer as dirty. The consumer needs to compare to the
previous value, like we do for state and context.
I added a `memoizedValue` field to the context dependency type. Then in
the consumer, we iterate over the current dependencies to see if
something changed. We only do this iteration after props and state has
already bailed out, so it's a relatively uncommon path, except at the
root of a changed subtree. Alternatively, we could move these
comparisons into `readContext`, but that's a much hotter path, so I
think this is an appropriate trade off.
* [Experiment] Lazily propagate context changes
When a context provider changes, we scan the tree for matching consumers
and mark them as dirty so that we know they have pending work. This
prevents us from bailing out if, say, an intermediate wrapper is
memoized.
Currently, we propagate these changes eagerly, at the provider.
However, in many cases, we would have ended up visiting the consumer
nodes anyway, as part of the normal render traversal, because there's no
memoized node in between that bails out.
We can save CPU cycles by propagating changes only when we hit a
memoized component — so, instead of propagating eagerly at the provider,
we propagate lazily if or when something bails out.
Most of our bailout logic is centralized in
`bailoutOnAlreadyFinishedWork`, so this ended up being not that
difficult to implement correctly.
There are some exceptions: Suspense and Offscreen. Those are special
because they sometimes defer the rendering of their children to a
completely separate render cycle. In those cases, we must take extra
care to propagate *all* the context changes, not just the first one.
I'm pleasantly surprised at how little I needed to change in this
initial implementation. I was worried I'd have to use the reconciler
fork, but I ended up being able to wrap all my changes in a regular
feature flag. So, we could run an experiment in parallel to our other
ones.
I do consider this a risky rollout overall because of the potential for
subtle semantic deviations. However, the model is simple enough that I
don't expect us to have trouble fixing regressions if or when they arise
during internal dogfooding.
---
This is largely based on [RFC#118](https://github.com/reactjs/rfcs/pull/118),
by @gnoff. I did deviate in some of the implementation details, though.
The main one is how I chose to track context changes. Instead of storing
a dirty flag on the stack, I added a `memoizedValue` field to the
context dependency object. Then, to check if something has changed, the
consumer compares the new context value to the old (memoized) one.
This is necessary because of Suspense and Offscreen — those components
defer work from one render into a later one. When the subtree continues
rendering, the stack from the previous render is no longer available.
But the memoized values on the dependencies list are. This requires a
bit more work when a consumer bails out, but nothing considerable, and
there are ways we could optimize it even further. Conceptually, this
model is really appealing, since it matches how our other features
"reactively" detect changes — `useMemo`, `useEffect`,
`getDerivedStateFromProps`, the built-in cache, and so on.
I also intentionally dropped support for
`unstable_calculateChangedBits`. We're planning to remove this API
anyway before the next major release, in favor of context selectors.
It's an unstable feature that we never advertised; I don't think it's
seen much adoption.
Co-Authored-By: Josh Story <jcs.gnoff@gmail.com>
* Propagate all contexts in single pass
Instead of propagating the tree once per changed context, we can check
all the contexts in a single propagation. This inverts the two loops so
that the faster loop (O(numberOfContexts)) is inside the more expensive
loop (O(numberOfFibers * avgContextDepsPerFiber)).
This adds a bit of overhead to the case where only a single context
changes because you have to unwrap the context from the array. I'm also
unsure if this will hurt cache locality.
Co-Authored-By: Josh Story <jcs.gnoff@gmail.com>
* Stop propagating at nearest dependency match
Because we now propagate all context providers in a single traversal, we
can defer context propagation to a subtree without losing information
about which context providers we're deferring — it's all of them.
Theoretically, this is a big optimization because it means we'll never
propagate to any tree that has work scheduled on it, nor will we ever
propagate the same tree twice.
There's an awkward case related to bailing out of the siblings of a
context consumer. Because those siblings don't bail out until after
they've already entered the begin phase, we have to do extra work to
make sure they don't unecessarily propagate context again. We could
avoid this by adding an earlier bailout for sibling nodes, something
we've discussed in the past. We should consider this during the next
refactor of the fiber tree structure.
Co-Authored-By: Josh Story <jcs.gnoff@gmail.com>
* Mark trees that need propagation in readContext
Instead of storing matched context consumers in a Set, we can mark
when a consumer receives an update inside `readContext`.
I hesistated to put anything in this function because it's such a hot
path, but so are bail outs. Fortunately, we only need to set this flag
once, the first time a context is read. So I think it's a reasonable
trade off.
In exchange, propagation is faster because we no longer need to
accumulate a Set of matched consumers, and fiber bailouts are faster
because we don't need to consult that Set. And the code is simpler.
Co-authored-by: Josh Story <jcs.gnoff@gmail.com>
In the lazy context implementation, not all context changes are
propagated from the provider, so we can't rely on the propagation alone
to mark the consumer as dirty. The consumer needs to compare to the
previous value, like we do for state and context.
I added a `memoizedValue` field to the context dependency type. Then in
the consumer, we iterate over the current dependencies to see if
something changed. We only do this iteration after props and state has
already bailed out, so it's a relatively uncommon path, except at the
root of a changed subtree. Alternatively, we could move these
comparisons into `readContext`, but that's a much hotter path, so I
think this is an appropriate trade off.
This commit also adds explicit index.stable and index.experimental forks to the react-is package so that we can avoid exporting references to SuspenseList in a stable release.
* The exported '<React.StrictMode>' tag remains the same and opts legacy subtrees into strict mode level one ('mode == StrictModeL1'). This mode enables DEV-only double rendering, double component lifecycles, string ref warnings, legacy context warnings, etc. The primary purpose of this mode is to help detected render phase side effects. No new behavior. Roots created with experimental 'createRoot' and 'createBlockingRoot' APIs will also (for now) continue to default to strict mode level 1.
In a subsequent commit I will add support for a 'level' attribute on the '<React.StrictMode>' tag (as well as a new option supported by ). This will be the way to opt into strict mode level 2 ('mode == StrictModeL2'). This mode will enable DEV-only double invoking of effects on initial mount. This will simulate future Offscreen API semantics for trees being mounted, then hidden, and then shown again. The primary purpose of this mode is to enable applications to prepare for compatibility with the new Offscreen API (more information to follow shortly).
For now, this commit changes no public facing behavior. The only mechanism for opting into strict mode level 2 is the pre-existing 'enableDoubleInvokingEffects' feature flag (only enabled within Facebook for now).
* Renamed strict mode constants
StrictModeL1 -> StrictLegacyMode and StrictModeL2 -> StrictEffectsMode
* Renamed tests
* Split strict effects mode into two flags
One flag ('enableStrictEffects') enables strict mode level 2. It is similar to 'debugRenderPhaseSideEffectsForStrictMode' which enables srtict mode level 1.
The second flag ('createRootStrictEffectsByDefault') controls the default strict mode level for 'createRoot' trees. For now, all 'createRoot' trees remain level 1 by default. We will experiment with level 2 within Facebook.
This is a prerequisite for adding a configurable option to 'createRoot' that enables choosing a different StrictMode level than the default.
* Add StrictMode 'unstable_level' prop and createRoot 'unstable_strictModeLevel' option
New StrictMode 'unstable_level' prop allows specifying which level of strict mode to use. If no level attribute is specified, StrictLegacyMode will be used to maintain backwards compatibility. Otherwise the following is true:
* Level 0 does nothing
* Level 1 selects StrictLegacyMode
* Level 2 selects StrictEffectsMode (which includes StrictLegacyMode)
Levels can be increased with nesting (0 -> 1 -> 2) but not decreased.
This commit also adds a new 'unstable_strictModeLevel' option to the createRoot and createBatchedRoot APIs. This option can be used to override default behavior to increase or decrease the StrictMode level of the root.
A subsequent commit will add additional DEV warnings:
* If a nested StrictMode tag attempts to explicitly decrease the level
* If a level attribute changes in an update
Use the pre-built scheduler (which includes a check for 'window' being defined in order to load the right scheduler implementation) rather than just directly importing a version of the scheduler that relies on window. Since the scheduling profiler's code runs partially in a web worker, it can't rely on window.
This commit changes scheduling profiler marks from a format like '--schedule-render-1' to '--schedule-render-1-Sync' (where 1 is the numeric value of the Sync lane). This will enable the profiler itself to show more meaningful labels for updates and render work.
The commit also refactors and adds additional tests for the scheduling profiler package.
It also updates the preprocessor to 'support' instant events. These are no-ops for us, but adding recognition of the event type will prevent profiles imported from e.g. Chrome Canary from throwing with an 'unrecognized event' error. (This will resolve issue #20767.)
With this change, if a node is a Fabric node, we route the setJSResponder call to FabricUIManager. Native counterpart is already landed. Tested internally as D26241364.
* Restore inspect-element bridge optimizations
When the new Suspense cache was integrated (so that startTransition could be used) I removed a couple of optimizations between the backend and frontend that reduced bridge traffic when e.g. dehydrated paths were inspected for elements that had not rendered since previously inspected. This commit re-adds those optimizations as well as an additional test with a bug fix that I noticed while reading the backend code.
There are two remaining TODO items as of this commit:
- Make inspected element edits and deletes also use transition API
- Don't over-eagerly refresh the cache in our ping-for-updates handler
I will addres both in subsequent commits.
* Poll for update only refreshes cache when there's an update
* Added inline comment