Similar to the rationale for `waitFor` (see #26285), we should always
await the result of an `act` call so that microtasks have a chance to
fire.
This only affects the internal `act` that we use in our repo, for now.
In the public `act` API, we don't yet require this; however, we
effectively will for any update that triggers suspense once `use` lands.
So we likely will start warning in an upcoming minor.
Similar to the rationale for `waitFor` (see #26285), we should always
await the result of an `act` call so that microtasks have a chance to
fire.
This only affects the internal `act` that we use in our repo, for now.
In the public `act` API, we don't yet require this; however, we
effectively will for any update that triggers suspense once `use` lands.
So we likely will start warning in an upcoming minor.
Similar to the rationale for `waitFor` (see
https://github.com/facebook/react/pull/26285), we should always await
the result of an `act` call so that microtasks have a chance to fire.
This only affects the internal `act` that we use in our repo, for now.
In the public `act` API, we don't yet require this; however, we
effectively will for any update that triggers suspense once `use` lands.
So we likely will start warning in an upcoming minor.
There's an old collection of test suites that test class component
behavior across ES6 (regular JavaScript classes), CoffeeScript classes,
and TypeScript classes. They work by running the same tests in all
environments and comparing the results.
Rather than use `act` or `waitFor` in these, I've changed them to use
`flushSync` instead so that they can flush synchronously. The reason is
that CoffeeScript doesn't have async/await, so we'd have to write those
tests differently than how they are written in the corresponding
modules. Since none of these tests cover any concurrent behavior, I
believe it's fine in this case to do everything synchronously; they
don't use any concurrent features, anyway, so effectively it's just
skipping a microtask.
## Summary
This is a small refactor of `ReactFabricHostComponent` to remove
unnecessary dependencies, unused methods and type definitions to
simplify a larger refactor of the class in a following PR
(https://github.com/facebook/react/pull/26321).
## How did you test this change?
Existing unit tests.
This PR is now based on #26256
The original matching function for `hydrateHoistable` some challenging
time complexity since we built up the list of matchable nodes for each
link of that type and then had to check to exclusion. This new
implementation aims to improve the complexity
For hoisted title tags we match the first title if it is valid (not in
SVG context and does not have `itemprop`, the two ways you opt out of
hoisting when rendering titles). This path is much faster than others
and we use it because valid Documents only have 1 title anyway and if we
did have a mismatch the rendered title still ends up as the
Document.title so there is no functional degradation for misses.
For hoisted link and meta tags we track all potentially hydratable
Elements of this type in a cache per Document. The cache is refreshed
once each commit if and only if there is a title or meta hoistable
hydrating. The caches are partitioned by a natural key for each type
(href for link and content for meta). Then secondary attributes are
checked to see if the potential match is matchable.
For link we check `rel`, `title`, and `crossorigin`. These should
provide enough entropy that we never have collisions except is contrived
cases and even then it should not affect functionality of the page. This
should also be tolerant of links being injected in arbitrary places in
the Document by 3rd party scripts and browser extensions
For meta we check `name`, `property`, `http-equiv`, and `charset`. These
should provide enough entropy that we don't have meaningful collisions.
It is concievable with og tags that there may be true duplciates `<meta
property="og:image:size:height" content="100" />` but even if we did
bind to the wrong instance meta tags are typically only read from SSR by
bots and rarely inserted by 3rd parties so an adverse functional outcome
is not expected.
Refactors the representation of ObjectExpression properties from a Map to an
`Array<ObjectProperty>` to prepare for the next diff which adds spread element
support.
## Do not hoist elements with `itemProp`
In HTML `itemprop` signifies a property of an `itemscope` with respect
to the Microdata spec
(https://html.spec.whatwg.org/multipage/microdata.html#microdata)
additionally `itemprop` is valid on any tag and can even make some tags
that are otherwise invalid in the `<body>` valid there (`<meta>` for
instance).
Originally I tried an approach where if you rendered something otherwise
hoistable inside an `itemscope` it would not hoist if it had an
`itemprop`. This meant that some components with `itemprop` could hoist
(if they were not scoped, which is generally invalid microdata
implementation). However the problem is things that do hoist, hoist into
the head and body and these tags can have an `itemscope`. This creates a
ton of ambiguity when trying to hydrate in these hoist scopes because we
can't know for certain whether a DOM node we find there was hoisted or
not even if it has an `itemprop` attribute. There are other scenarios
too that have abiguous semantics like rendering a hoistable with
`itemProp` outside of `<html itemScope={true>`. Is it fair to embed that
hoistable inside that itemScope even though it was defined outside?
To simplify the situation and disambiguate I dropped the `itemscope`
portion from the implementation and now any host component that could
normally be hoisted will not hoist if it has an `itemProp` prop.
In addition to the changes made for `itemProp` this PR also modifies
part of the hydration implementation to be more tolerant of tags
injected by 3rd parties. This was opportunistically done when we needed
to have context information like `inItemScope` but with the most recent
implementation that has been removed. I have however left the hydration
changes in place as it is a goal to make React handle hydrating the
entire Document even when we cannot control whether 3rd parties are
going to inject tags that React will not render but are also not
hoistables
-------
##### Original Description when we considered tracking itemScope
>One recent decision was to make elements using the `itemProp` prop not
hoistable if they were inside and itemScope. This better fits with
Microdata spec which allows for meta tags and other tag types usually
reserved for the `<head>` to be used in the `<body>` when using
itemScope.
>
>To implement this a number of small changes were necessary
>
>1. HostContext in prod needed to expand beyond just tracking the
element namespace for new element creation. It now tracks whether we are
in an itemScope. To keep this efficient it is modeled as a bitmask.
>2. To disambiguate what is and is not a potential instance in the DOM
for hoistables the hydration algo was updated to skip past non-matching
instances while attempting to claim the instance rather than ahead of
time (getNextHydratable).
>3. React will not consider an itemScope on `<html>`, `<head>`, or
`<body>` as a valid scope for the hoisting opt-out. This is important as
an invariant so we can make assumptions about certain tags in these
scopes. This should not be a functional breaking change because if any
of these tags have an `itemScope` then it can just be moved into the
first node inside the `<body>`
>
>Since we were already updating the logic for hydration to better
support `itemScope` opt-out I also changed the hydration behavior for
suspected 3rd party nodes in `<head>` and `<body>`. Now if you are
hydrating in either of those contexts hydration will skip past any
non-matching nodes until it finds a match. This allows 3rd party scripts
and extensions to inject nodes in either context that React does not
expect and still avoid a hydration mismatch.
>
>This new algorithm isn't perfect and it is possible for a mismatch to
occur. The most glaring case may be if a 3rd party script prepends a
`<div>` into `<body>` and you render a `<div>` in `<body>` in your app.
there is nothing to signal to React that this div was 3rd party so it
will claim is as the hydrated instance and hydration will almost
certainly fail immediately afterwards.
>
>The expectation is that this is rare and that if falling back to client
rendering is transparent to the user then there is not problem here. We
will continue to evaluate this and may change the hydration matching
algorithm further to match user and developer expectations
BuildHIR currently propagates UnsupportedNodes for collection types where the
element itself can fail (for example object expressions where the key may not be
valid). However, given that we currently abort compilation after the first
failing pass (and will probably do so for quite a while) I think we can simplify
and just always return the collection. Note that I already did this for
destructuring. I'm open to leaving the code as-is if you prefer, though.
This PR starts to clean up our handling of lvalues and rvalues by adding new
`eachInstructionLValues()` and `mapInstructionLValues()` helpers. Now,
`eachInstructionOperand()` and `mapInstructionOperands()` only visit true
rvalues, and the new passes must be used to visit lvalues. This allows us to
remove the special-casing for StoreLocal and Destructure in most of the passes.
Currently, any commit to React causes an internal sync since the Git
commit hash is part of the build. This creates a lot more sync commits
and noise than necessary, see:
https://github.com/facebook/react/commits/builds/facebook-www
This PR changes the version string to be a hash of the target build
files instead. This way we get a new version with any change that
actually impacts the generated files and still have a matching version
across the files.
Some build artifacts contain multiple version strings. It seems like an
oversight to me that this `.replace` call just replaces the one that
happens to be first.
Instead of replacing original function with compiled code, this adds an option
to append the code and switch between the two based on an `isForgetEnabled` test
condition that's imported from the specified gatingModule.
---
**This PR slightly changes the semantics of ReactiveScopeDependencies**.
Previously, reading a ReactiveScopeDependency is guaranteed to preserve the
`nullthrows` semantics of its own declarations (not that of its inner scopes).
This does not affect the overall correctness properties, since we already hoist
reading of conditional dependencies (and thus may throw earlier than the
original source).
E.g. we already do not preserve *where* the nullthrows occurs.
```javascript
function Component(props) {
// throws here, before print(x)
const c_0 = props.a.b !== $[0];
let x;
if (c_0) {
x = {};
print(x);
if (...) mutate1(x, props.a.b);
mutate2(x, props.a.b);
// ...
```
### Summary
This is an optimization, not a correctness property.
When propagating reactive dependencies of an inner scope up to its parent, we
want to *retain information about conditional dependencies* -- not the derived
unconditional dependencies. This helps us produce more granular dependencies in
the parent scope.
Current implementation:
```javascript
const innerScopeDeps = innerScope.depTree.deriveMinimalUnconditionalDeps();
for (const dep of innerScopeDeps) {
currentScope.depTree.addDep(dep);
}
```
New implementation:
```javascript
// union of a tree takes union of each node
currentScope.depTree = currentScope.depTree.union(innerScope.depTree);
```
### Example
In the below example:
- `scope @1` has a conditional dependency of `props.a.b`, but that reduces to
the unconditional dependency `props`
- `scope @0` itself has a unconditional dependency of `props.a.b`
- Currently, Forget joins the derived / reduced dependencies of inner scopes,
which adds `props` as unconditional dependency of `scope @0`
- With this change, Forget joins the property trees and retains info about
conditional deps, which adds `props.a.b` as a conditional dep of `scope @0`.
```javascript
// scope @0 (deps=[???] decls=[x, y])
let y = {};
// scope @1 (deps=[props] decls=[x])
let x = {};
if (foo) mutate1(x, props.a.b);
mutate2(y, props.a.b);
```
### Followup
We currently keep track of properties unconditionally accessed per
ReactiveBlock. Eventually we want to keep track of properties unconditionally
accessed across blocks (as according to control flow).
Consider the following code, in which sibling scopes 0 and 1 are sequentially
executed. In this case, we can safely add props.a.b as a dependency of scope 1.
```javascript
// scope@0 (deps=[props.a.b], decls=[x])
let x = { a: foo(props.a.b) };
// scope@1 (deps=[???], decls=[y])
let y = {};
if (...) {
mutate(y, props.a.b);
}
```
---
Implementation details summarized in comments.
Overall, we want to calculate a `ReactiveDependencyTree` for every conditional
block. If we know that conditional blocks are exhaustive (e.g. all CFG paths
calculates a tree), we can take `intersection(depsFromEachBlock)` and add this
to the parent Reactive + conditional scope `parentDeps = union(parentDeps,
intersection(...))`.
We use trees instead of individual deps here because we can still derive
unconditional accesses.
e.g.
```
let x = {};
// props.a is an unconditional access here
if (foo(other)) {
x.a = props.a.b;
} else {
x.b = props.a.c;
}
```
---
Small refactor of reactive dependency logic, no behavioral change.
- Moves ReactiveDependencyTree logic into `DeriveMinimalDependencies`.
- this moves hides most helper functions + types 🥳
- made `ReactiveDependencyTree` a class
- Changes `#dependencies` type: `Set<ReactiveScopeDep>` ->
`ReactiveDependencyTree`
- instead of collecting all dependencies into a tree in the end, we now eagerly
join dependencies into the tree on `visitDep`
- this is needed for the next PR in the stack, which relies on incremental
merging
When the work loop is suspended, we shouldn't schedule a new render task
until the promise has resolved. When I originally implemented this, I
wasn't sure where to put this logic — `ensureRootIsScheduled` is the
more natural place for it, but that's also a really hot path, so I chose
to do it elsewhere, and left a TODO to reconsider later.
Now it's later. I'm working on a refactor to move the
`ensureRootIsScheduled` call to always happen in a microtask, so that if
there are multiple updates/pings in a single event, they get batched
into a single operation. Which means I can put the logic in that
function where it belongs.
(This only affects our own internal repo; it's not a public API.)
I think most of us agree this is a less confusing name. It's possible
someone will confuse it with `console.log`. If that becomes a problem we
can warn in dev or something.
## Summary
The following methods have exactly the same implementation on Fabric and
the legacy renderer:
* `findHostInstance_DEPRECATED`
* `findNodeHandle`
* `dispatchCommand`
* `sendAccessibilityEvent`
This just extracts those functions to a common module so they're easier
to change (no need to sync changes in 2 files).
## How did you test this change?
Existing tests (this is a refactor).
Based on a bug report from @bvaughn.
`act` should not consult `shouldYield` when it's performing work,
because in a unit testing environment, I/O (such as `setTimeout`) is
likely mocked. So the result of `shouldYield` can't be trusted.
In the regression test, I simulate the bug by mocking `shouldYield` to
always return `true`. This causes an infinite loop in `act`, because it
will keep trying to render and React will keep yielding.
We support any super type of anything that we can serialize. Meaning
that as long as the Type that's passed through is less precise, it means
that we can encoded it as any subtype and therefore the incoming type
doesn't have to be the subtype in that case. Basically, as long as
you're only passing through an `Iterable<T>` in TypeScript, then you can
pass any `Iterable<T>` and we'll treat it as an array.
For example we support Promises *and* Thenables but both are encoded as
Promises.
We support Arrays and since Arrays are also Iterables, we can support
Iterables.
For @wongmjane
Previously when a called server reference function was rejected, the
emitted error chunk was not flushed, and the request was not properly
closed.
Co-authored-by: Sebastian Markbage <sebastian@calyptus.eu>
We always look up these references in a map so it doesn't matter what
their value is. It could be a hash for example.
The loaders now encode a single $$id instead of filepath + name.
This changes the react-client-manifest to have a single level. The value
inside the map is still split into module id + export name because
that's what gets looked up in webpack.
The react-ssr-manifest is still two levels because that's a reverse
lookup.
This converts some of our test suite to use the `waitFor` test pattern,
instead of the `expect(Scheduler).toFlushAndYield` pattern. Most of
these changes are automated with jscodeshift, with some slight manual
cleanup in certain cases.
See #26285 for full context.
This converts some of our test suite to use the `waitFor` test pattern,
instead of the `expect(Scheduler).toFlushAndYield` pattern. Most of
these changes are automated with jscodeshift, with some slight manual
cleanup in certain cases.
See #26285 for full context.
This converts some of our test suite to use the `waitFor` test pattern,
instead of the `expect(Scheduler).toFlushAndYield` pattern. Most of
these changes are automated with jscodeshift, with some slight manual
cleanup in certain cases.
See #26285 for full context.
This converts some of our test suite to use the `waitFor` test pattern,
instead of the `expect(Scheduler).toFlushAndYield` pattern. Most of
these changes are automated with jscodeshift, with some slight manual
cleanup in certain cases.
See #26285 for full context.
I'm in the process of codemodding our test suite to the waitFor pattern.
See #26285 for full context.
This module required a lot of manual changes so I'm doing it as its own
PR. The reason is that most of the tests involved simulating an async
import by wrapping them in `Promise.resolve()`, which means they would
immediately resolve the next time the microtask queue was flushed. I
rewrote the tests to resolve the simulated import explicitly.
While converting these tests, I also realized that the `waitFor` helpers
weren't properly waiting for the entire microtask queue to recursively
finish — if a microtask schedules another microtask, the subsequent one
wouldn't fire until after `waitFor` had resolved. To fix this, I used
the same strategy as `act` — wait for a real task to finish before
proceeding, such as a message event.
## Summary
Fix bug in how the Fizz external runtime processes existing template
elements.
Bug:
- getElementsByTagName returns a HTMLCollection, which is live.
- while iterating over an HTMLCollection, we call handleNode which
removes nodes
Fix:
- Call Array.from to copy children of `document.body` before processing.
- We could use `querySelectorAll` instead, but that is likely slower due
to reading more nodes.
## How did you test this change?
Did ad-hoc testing on Facebook home page by commenting out the mutation
observer and adding the following.
```javascript
window.addEventListener('DOMContentLoaded', function () {
handleExistingNodes(document.body);
});
```
This converts some of our test suite to use the `waitFor` test pattern,
instead of the `expect(Scheduler).toFlushAndYield` pattern. Most of
these changes are automated with jscodeshift, with some slight manual
cleanup in certain cases.
See #26285 for full context.