Remove Reconciler fork (1/2) (#25774)

We've heard from multiple contributors that the Reconciler forking
mechanism was confusing and/or annoying to deal with. Since it's
currently unused and there's no immediate plans to start using it again,
this removes the forking.

Fully removing the fork is split into 2 steps to preserve file history:

**This PR**
- remove `enableNewReconciler` feature flag.
- remove `unstable_isNewReconciler` export
- remove eslint rules for cross fork imports
- remove `*.new.js` files and update imports
- merge non-suffixed files into `*.old` files where both exist
(sometimes types were defined there)

**#25775**
- rename `*.old` files
This commit is contained in:
Jan Kassens 2022-12-01 23:06:25 -05:00 committed by GitHub
parent 030dae2f4c
commit 420f0b7fa1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
110 changed files with 71 additions and 33065 deletions

View File

@ -462,35 +462,6 @@ jobs:
cp ./scripts/release/ci-npmrc ~/.npmrc
scripts/release/publish.js --ci --tags << parameters.dist_tag >>
# We don't always keep the reconciler forks in sync (otherwise it we wouldn't
# have forked it) but during periods when they are meant to be in sync, we
# use this job to confirm there are no differences.
sync_reconciler_forks:
docker: *docker
environment: *environment
steps:
- checkout
- *restore_node_modules
- run:
name: Fetch revisions that contain an intentional fork
# This will fetch each revision listed in the `forked-revisions` file,
# which may be necessary if it's not part of main. For example, it
# may have been part of a PR branch that was squashed on merge.
command: |
cut -d " " -f 1 scripts/merge-fork/forked-revisions | xargs -r git fetch origin
- run:
name: Revert forked revisions
# This will revert the changes without committing. At the end, it's
# expected that both forks will be identical.
command: |
cut -d " " -f 1 scripts/merge-fork/forked-revisions | xargs -r git revert --no-commit
- run:
name: Confirm reconciler forks are the same
command: |
yarn replace-fork
git diff --quiet || (echo "Reconciler forks are not the same! Run yarn replace-fork. Or, if this was intentional, add the commit SHA to scripts/merge-fork/forked-revisions." && false)
workflows:
version: 2
@ -506,11 +477,6 @@ workflows:
- yarn_flow:
requires:
- setup
# NOTE: This job is only enabled when we want the forks to be in sync.
# When the forks intentionally diverge, comment out the job to disable it.
- sync_reconciler_forks:
requires:
- setup
- check_generated_fizz_runtime:
requires:
- setup

View File

@ -112,14 +112,6 @@ module.exports = {
'react-internal/no-to-warn-dev-within-to-throw': ERROR,
'react-internal/warning-args': ERROR,
'react-internal/no-production-logging': ERROR,
'react-internal/no-cross-fork-imports': ERROR,
'react-internal/no-cross-fork-types': [
ERROR,
{
old: [],
new: [],
},
],
},
overrides: [

View File

@ -140,8 +140,6 @@
"prettier": "node ./scripts/prettier/index.js write-changed",
"prettier-all": "node ./scripts/prettier/index.js write",
"version-check": "node ./scripts/tasks/version-check.js",
"merge-fork": "node ./scripts/merge-fork/merge-fork.js",
"replace-fork": "node ./scripts/merge-fork/replace-fork.js",
"publish-prereleases": "node ./scripts/release/publish-using-ci-workflow.js",
"download-build": "node ./scripts/release/download-experimental-build.js",
"download-build-for-head": "node ./scripts/release/download-experimental-build.js --commit=$(git rev-parse HEAD)",

View File

@ -12,7 +12,7 @@ import {
createContainer,
updateContainer,
injectIntoDevTools,
} from 'react-reconciler/src/ReactFiberReconciler';
} from 'react-reconciler/src/ReactFiberReconciler.old';
import Transform from 'art/core/transform';
import Mode from 'art/modes/current';
import FastNoSideEffects from 'art/modes/fast-noSideEffects';

View File

@ -10,7 +10,7 @@ import Mode from 'art/modes/current';
import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
const pooledTransform = new Transform();

View File

@ -29,7 +29,7 @@ import {
markNodeAsResource,
} from './ReactDOMComponentTree';
import {HTML_NAMESPACE, SVG_NAMESPACE} from '../shared/DOMNamespaces';
import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext';
import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext.old';
// The resource types we support. currently they match the form for the as argument.
// In the future this may need to change, especially when modules / scripts are supported

View File

@ -7,7 +7,7 @@
* @flow
*/
import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities';
import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
import type {DOMEventName} from '../events/DOMEventNames';
import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {
@ -81,7 +81,7 @@ import {
} from 'react-reconciler/src/ReactWorkTags';
import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
// TODO: Remove this deep import when we delete the legacy root API
import {ConcurrentMode, NoMode} from 'react-reconciler/src/ReactTypeOfMode';

View File

@ -7,7 +7,7 @@
* @flow
*/
import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities';
import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
import type {AnyNativeEvent} from '../events/PluginModuleType';
import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {Container, SuspenseInstance} from '../client/ReactDOMHostConfig';
@ -52,7 +52,7 @@ import {
IdleEventPriority,
getCurrentUpdatePriority,
setCurrentUpdatePriority,
} from 'react-reconciler/src/ReactEventPriorities';
} from 'react-reconciler/src/ReactEventPriorities.old';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {isRootDehydrated} from 'react-reconciler/src/ReactFiberShellHydration';

View File

@ -12,7 +12,7 @@ import type {Container, SuspenseInstance} from '../client/ReactDOMHostConfig';
import type {DOMEventName} from '../events/DOMEventNames';
import type {EventSystemFlags} from './EventSystemFlags';
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities';
import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
import {enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay} from 'shared/ReactFeatureFlags';
import {
@ -35,7 +35,7 @@ import {
getClosestInstanceFromNode,
} from '../client/ReactDOMComponentTree';
import {HostRoot, SuspenseComponent} from 'react-reconciler/src/ReactWorkTags';
import {isHigherEventPriority} from 'react-reconciler/src/ReactEventPriorities';
import {isHigherEventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
import {isRootDehydrated} from 'react-reconciler/src/ReactFiberShellHydration';
let _attemptSynchronousHydration: (fiber: Object) => void;

View File

@ -30,7 +30,6 @@ export {
unstable_batchedUpdates,
unstable_createEventHandle,
unstable_flushControlled,
unstable_isNewReconciler,
unstable_renderSubtreeIntoContainer,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
preinit,

View File

@ -22,7 +22,6 @@ export {
unstable_batchedUpdates,
unstable_createEventHandle,
unstable_flushControlled,
unstable_isNewReconciler,
unstable_renderSubtreeIntoContainer,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
preinit,

View File

@ -16,7 +16,6 @@ export {
unstable_batchedUpdates,
unstable_createEventHandle,
unstable_flushControlled,
unstable_isNewReconciler,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
preinit,
preload,

View File

@ -36,7 +36,6 @@ describe('react-dom-server-rendering-stub', () => {
expect(ReactDOM.unstable_batchedUpdates).toBe(undefined);
expect(ReactDOM.unstable_createEventHandle).toBe(undefined);
expect(ReactDOM.unstable_flushControlled).toBe(undefined);
expect(ReactDOM.unstable_isNewReconciler).toBe(undefined);
expect(ReactDOM.unstable_renderSubtreeIntoContainer).toBe(undefined);
expect(ReactDOM.unstable_runWithPriority).toBe(undefined);
});

View File

@ -43,15 +43,14 @@ import {
attemptDiscreteHydration,
attemptContinuousHydration,
attemptHydrationAtCurrentPriority,
} from 'react-reconciler/src/ReactFiberReconciler';
} from 'react-reconciler/src/ReactFiberReconciler.old';
import {
runWithPriority,
getCurrentUpdatePriority,
} from 'react-reconciler/src/ReactEventPriorities';
} from 'react-reconciler/src/ReactEventPriorities.old';
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
import {canUseDOM} from 'shared/ExecutionEnvironment';
import ReactVersion from 'shared/ReactVersion';
import {enableNewReconciler} from 'shared/ReactFeatureFlags';
import {
getClosestInstanceFromNode,
@ -256,5 +255,3 @@ if (__DEV__) {
}
}
}
export const unstable_isNewReconciler = enableNewReconciler;

View File

@ -37,7 +37,7 @@ import {
getPublicRootInstance,
findHostInstance,
findHostInstanceWithWarning,
} from 'react-reconciler/src/ReactFiberReconciler';
} from 'react-reconciler/src/ReactFiberReconciler.old';
import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import ReactSharedInternals from 'shared/ReactSharedInternals';

View File

@ -76,7 +76,7 @@ import {
registerMutableSourceForHydration,
flushSync,
isAlreadyRendering,
} from 'react-reconciler/src/ReactFiberReconciler';
} from 'react-reconciler/src/ReactFiberReconciler.old';
import {ConcurrentRoot} from 'react-reconciler/src/ReactRootTags';
/* global reportError */

View File

@ -19,4 +19,4 @@ export {
findBoundingRects,
focusWithin,
observeVisibleRects,
} from 'react-reconciler/src/ReactFiberReconciler';
} from 'react-reconciler/src/ReactFiberReconciler.old';

View File

@ -19,4 +19,4 @@ export {
findBoundingRects,
focusWithin,
observeVisibleRects,
} from 'react-reconciler/src/ReactFiberReconciler';
} from 'react-reconciler/src/ReactFiberReconciler.old';

View File

@ -19,4 +19,4 @@ export {
findBoundingRects,
focusWithin,
observeVisibleRects,
} from 'react-reconciler/src/ReactFiberReconciler';
} from 'react-reconciler/src/ReactFiberReconciler.old';

View File

@ -19,4 +19,4 @@ export {
findBoundingRects,
focusWithin,
observeVisibleRects,
} from 'react-reconciler/src/ReactFiberReconciler';
} from 'react-reconciler/src/ReactFiberReconciler.old';

View File

@ -22,7 +22,7 @@ import {
updateContainer,
injectIntoDevTools,
getPublicRootInstance,
} from 'react-reconciler/src/ReactFiberReconciler';
} from 'react-reconciler/src/ReactFiberReconciler.old';
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
import {setBatchingImplementation} from './legacy-events/ReactGenericBatching';

View File

@ -26,7 +26,7 @@ import {dispatchEvent} from './ReactFabricEventEmitter';
import {
DefaultEventPriority,
DiscreteEventPriority,
} from 'react-reconciler/src/ReactEventPriorities';
} from 'react-reconciler/src/ReactEventPriorities.old';
// Modules provided by RN:
import {

View File

@ -24,7 +24,7 @@ import {
} from './ReactNativeComponentTree';
import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;

View File

@ -22,7 +22,7 @@ import {
updateContainer,
injectIntoDevTools,
getPublicRootInstance,
} from 'react-reconciler/src/ReactFiberReconciler';
} from 'react-reconciler/src/ReactFiberReconciler.old';
// TODO: direct imports like some-package/src/* are bad. Fix me.
import {getStackByFiberInDevAndProd} from 'react-reconciler/src/ReactFiberComponentStack';
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';

View File

@ -18,7 +18,7 @@ import type {
Fiber,
TransitionTracingCallbacks,
} from 'react-reconciler/src/ReactInternalTypes';
import type {UpdateQueue} from 'react-reconciler/src/ReactFiberClassUpdateQueue.new';
import type {UpdateQueue} from 'react-reconciler/src/ReactFiberClassUpdateQueue';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {RootTag} from 'react-reconciler/src/ReactRootTags';

View File

@ -7,4 +7,4 @@
* @flow
*/
export * from './src/ReactFiberReconciler';
export * from './src/ReactFiberReconciler.old';

File diff suppressed because it is too large Load Diff

View File

@ -1,74 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import {enableNewReconciler} from 'shared/ReactFeatureFlags';
import {
DiscreteEventPriority as DiscreteEventPriority_old,
ContinuousEventPriority as ContinuousEventPriority_old,
DefaultEventPriority as DefaultEventPriority_old,
IdleEventPriority as IdleEventPriority_old,
getCurrentUpdatePriority as getCurrentUpdatePriority_old,
setCurrentUpdatePriority as setCurrentUpdatePriority_old,
runWithPriority as runWithPriority_old,
isHigherEventPriority as isHigherEventPriority_old,
} from './ReactEventPriorities.old';
import {
DiscreteEventPriority as DiscreteEventPriority_new,
ContinuousEventPriority as ContinuousEventPriority_new,
DefaultEventPriority as DefaultEventPriority_new,
IdleEventPriority as IdleEventPriority_new,
getCurrentUpdatePriority as getCurrentUpdatePriority_new,
setCurrentUpdatePriority as setCurrentUpdatePriority_new,
runWithPriority as runWithPriority_new,
isHigherEventPriority as isHigherEventPriority_new,
} from './ReactEventPriorities.new';
export opaque type EventPriority = number;
export const DiscreteEventPriority: EventPriority = enableNewReconciler
? (DiscreteEventPriority_new: any)
: (DiscreteEventPriority_old: any);
export const ContinuousEventPriority: EventPriority = enableNewReconciler
? (ContinuousEventPriority_new: any)
: (ContinuousEventPriority_old: any);
export const DefaultEventPriority: EventPriority = enableNewReconciler
? (DefaultEventPriority_new: any)
: (DefaultEventPriority_old: any);
export const IdleEventPriority: EventPriority = enableNewReconciler
? (IdleEventPriority_new: any)
: (IdleEventPriority_old: any);
export function runWithPriority<T>(priority: EventPriority, fn: () => T): T {
return enableNewReconciler
? runWithPriority_new((priority: any), fn)
: runWithPriority_old((priority: any), fn);
}
export function getCurrentUpdatePriority(): EventPriority {
return enableNewReconciler
? (getCurrentUpdatePriority_new(): any)
: (getCurrentUpdatePriority_old(): any);
}
export function setCurrentUpdatePriority(priority: EventPriority): void {
return enableNewReconciler
? setCurrentUpdatePriority_new((priority: any))
: setCurrentUpdatePriority_old((priority: any));
}
export function isHigherEventPriority(
a: EventPriority,
b: EventPriority,
): boolean {
return enableNewReconciler
? isHigherEventPriority_new((a: any), (b: any))
: isHigherEventPriority_old((a: any), (b: any));
}

View File

@ -1,82 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Lane, Lanes} from './ReactFiberLane.new';
import {
NoLane,
SyncLane,
InputContinuousLane,
DefaultLane,
IdleLane,
getHighestPriorityLane,
includesNonIdleWork,
} from './ReactFiberLane.new';
export opaque type EventPriority = Lane;
export const DiscreteEventPriority: EventPriority = SyncLane;
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
export const DefaultEventPriority: EventPriority = DefaultLane;
export const IdleEventPriority: EventPriority = IdleLane;
let currentUpdatePriority: EventPriority = NoLane;
export function getCurrentUpdatePriority(): EventPriority {
return currentUpdatePriority;
}
export function setCurrentUpdatePriority(newPriority: EventPriority) {
currentUpdatePriority = newPriority;
}
export function runWithPriority<T>(priority: EventPriority, fn: () => T): T {
const previousPriority = currentUpdatePriority;
try {
currentUpdatePriority = priority;
return fn();
} finally {
currentUpdatePriority = previousPriority;
}
}
export function higherEventPriority(
a: EventPriority,
b: EventPriority,
): EventPriority {
return a !== 0 && a < b ? a : b;
}
export function lowerEventPriority(
a: EventPriority,
b: EventPriority,
): EventPriority {
return a === 0 || a > b ? a : b;
}
export function isHigherEventPriority(
a: EventPriority,
b: EventPriority,
): boolean {
return a !== 0 && a < b;
}
export function lanesToEventPriority(lanes: Lanes): EventPriority {
const lane = getHighestPriorityLane(lanes);
if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
return DiscreteEventPriority;
}
if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
return ContinuousEventPriority;
}
if (includesNonIdleWork(lane)) {
return DefaultEventPriority;
}
return IdleEventPriority;
}

View File

@ -1,910 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ReactElement} from 'shared/ReactElementType';
import type {ReactFragment, ReactPortal, ReactScope} from 'shared/ReactTypes';
import type {Fiber} from './ReactInternalTypes';
import type {RootTag} from './ReactRootTags';
import type {WorkTag} from './ReactWorkTags';
import type {TypeOfMode} from './ReactTypeOfMode';
import type {Lanes} from './ReactFiberLane.new';
import type {SuspenseInstance} from './ReactFiberHostConfig';
import type {
OffscreenProps,
OffscreenInstance,
} from './ReactFiberOffscreenComponent';
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
import {
supportsResources,
supportsSingletons,
isHostResourceType,
isHostSingletonType,
} from './ReactFiberHostConfig';
import {
createRootStrictEffectsByDefault,
enableCache,
enableProfilerTimer,
enableScopeAPI,
enableLegacyHidden,
enableSyncDefaultUpdates,
allowConcurrentByDefault,
enableTransitionTracing,
enableDebugTracing,
enableFloat,
enableHostSingletons,
} from 'shared/ReactFeatureFlags';
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
import {ConcurrentRoot} from './ReactRootTags';
import {
IndeterminateComponent,
ClassComponent,
HostRoot,
HostComponent,
HostText,
HostPortal,
HostResource,
HostSingleton,
ForwardRef,
Fragment,
Mode,
ContextProvider,
ContextConsumer,
Profiler,
SuspenseComponent,
SuspenseListComponent,
DehydratedFragment,
FunctionComponent,
MemoComponent,
SimpleMemoComponent,
LazyComponent,
ScopeComponent,
OffscreenComponent,
LegacyHiddenComponent,
CacheComponent,
TracingMarkerComponent,
} from './ReactWorkTags';
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
import {
resolveClassForHotReloading,
resolveFunctionForHotReloading,
resolveForwardRefForHotReloading,
} from './ReactFiberHotReloading.new';
import {NoLanes} from './ReactFiberLane.new';
import {
NoMode,
ConcurrentMode,
DebugTracingMode,
ProfileMode,
StrictLegacyMode,
StrictEffectsMode,
ConcurrentUpdatesByDefaultMode,
} from './ReactTypeOfMode';
import {
REACT_FORWARD_REF_TYPE,
REACT_FRAGMENT_TYPE,
REACT_DEBUG_TRACING_MODE_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_PROFILER_TYPE,
REACT_PROVIDER_TYPE,
REACT_CONTEXT_TYPE,
REACT_SUSPENSE_TYPE,
REACT_SUSPENSE_LIST_TYPE,
REACT_MEMO_TYPE,
REACT_LAZY_TYPE,
REACT_SCOPE_TYPE,
REACT_OFFSCREEN_TYPE,
REACT_LEGACY_HIDDEN_TYPE,
REACT_CACHE_TYPE,
REACT_TRACING_MARKER_TYPE,
} from 'shared/ReactSymbols';
import {TransitionTracingMarker} from './ReactFiberTracingMarkerComponent.new';
import {detachOffscreenInstance} from './ReactFiberCommitWork.new';
import {getHostContext} from './ReactFiberHostContext.new';
export type {Fiber};
let hasBadMapPolyfill;
if (__DEV__) {
hasBadMapPolyfill = false;
try {
const nonExtensibleObject = Object.preventExtensions({});
/* eslint-disable no-new */
new Map([[nonExtensibleObject, null]]);
new Set([nonExtensibleObject]);
/* eslint-enable no-new */
} catch (e) {
// TODO: Consider warning about bad polyfills
hasBadMapPolyfill = true;
}
}
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;
// Fiber
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
this.refCleanup = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
// Effects
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
this.lanes = NoLanes;
this.childLanes = NoLanes;
this.alternate = null;
if (enableProfilerTimer) {
// Note: The following is done to avoid a v8 performance cliff.
//
// Initializing the fields below to smis and later updating them with
// double values will cause Fibers to end up having separate shapes.
// This behavior/bug has something to do with Object.preventExtension().
// Fortunately this only impacts DEV builds.
// Unfortunately it makes React unusably slow for some applications.
// To work around this, initialize the fields below with doubles.
//
// Learn more about this here:
// https://github.com/facebook/react/issues/14365
// https://bugs.chromium.org/p/v8/issues/detail?id=8538
this.actualDuration = Number.NaN;
this.actualStartTime = Number.NaN;
this.selfBaseDuration = Number.NaN;
this.treeBaseDuration = Number.NaN;
// It's okay to replace the initial doubles with smis after initialization.
// This won't trigger the performance cliff mentioned above,
// and it simplifies other profiler code (including DevTools).
this.actualDuration = 0;
this.actualStartTime = -1;
this.selfBaseDuration = 0;
this.treeBaseDuration = 0;
}
if (__DEV__) {
// This isn't directly used but is handy for debugging internals:
this._debugSource = null;
this._debugOwner = null;
this._debugNeedsRemount = false;
this._debugHookTypes = null;
if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
Object.preventExtensions(this);
}
}
}
// This is a constructor function, rather than a POJO constructor, still
// please ensure we do the following:
// 1) Nobody should add any instance methods on this. Instance methods can be
// more difficult to predict when they get optimized and they are almost
// never inlined properly in static compilers.
// 2) Nobody should rely on `instanceof Fiber` for type testing. We should
// always know when it is a fiber.
// 3) We might want to experiment with using numeric keys since they are easier
// to optimize in a non-JIT environment.
// 4) We can easily go from a constructor to a createFiber object literal if that
// is faster.
// 5) It should be easy to port this to a C struct and keep a C implementation
// compatible.
const createFiber = function(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
): Fiber {
// $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
return new FiberNode(tag, pendingProps, key, mode);
};
function shouldConstruct(Component: Function) {
const prototype = Component.prototype;
return !!(prototype && prototype.isReactComponent);
}
export function isSimpleFunctionComponent(type: any): boolean {
return (
typeof type === 'function' &&
!shouldConstruct(type) &&
type.defaultProps === undefined
);
}
export function resolveLazyComponentTag(Component: Function): WorkTag {
if (typeof Component === 'function') {
return shouldConstruct(Component) ? ClassComponent : FunctionComponent;
} else if (Component !== undefined && Component !== null) {
const $$typeof = Component.$$typeof;
if ($$typeof === REACT_FORWARD_REF_TYPE) {
return ForwardRef;
}
if ($$typeof === REACT_MEMO_TYPE) {
return MemoComponent;
}
}
return IndeterminateComponent;
}
// This is used to create an alternate fiber to do work on.
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
let workInProgress = current.alternate;
if (workInProgress === null) {
// We use a double buffering pooling technique because we know that we'll
// only ever need at most two versions of a tree. We pool the "other" unused
// node that we're free to reuse. This is lazily created to avoid allocating
// extra objects for things that are never updated. It also allow us to
// reclaim the extra memory if needed.
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
if (__DEV__) {
// DEV-only fields
workInProgress._debugSource = current._debugSource;
workInProgress._debugOwner = current._debugOwner;
workInProgress._debugHookTypes = current._debugHookTypes;
}
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
// Needed because Blocks store data on type.
workInProgress.type = current.type;
// We already have an alternate.
// Reset the effect tag.
workInProgress.flags = NoFlags;
// The effects are no longer valid.
workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null;
if (enableProfilerTimer) {
// We intentionally reset, rather than copy, actualDuration & actualStartTime.
// This prevents time from endlessly accumulating in new commits.
// This has the downside of resetting values for different priority renders,
// But works for yielding (the common case) and should support resuming.
workInProgress.actualDuration = 0;
workInProgress.actualStartTime = -1;
}
}
// Reset all effects except static ones.
// Static effects are not specific to a render.
workInProgress.flags = current.flags & StaticMask;
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
// Clone the dependencies object. This is mutated during the render phase, so
// it cannot be shared with the current fiber.
const currentDependencies = current.dependencies;
workInProgress.dependencies =
currentDependencies === null
? null
: {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext,
};
// These will be overridden during the parent's reconciliation
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
workInProgress.refCleanup = current.refCleanup;
if (enableProfilerTimer) {
workInProgress.selfBaseDuration = current.selfBaseDuration;
workInProgress.treeBaseDuration = current.treeBaseDuration;
}
if (__DEV__) {
workInProgress._debugNeedsRemount = current._debugNeedsRemount;
switch (workInProgress.tag) {
case IndeterminateComponent:
case FunctionComponent:
case SimpleMemoComponent:
workInProgress.type = resolveFunctionForHotReloading(current.type);
break;
case ClassComponent:
workInProgress.type = resolveClassForHotReloading(current.type);
break;
case ForwardRef:
workInProgress.type = resolveForwardRefForHotReloading(current.type);
break;
default:
break;
}
}
return workInProgress;
}
// Used to reuse a Fiber for a second pass.
export function resetWorkInProgress(
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber {
// This resets the Fiber to what createFiber or createWorkInProgress would
// have set the values to before during the first pass. Ideally this wouldn't
// be necessary but unfortunately many code paths reads from the workInProgress
// when they should be reading from current and writing to workInProgress.
// We assume pendingProps, index, key, ref, return are still untouched to
// avoid doing another reconciliation.
// Reset the effect flags but keep any Placement tags, since that's something
// that child fiber is setting, not the reconciliation.
workInProgress.flags &= StaticMask | Placement;
// The effects are no longer valid.
const current = workInProgress.alternate;
if (current === null) {
// Reset to createFiber's initial values.
workInProgress.childLanes = NoLanes;
workInProgress.lanes = renderLanes;
workInProgress.child = null;
workInProgress.subtreeFlags = NoFlags;
workInProgress.memoizedProps = null;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.dependencies = null;
workInProgress.stateNode = null;
if (enableProfilerTimer) {
// Note: We don't reset the actualTime counts. It's useful to accumulate
// actual time across multiple render passes.
workInProgress.selfBaseDuration = 0;
workInProgress.treeBaseDuration = 0;
}
} else {
// Reset to the cloned values that createWorkInProgress would've.
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
// Needed because Blocks store data on type.
workInProgress.type = current.type;
// Clone the dependencies object. This is mutated during the render phase, so
// it cannot be shared with the current fiber.
const currentDependencies = current.dependencies;
workInProgress.dependencies =
currentDependencies === null
? null
: {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext,
};
if (enableProfilerTimer) {
// Note: We don't reset the actualTime counts. It's useful to accumulate
// actual time across multiple render passes.
workInProgress.selfBaseDuration = current.selfBaseDuration;
workInProgress.treeBaseDuration = current.treeBaseDuration;
}
}
return workInProgress;
}
export function createHostRootFiber(
tag: RootTag,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
): Fiber {
let mode;
if (tag === ConcurrentRoot) {
mode = ConcurrentMode;
if (isStrictMode === true || createRootStrictEffectsByDefault) {
mode |= StrictLegacyMode | StrictEffectsMode;
}
if (
// We only use this flag for our repo tests to check both behaviors.
// TODO: Flip this flag and rename it something like "forceConcurrentByDefaultForTesting"
!enableSyncDefaultUpdates ||
// Only for internal experiments.
(allowConcurrentByDefault && concurrentUpdatesByDefaultOverride)
) {
mode |= ConcurrentUpdatesByDefaultMode;
}
} else {
mode = NoMode;
}
if (enableProfilerTimer && isDevToolsPresent) {
// Always collect profile timings when DevTools are present.
// This enables DevTools to start capturing timing at any point
// Without some nodes in the tree having empty base times.
mode |= ProfileMode;
}
return createFiber(HostRoot, null, null, mode);
}
export function createFiberFromTypeAndProps(
type: any, // React$ElementType
key: null | string,
pendingProps: any,
owner: null | Fiber,
mode: TypeOfMode,
lanes: Lanes,
): Fiber {
let fiberTag = IndeterminateComponent;
// The resolved type is set if we know what the final type will be. I.e. it's not lazy.
let resolvedType = type;
if (typeof type === 'function') {
if (shouldConstruct(type)) {
fiberTag = ClassComponent;
if (__DEV__) {
resolvedType = resolveClassForHotReloading(resolvedType);
}
} else {
if (__DEV__) {
resolvedType = resolveFunctionForHotReloading(resolvedType);
}
}
} else if (typeof type === 'string') {
if (
enableFloat &&
supportsResources &&
enableHostSingletons &&
supportsSingletons
) {
const hostContext = getHostContext();
fiberTag = isHostResourceType(type, pendingProps, hostContext)
? HostResource
: isHostSingletonType(type)
? HostSingleton
: HostComponent;
} else if (enableFloat && supportsResources) {
const hostContext = getHostContext();
fiberTag = isHostResourceType(type, pendingProps, hostContext)
? HostResource
: HostComponent;
} else if (enableHostSingletons && supportsSingletons) {
fiberTag = isHostSingletonType(type) ? HostSingleton : HostComponent;
} else {
fiberTag = HostComponent;
}
} else {
getTag: switch (type) {
case REACT_FRAGMENT_TYPE:
return createFiberFromFragment(pendingProps.children, mode, lanes, key);
case REACT_STRICT_MODE_TYPE:
fiberTag = Mode;
mode |= StrictLegacyMode;
if ((mode & ConcurrentMode) !== NoMode) {
// Strict effects should never run on legacy roots
mode |= StrictEffectsMode;
}
break;
case REACT_PROFILER_TYPE:
return createFiberFromProfiler(pendingProps, mode, lanes, key);
case REACT_SUSPENSE_TYPE:
return createFiberFromSuspense(pendingProps, mode, lanes, key);
case REACT_SUSPENSE_LIST_TYPE:
return createFiberFromSuspenseList(pendingProps, mode, lanes, key);
case REACT_OFFSCREEN_TYPE:
return createFiberFromOffscreen(pendingProps, mode, lanes, key);
case REACT_LEGACY_HIDDEN_TYPE:
if (enableLegacyHidden) {
return createFiberFromLegacyHidden(pendingProps, mode, lanes, key);
}
// eslint-disable-next-line no-fallthrough
case REACT_SCOPE_TYPE:
if (enableScopeAPI) {
return createFiberFromScope(type, pendingProps, mode, lanes, key);
}
// eslint-disable-next-line no-fallthrough
case REACT_CACHE_TYPE:
if (enableCache) {
return createFiberFromCache(pendingProps, mode, lanes, key);
}
// eslint-disable-next-line no-fallthrough
case REACT_TRACING_MARKER_TYPE:
if (enableTransitionTracing) {
return createFiberFromTracingMarker(pendingProps, mode, lanes, key);
}
// eslint-disable-next-line no-fallthrough
case REACT_DEBUG_TRACING_MODE_TYPE:
if (enableDebugTracing) {
fiberTag = Mode;
mode |= DebugTracingMode;
break;
}
// eslint-disable-next-line no-fallthrough
default: {
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
fiberTag = ContextProvider;
break getTag;
case REACT_CONTEXT_TYPE:
// This is a consumer
fiberTag = ContextConsumer;
break getTag;
case REACT_FORWARD_REF_TYPE:
fiberTag = ForwardRef;
if (__DEV__) {
resolvedType = resolveForwardRefForHotReloading(resolvedType);
}
break getTag;
case REACT_MEMO_TYPE:
fiberTag = MemoComponent;
break getTag;
case REACT_LAZY_TYPE:
fiberTag = LazyComponent;
resolvedType = null;
break getTag;
}
}
let info = '';
if (__DEV__) {
if (
type === undefined ||
(typeof type === 'object' &&
type !== null &&
Object.keys(type).length === 0)
) {
info +=
' You likely forgot to export your component from the file ' +
"it's defined in, or you might have mixed up default and " +
'named imports.';
}
const ownerName = owner ? getComponentNameFromFiber(owner) : null;
if (ownerName) {
info += '\n\nCheck the render method of `' + ownerName + '`.';
}
}
throw new Error(
'Element type is invalid: expected a string (for built-in ' +
'components) or a class/function (for composite components) ' +
`but got: ${type == null ? type : typeof type}.${info}`,
);
}
}
}
const fiber = createFiber(fiberTag, pendingProps, key, mode);
fiber.elementType = type;
fiber.type = resolvedType;
fiber.lanes = lanes;
if (__DEV__) {
fiber._debugOwner = owner;
}
return fiber;
}
export function createFiberFromElement(
element: ReactElement,
mode: TypeOfMode,
lanes: Lanes,
): Fiber {
let owner = null;
if (__DEV__) {
owner = element._owner;
}
const type = element.type;
const key = element.key;
const pendingProps = element.props;
const fiber = createFiberFromTypeAndProps(
type,
key,
pendingProps,
owner,
mode,
lanes,
);
if (__DEV__) {
fiber._debugSource = element._source;
fiber._debugOwner = element._owner;
}
return fiber;
}
export function createFiberFromFragment(
elements: ReactFragment,
mode: TypeOfMode,
lanes: Lanes,
key: null | string,
): Fiber {
const fiber = createFiber(Fragment, elements, key, mode);
fiber.lanes = lanes;
return fiber;
}
function createFiberFromScope(
scope: ReactScope,
pendingProps: any,
mode: TypeOfMode,
lanes: Lanes,
key: null | string,
) {
const fiber = createFiber(ScopeComponent, pendingProps, key, mode);
fiber.type = scope;
fiber.elementType = scope;
fiber.lanes = lanes;
return fiber;
}
function createFiberFromProfiler(
pendingProps: any,
mode: TypeOfMode,
lanes: Lanes,
key: null | string,
): Fiber {
if (__DEV__) {
if (typeof pendingProps.id !== 'string') {
console.error(
'Profiler must specify an "id" of type `string` as a prop. Received the type `%s` instead.',
typeof pendingProps.id,
);
}
}
const fiber = createFiber(Profiler, pendingProps, key, mode | ProfileMode);
fiber.elementType = REACT_PROFILER_TYPE;
fiber.lanes = lanes;
if (enableProfilerTimer) {
fiber.stateNode = {
effectDuration: 0,
passiveEffectDuration: 0,
};
}
return fiber;
}
export function createFiberFromSuspense(
pendingProps: any,
mode: TypeOfMode,
lanes: Lanes,
key: null | string,
): Fiber {
const fiber = createFiber(SuspenseComponent, pendingProps, key, mode);
fiber.elementType = REACT_SUSPENSE_TYPE;
fiber.lanes = lanes;
return fiber;
}
export function createFiberFromSuspenseList(
pendingProps: any,
mode: TypeOfMode,
lanes: Lanes,
key: null | string,
): Fiber {
const fiber = createFiber(SuspenseListComponent, pendingProps, key, mode);
fiber.elementType = REACT_SUSPENSE_LIST_TYPE;
fiber.lanes = lanes;
return fiber;
}
export function createFiberFromOffscreen(
pendingProps: OffscreenProps,
mode: TypeOfMode,
lanes: Lanes,
key: null | string,
): Fiber {
const fiber = createFiber(OffscreenComponent, pendingProps, key, mode);
fiber.elementType = REACT_OFFSCREEN_TYPE;
fiber.lanes = lanes;
const primaryChildInstance: OffscreenInstance = {
_visibility: OffscreenVisible,
_pendingMarkers: null,
_retryCache: null,
_transitions: null,
_current: null,
detach: () => detachOffscreenInstance(primaryChildInstance),
};
fiber.stateNode = primaryChildInstance;
return fiber;
}
export function createFiberFromLegacyHidden(
pendingProps: OffscreenProps,
mode: TypeOfMode,
lanes: Lanes,
key: null | string,
): Fiber {
const fiber = createFiber(LegacyHiddenComponent, pendingProps, key, mode);
fiber.elementType = REACT_LEGACY_HIDDEN_TYPE;
fiber.lanes = lanes;
// Adding a stateNode for legacy hidden because it's currently using
// the offscreen implementation, which depends on a state node
const instance: OffscreenInstance = {
_visibility: OffscreenVisible,
_pendingMarkers: null,
_transitions: null,
_retryCache: null,
_current: null,
detach: () => detachOffscreenInstance(instance),
};
fiber.stateNode = instance;
return fiber;
}
export function createFiberFromCache(
pendingProps: any,
mode: TypeOfMode,
lanes: Lanes,
key: null | string,
): Fiber {
const fiber = createFiber(CacheComponent, pendingProps, key, mode);
fiber.elementType = REACT_CACHE_TYPE;
fiber.lanes = lanes;
return fiber;
}
export function createFiberFromTracingMarker(
pendingProps: any,
mode: TypeOfMode,
lanes: Lanes,
key: null | string,
): Fiber {
const fiber = createFiber(TracingMarkerComponent, pendingProps, key, mode);
fiber.elementType = REACT_TRACING_MARKER_TYPE;
fiber.lanes = lanes;
const tracingMarkerInstance: TracingMarkerInstance = {
tag: TransitionTracingMarker,
transitions: null,
pendingBoundaries: null,
aborts: null,
name: pendingProps.name,
};
fiber.stateNode = tracingMarkerInstance;
return fiber;
}
export function createFiberFromText(
content: string,
mode: TypeOfMode,
lanes: Lanes,
): Fiber {
const fiber = createFiber(HostText, content, null, mode);
fiber.lanes = lanes;
return fiber;
}
export function createFiberFromHostInstanceForDeletion(): Fiber {
const fiber = createFiber(HostComponent, null, null, NoMode);
fiber.elementType = 'DELETED';
return fiber;
}
export function createFiberFromDehydratedFragment(
dehydratedNode: SuspenseInstance,
): Fiber {
const fiber = createFiber(DehydratedFragment, null, null, NoMode);
fiber.stateNode = dehydratedNode;
return fiber;
}
export function createFiberFromPortal(
portal: ReactPortal,
mode: TypeOfMode,
lanes: Lanes,
): Fiber {
const pendingProps = portal.children !== null ? portal.children : [];
const fiber = createFiber(HostPortal, pendingProps, portal.key, mode);
fiber.lanes = lanes;
fiber.stateNode = {
containerInfo: portal.containerInfo,
pendingChildren: null, // Used by persistent updates
implementation: portal.implementation,
};
return fiber;
}
// Used for stashing WIP properties to replay failed work in DEV.
export function assignFiberPropertiesInDEV(
target: Fiber | null,
source: Fiber,
): Fiber {
if (target === null) {
// This Fiber's initial properties will always be overwritten.
// We only use a Fiber to ensure the same hidden class so DEV isn't slow.
target = createFiber(IndeterminateComponent, null, null, NoMode);
}
// This is intentionally written as a list of all properties.
// We tried to use Object.assign() instead but this is called in
// the hottest path, and Object.assign() was too slow:
// https://github.com/facebook/react/issues/12502
// This code is DEV-only so size is not a concern.
target.tag = source.tag;
target.key = source.key;
target.elementType = source.elementType;
target.type = source.type;
target.stateNode = source.stateNode;
target.return = source.return;
target.child = source.child;
target.sibling = source.sibling;
target.index = source.index;
target.ref = source.ref;
target.refCleanup = source.refCleanup;
target.pendingProps = source.pendingProps;
target.memoizedProps = source.memoizedProps;
target.updateQueue = source.updateQueue;
target.memoizedState = source.memoizedState;
target.dependencies = source.dependencies;
target.mode = source.mode;
target.flags = source.flags;
target.subtreeFlags = source.subtreeFlags;
target.deletions = source.deletions;
target.lanes = source.lanes;
target.childLanes = source.childLanes;
target.alternate = source.alternate;
if (enableProfilerTimer) {
target.actualDuration = source.actualDuration;
target.actualStartTime = source.actualStartTime;
target.selfBaseDuration = source.selfBaseDuration;
target.treeBaseDuration = source.treeBaseDuration;
}
target._debugSource = source._debugSource;
target._debugOwner = source._debugOwner;
target._debugNeedsRemount = source._debugNeedsRemount;
target._debugHookTypes = source._debugHookTypes;
return target;
}

View File

@ -1,57 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber} from './ReactFiber.new';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {warnsIfNotActing} from './ReactFiberHostConfig';
const {ReactCurrentActQueue} = ReactSharedInternals;
export function isLegacyActEnvironment(fiber: Fiber): boolean {
if (__DEV__) {
// Legacy mode. We preserve the behavior of React 17's act. It assumes an
// act environment whenever `jest` is defined, but you can still turn off
// spurious warnings by setting IS_REACT_ACT_ENVIRONMENT explicitly
// to false.
const isReactActEnvironmentGlobal =
// $FlowFixMe Flow doesn't know about IS_REACT_ACT_ENVIRONMENT global
typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined'
? IS_REACT_ACT_ENVIRONMENT
: undefined;
// $FlowFixMe - Flow doesn't know about jest
const jestIsDefined = typeof jest !== 'undefined';
return (
warnsIfNotActing && jestIsDefined && isReactActEnvironmentGlobal !== false
);
}
return false;
}
export function isConcurrentActEnvironment(): void | boolean {
if (__DEV__) {
const isReactActEnvironmentGlobal =
typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined'
? IS_REACT_ACT_ENVIRONMENT
: undefined;
if (!isReactActEnvironmentGlobal && ReactCurrentActQueue.current !== null) {
// TODO: Include link to relevant documentation page.
console.error(
'The current testing environment is not configured to support ' +
'act(...)',
);
}
return isReactActEnvironmentGlobal;
}
return false;
}

File diff suppressed because it is too large Load Diff

View File

@ -170,7 +170,7 @@ import {
getResource,
} from './ReactFiberHostConfig';
import type {SuspenseInstance} from './ReactFiberHostConfig';
import {shouldError, shouldSuspend} from './ReactFiberReconciler';
import {shouldError, shouldSuspend} from './ReactFiberReconciler.old';
import {pushHostContext, pushHostContainer} from './ReactFiberHostContext.old';
import {
suspenseStackCursor,

View File

@ -1,41 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {CacheDispatcher} from './ReactInternalTypes';
import type {Cache} from './ReactFiberCacheComponent.new';
import {enableCache} from 'shared/ReactFeatureFlags';
import {readContext} from './ReactFiberNewContext.new';
import {CacheContext} from './ReactFiberCacheComponent.new';
function getCacheSignal(): AbortSignal {
if (!enableCache) {
throw new Error('Not implemented.');
}
const cache: Cache = readContext(CacheContext);
return cache.controller.signal;
}
function getCacheForType<T>(resourceType: () => T): T {
if (!enableCache) {
throw new Error('Not implemented.');
}
const cache: Cache = readContext(CacheContext);
let cacheForType: T | void = (cache.data.get(resourceType): any);
if (cacheForType === undefined) {
cacheForType = resourceType();
cache.data.set(resourceType, cacheForType);
}
return cacheForType;
}
export const DefaultCacheDispatcher: CacheDispatcher = {
getCacheSignal,
getCacheForType,
};

View File

@ -1,147 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ReactContext} from 'shared/ReactTypes';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import {enableCache} from 'shared/ReactFeatureFlags';
import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
import {pushProvider, popProvider} from './ReactFiberNewContext.new';
import * as Scheduler from 'scheduler';
// In environments without AbortController (e.g. tests)
// replace it with a lightweight shim that only has the features we use.
const AbortControllerLocal: typeof AbortController = enableCache
? typeof AbortController !== 'undefined'
? AbortController
: (function AbortControllerShim() {
const listeners = [];
const signal = (this.signal = {
aborted: false,
addEventListener: (type, listener) => {
listeners.push(listener);
},
});
this.abort = () => {
signal.aborted = true;
listeners.forEach(listener => listener());
};
}: AbortController)
: (null: any);
export type Cache = {
controller: AbortControllerLocal,
data: Map<() => mixed, mixed>,
refCount: number,
};
export type CacheComponentState = {
+parent: Cache,
+cache: Cache,
};
export type SpawnedCachePool = {
+parent: Cache,
+pool: Cache,
};
// Intentionally not named imports because Rollup would
// use dynamic dispatch for CommonJS interop named imports.
const {
unstable_scheduleCallback: scheduleCallback,
unstable_NormalPriority: NormalPriority,
} = Scheduler;
export const CacheContext: ReactContext<Cache> = enableCache
? {
$$typeof: REACT_CONTEXT_TYPE,
// We don't use Consumer/Provider for Cache components. So we'll cheat.
Consumer: (null: any),
Provider: (null: any),
// We'll initialize these at the root.
_currentValue: (null: any),
_currentValue2: (null: any),
_threadCount: 0,
_defaultValue: (null: any),
_globalName: (null: any),
}
: (null: any);
if (__DEV__ && enableCache) {
CacheContext._currentRenderer = null;
CacheContext._currentRenderer2 = null;
}
// Creates a new empty Cache instance with a ref-count of 0. The caller is responsible
// for retaining the cache once it is in use (retainCache), and releasing the cache
// once it is no longer needed (releaseCache).
export function createCache(): Cache {
if (!enableCache) {
return (null: any);
}
const cache: Cache = {
controller: new AbortControllerLocal(),
data: new Map(),
refCount: 0,
};
return cache;
}
export function retainCache(cache: Cache) {
if (!enableCache) {
return;
}
if (__DEV__) {
if (cache.controller.signal.aborted) {
console.warn(
'A cache instance was retained after it was already freed. ' +
'This likely indicates a bug in React.',
);
}
}
cache.refCount++;
}
// Cleanup a cache instance, potentially freeing it if there are no more references
export function releaseCache(cache: Cache) {
if (!enableCache) {
return;
}
cache.refCount--;
if (__DEV__) {
if (cache.refCount < 0) {
console.warn(
'A cache instance was released after it was already freed. ' +
'This likely indicates a bug in React.',
);
}
}
if (cache.refCount === 0) {
scheduleCallback(NormalPriority, () => {
cache.controller.abort();
});
}
}
export function pushCacheProvider(workInProgress: Fiber, cache: Cache) {
if (!enableCache) {
return;
}
pushProvider(workInProgress, CacheContext, cache);
}
export function popCacheProvider(workInProgress: Fiber, cache: Cache) {
if (!enableCache) {
return;
}
popProvider(CacheContext, workInProgress);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,742 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
// UpdateQueue is a linked list of prioritized updates.
//
// Like fibers, update queues come in pairs: a current queue, which represents
// the visible state of the screen, and a work-in-progress queue, which can be
// mutated and processed asynchronously before it is committed — a form of
// double buffering. If a work-in-progress render is discarded before finishing,
// we create a new work-in-progress by cloning the current queue.
//
// Both queues share a persistent, singly-linked list structure. To schedule an
// update, we append it to the end of both queues. Each queue maintains a
// pointer to first update in the persistent list that hasn't been processed.
// The work-in-progress pointer always has a position equal to or greater than
// the current queue, since we always work on that one. The current queue's
// pointer is only updated during the commit phase, when we swap in the
// work-in-progress.
//
// For example:
//
// Current pointer: A - B - C - D - E - F
// Work-in-progress pointer: D - E - F
// ^
// The work-in-progress queue has
// processed more updates than current.
//
// The reason we append to both queues is because otherwise we might drop
// updates without ever processing them. For example, if we only add updates to
// the work-in-progress queue, some updates could be lost whenever a work-in
// -progress render restarts by cloning from current. Similarly, if we only add
// updates to the current queue, the updates will be lost whenever an already
// in-progress queue commits and swaps with the current queue. However, by
// adding to both queues, we guarantee that the update will be part of the next
// work-in-progress. (And because the work-in-progress queue becomes the
// current queue once it commits, there's no danger of applying the same
// update twice.)
//
// Prioritization
// --------------
//
// Updates are not sorted by priority, but by insertion; new updates are always
// appended to the end of the list.
//
// The priority is still important, though. When processing the update queue
// during the render phase, only the updates with sufficient priority are
// included in the result. If we skip an update because it has insufficient
// priority, it remains in the queue to be processed later, during a lower
// priority render. Crucially, all updates subsequent to a skipped update also
// remain in the queue *regardless of their priority*. That means high priority
// updates are sometimes processed twice, at two separate priorities. We also
// keep track of a base state, that represents the state before the first
// update in the queue is applied.
//
// For example:
//
// Given a base state of '', and the following queue of updates
//
// A1 - B2 - C1 - D2
//
// where the number indicates the priority, and the update is applied to the
// previous state by appending a letter, React will process these updates as
// two separate renders, one per distinct priority level:
//
// First render, at priority 1:
// Base state: ''
// Updates: [A1, C1]
// Result state: 'AC'
//
// Second render, at priority 2:
// Base state: 'A' <- The base state does not include C1,
// because B2 was skipped.
// Updates: [B2, C1, D2] <- C1 was rebased on top of B2
// Result state: 'ABCD'
//
// Because we process updates in insertion order, and rebase high priority
// updates when preceding updates are skipped, the final result is deterministic
// regardless of priority. Intermediate state may vary according to system
// resources, but the final state is always the same.
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {Lanes, Lane} from './ReactFiberLane.new';
import {
NoLane,
NoLanes,
OffscreenLane,
isSubsetOfLanes,
mergeLanes,
removeLanes,
isTransitionLane,
intersectLanes,
markRootEntangled,
} from './ReactFiberLane.new';
import {
enterDisallowedContextReadInDEV,
exitDisallowedContextReadInDEV,
} from './ReactFiberNewContext.new';
import {
Callback,
Visibility,
ShouldCapture,
DidCapture,
} from './ReactFiberFlags';
import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags';
import {StrictLegacyMode} from './ReactTypeOfMode';
import {
markSkippedUpdateLanes,
isUnsafeClassRenderPhaseUpdate,
getWorkInProgressRootRenderLanes,
} from './ReactFiberWorkLoop.new';
import {
enqueueConcurrentClassUpdate,
unsafe_markUpdateLaneFromFiberToRoot,
} from './ReactFiberConcurrentUpdates.new';
import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.new';
import assign from 'shared/assign';
export type Update<State> = {
// TODO: Temporary field. Will remove this by storing a map of
// transition -> event time on the root.
eventTime: number,
lane: Lane,
tag: 0 | 1 | 2 | 3,
payload: any,
callback: (() => mixed) | null,
next: Update<State> | null,
};
export type SharedQueue<State> = {
pending: Update<State> | null,
lanes: Lanes,
hiddenCallbacks: Array<() => mixed> | null,
};
export type UpdateQueue<State> = {
baseState: State,
firstBaseUpdate: Update<State> | null,
lastBaseUpdate: Update<State> | null,
shared: SharedQueue<State>,
callbacks: Array<() => mixed> | null,
};
export const UpdateState = 0;
export const ReplaceState = 1;
export const ForceUpdate = 2;
export const CaptureUpdate = 3;
// Global state that is reset at the beginning of calling `processUpdateQueue`.
// It should only be read right after calling `processUpdateQueue`, via
// `checkHasForceUpdateAfterProcessing`.
let hasForceUpdate = false;
let didWarnUpdateInsideUpdate;
let currentlyProcessingQueue;
export let resetCurrentlyProcessingQueue: () => void;
if (__DEV__) {
didWarnUpdateInsideUpdate = false;
currentlyProcessingQueue = null;
resetCurrentlyProcessingQueue = () => {
currentlyProcessingQueue = null;
};
}
export function initializeUpdateQueue<State>(fiber: Fiber): void {
const queue: UpdateQueue<State> = {
baseState: fiber.memoizedState,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
lanes: NoLanes,
hiddenCallbacks: null,
},
callbacks: null,
};
fiber.updateQueue = queue;
}
export function cloneUpdateQueue<State>(
current: Fiber,
workInProgress: Fiber,
): void {
// Clone the update queue from current. Unless it's already a clone.
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
if (queue === currentQueue) {
const clone: UpdateQueue<State> = {
baseState: currentQueue.baseState,
firstBaseUpdate: currentQueue.firstBaseUpdate,
lastBaseUpdate: currentQueue.lastBaseUpdate,
shared: currentQueue.shared,
callbacks: null,
};
workInProgress.updateQueue = clone;
}
}
export function createUpdate(eventTime: number, lane: Lane): Update<mixed> {
const update: Update<mixed> = {
eventTime,
lane,
tag: UpdateState,
payload: null,
callback: null,
next: null,
};
return update;
}
export function enqueueUpdate<State>(
fiber: Fiber,
update: Update<State>,
lane: Lane,
): FiberRoot | null {
const updateQueue = fiber.updateQueue;
if (updateQueue === null) {
// Only occurs if the fiber has been unmounted.
return null;
}
const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
if (__DEV__) {
if (
currentlyProcessingQueue === sharedQueue &&
!didWarnUpdateInsideUpdate
) {
console.error(
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
'from inside an update function. Update functions should be pure, ' +
'with zero side-effects. Consider using componentDidUpdate or a ' +
'callback.',
);
didWarnUpdateInsideUpdate = true;
}
}
if (isUnsafeClassRenderPhaseUpdate(fiber)) {
// This is an unsafe render phase update. Add directly to the update
// queue so we can process it immediately during the current render.
const pending = sharedQueue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
// Update the childLanes even though we're most likely already rendering
// this fiber. This is for backwards compatibility in the case where you
// update a different component during render phase than the one that is
// currently renderings (a pattern that is accompanied by a warning).
return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane);
} else {
return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
}
}
export function entangleTransitions(root: FiberRoot, fiber: Fiber, lane: Lane) {
const updateQueue = fiber.updateQueue;
if (updateQueue === null) {
// Only occurs if the fiber has been unmounted.
return;
}
const sharedQueue: SharedQueue<mixed> = (updateQueue: any).shared;
if (isTransitionLane(lane)) {
let queueLanes = sharedQueue.lanes;
// If any entangled lanes are no longer pending on the root, then they must
// have finished. We can remove them from the shared queue, which represents
// a superset of the actually pending lanes. In some cases we may entangle
// more than we need to, but that's OK. In fact it's worse if we *don't*
// entangle when we should.
queueLanes = intersectLanes(queueLanes, root.pendingLanes);
// Entangle the new transition lane with the other transition lanes.
const newQueueLanes = mergeLanes(queueLanes, lane);
sharedQueue.lanes = newQueueLanes;
// Even if queue.lanes already include lane, we don't know for certain if
// the lane finished since the last time we entangled it. So we need to
// entangle it again, just to be sure.
markRootEntangled(root, newQueueLanes);
}
}
export function enqueueCapturedUpdate<State>(
workInProgress: Fiber,
capturedUpdate: Update<State>,
) {
// Captured updates are updates that are thrown by a child during the render
// phase. They should be discarded if the render is aborted. Therefore,
// we should only put them on the work-in-progress queue, not the current one.
let queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
// Check if the work-in-progress queue is a clone.
const current = workInProgress.alternate;
if (current !== null) {
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
if (queue === currentQueue) {
// The work-in-progress queue is the same as current. This happens when
// we bail out on a parent fiber that then captures an error thrown by
// a child. Since we want to append the update only to the work-in
// -progress queue, we need to clone the updates. We usually clone during
// processUpdateQueue, but that didn't happen in this case because we
// skipped over the parent when we bailed out.
let newFirst = null;
let newLast = null;
const firstBaseUpdate = queue.firstBaseUpdate;
if (firstBaseUpdate !== null) {
// Loop through the updates and clone them.
let update: Update<State> = firstBaseUpdate;
do {
const clone: Update<State> = {
eventTime: update.eventTime,
lane: update.lane,
tag: update.tag,
payload: update.payload,
// When this update is rebased, we should not fire its
// callback again.
callback: null,
next: null,
};
if (newLast === null) {
newFirst = newLast = clone;
} else {
newLast.next = clone;
newLast = clone;
}
// $FlowFixMe[incompatible-type] we bail out when we get a null
update = update.next;
} while (update !== null);
// Append the captured update the end of the cloned list.
if (newLast === null) {
newFirst = newLast = capturedUpdate;
} else {
newLast.next = capturedUpdate;
newLast = capturedUpdate;
}
} else {
// There are no base updates.
newFirst = newLast = capturedUpdate;
}
queue = {
baseState: currentQueue.baseState,
firstBaseUpdate: newFirst,
lastBaseUpdate: newLast,
shared: currentQueue.shared,
callbacks: currentQueue.callbacks,
};
workInProgress.updateQueue = queue;
return;
}
}
// Append the update to the end of the list.
const lastBaseUpdate = queue.lastBaseUpdate;
if (lastBaseUpdate === null) {
queue.firstBaseUpdate = capturedUpdate;
} else {
lastBaseUpdate.next = capturedUpdate;
}
queue.lastBaseUpdate = capturedUpdate;
}
function getStateFromUpdate<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
update: Update<State>,
prevState: State,
nextProps: any,
instance: any,
): any {
switch (update.tag) {
case ReplaceState: {
const payload = update.payload;
if (typeof payload === 'function') {
// Updater function
if (__DEV__) {
enterDisallowedContextReadInDEV();
}
const nextState = payload.call(instance, prevState, nextProps);
if (__DEV__) {
if (
debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictLegacyMode
) {
setIsStrictModeForDevtools(true);
try {
payload.call(instance, prevState, nextProps);
} finally {
setIsStrictModeForDevtools(false);
}
}
exitDisallowedContextReadInDEV();
}
return nextState;
}
// State object
return payload;
}
case CaptureUpdate: {
workInProgress.flags =
(workInProgress.flags & ~ShouldCapture) | DidCapture;
}
// Intentional fallthrough
case UpdateState: {
const payload = update.payload;
let partialState;
if (typeof payload === 'function') {
// Updater function
if (__DEV__) {
enterDisallowedContextReadInDEV();
}
partialState = payload.call(instance, prevState, nextProps);
if (__DEV__) {
if (
debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictLegacyMode
) {
setIsStrictModeForDevtools(true);
try {
payload.call(instance, prevState, nextProps);
} finally {
setIsStrictModeForDevtools(false);
}
}
exitDisallowedContextReadInDEV();
}
} else {
// Partial state object
partialState = payload;
}
if (partialState === null || partialState === undefined) {
// Null and undefined are treated as no-ops.
return prevState;
}
// Merge the partial state and the previous state.
return assign({}, prevState, partialState);
}
case ForceUpdate: {
hasForceUpdate = true;
return prevState;
}
}
return prevState;
}
export function processUpdateQueue<State>(
workInProgress: Fiber,
props: any,
instance: any,
renderLanes: Lanes,
): void {
// This is always non-null on a ClassComponent or HostRoot
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
hasForceUpdate = false;
if (__DEV__) {
// $FlowFixMe[escaped-generic] discovered when updating Flow
currentlyProcessingQueue = queue.shared;
}
let firstBaseUpdate = queue.firstBaseUpdate;
let lastBaseUpdate = queue.lastBaseUpdate;
// Check if there are pending updates. If so, transfer them to the base queue.
let pendingQueue = queue.shared.pending;
if (pendingQueue !== null) {
queue.shared.pending = null;
// The pending queue is circular. Disconnect the pointer between first
// and last so that it's non-circular.
const lastPendingUpdate = pendingQueue;
const firstPendingUpdate = lastPendingUpdate.next;
lastPendingUpdate.next = null;
// Append pending updates to base queue
if (lastBaseUpdate === null) {
firstBaseUpdate = firstPendingUpdate;
} else {
lastBaseUpdate.next = firstPendingUpdate;
}
lastBaseUpdate = lastPendingUpdate;
// If there's a current queue, and it's different from the base queue, then
// we need to transfer the updates to that queue, too. Because the base
// queue is a singly-linked list with no cycles, we can append to both
// lists and take advantage of structural sharing.
// TODO: Pass `current` as argument
const current = workInProgress.alternate;
if (current !== null) {
// This is always non-null on a ClassComponent or HostRoot
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
const currentLastBaseUpdate = currentQueue.lastBaseUpdate;
if (currentLastBaseUpdate !== lastBaseUpdate) {
if (currentLastBaseUpdate === null) {
currentQueue.firstBaseUpdate = firstPendingUpdate;
} else {
currentLastBaseUpdate.next = firstPendingUpdate;
}
currentQueue.lastBaseUpdate = lastPendingUpdate;
}
}
}
// These values may change as we process the queue.
if (firstBaseUpdate !== null) {
// Iterate through the list of updates to compute the result.
let newState = queue.baseState;
// TODO: Don't need to accumulate this. Instead, we can remove renderLanes
// from the original lanes.
let newLanes = NoLanes;
let newBaseState = null;
let newFirstBaseUpdate = null;
let newLastBaseUpdate = null;
let update: Update<State> = firstBaseUpdate;
do {
// TODO: Don't need this field anymore
const updateEventTime = update.eventTime;
// An extra OffscreenLane bit is added to updates that were made to
// a hidden tree, so that we can distinguish them from updates that were
// already there when the tree was hidden.
const updateLane = removeLanes(update.lane, OffscreenLane);
const isHiddenUpdate = updateLane !== update.lane;
// Check if this update was made while the tree was hidden. If so, then
// it's not a "base" update and we should disregard the extra base lanes
// that were added to renderLanes when we entered the Offscreen tree.
const shouldSkipUpdate = isHiddenUpdate
? !isSubsetOfLanes(getWorkInProgressRootRenderLanes(), updateLane)
: !isSubsetOfLanes(renderLanes, updateLane);
if (shouldSkipUpdate) {
// Priority is insufficient. Skip this update. If this is the first
// skipped update, the previous update/state is the new base
// update/state.
const clone: Update<State> = {
eventTime: updateEventTime,
lane: updateLane,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
if (newLastBaseUpdate === null) {
newFirstBaseUpdate = newLastBaseUpdate = clone;
newBaseState = newState;
} else {
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
// Update the remaining priority in the queue.
newLanes = mergeLanes(newLanes, updateLane);
} else {
// This update does have sufficient priority.
if (newLastBaseUpdate !== null) {
const clone: Update<State> = {
eventTime: updateEventTime,
// This update is going to be committed so we never want uncommit
// it. Using NoLane works because 0 is a subset of all bitmasks, so
// this will never be skipped by the check above.
lane: NoLane,
tag: update.tag,
payload: update.payload,
// When this update is rebased, we should not fire its
// callback again.
callback: null,
next: null,
};
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
// Process this update.
newState = getStateFromUpdate(
workInProgress,
queue,
update,
newState,
props,
instance,
);
const callback = update.callback;
if (callback !== null) {
workInProgress.flags |= Callback;
if (isHiddenUpdate) {
workInProgress.flags |= Visibility;
}
const callbacks = queue.callbacks;
if (callbacks === null) {
queue.callbacks = [callback];
} else {
callbacks.push(callback);
}
}
}
// $FlowFixMe[incompatible-type] we bail out when we get a null
update = update.next;
if (update === null) {
pendingQueue = queue.shared.pending;
if (pendingQueue === null) {
break;
} else {
// An update was scheduled from inside a reducer. Add the new
// pending updates to the end of the list and keep processing.
const lastPendingUpdate = pendingQueue;
// Intentionally unsound. Pending updates form a circular list, but we
// unravel them when transferring them to the base queue.
const firstPendingUpdate = ((lastPendingUpdate.next: any): Update<State>);
lastPendingUpdate.next = null;
update = firstPendingUpdate;
queue.lastBaseUpdate = lastPendingUpdate;
queue.shared.pending = null;
}
}
} while (true);
if (newLastBaseUpdate === null) {
newBaseState = newState;
}
queue.baseState = ((newBaseState: any): State);
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate;
if (firstBaseUpdate === null) {
// `queue.lanes` is used for entangling transitions. We can set it back to
// zero once the queue is empty.
queue.shared.lanes = NoLanes;
}
// Set the remaining expiration time to be whatever is remaining in the queue.
// This should be fine because the only two other things that contribute to
// expiration time are props and context. We're already in the middle of the
// begin phase by the time we start processing the queue, so we've already
// dealt with the props. Context in components that specify
// shouldComponentUpdate is tricky; but we'll have to account for
// that regardless.
markSkippedUpdateLanes(newLanes);
workInProgress.lanes = newLanes;
workInProgress.memoizedState = newState;
}
if (__DEV__) {
currentlyProcessingQueue = null;
}
}
function callCallback(callback, context) {
if (typeof callback !== 'function') {
throw new Error(
'Invalid argument passed as callback. Expected a function. Instead ' +
`received: ${callback}`,
);
}
callback.call(context);
}
export function resetHasForceUpdateBeforeProcessing() {
hasForceUpdate = false;
}
export function checkHasForceUpdateAfterProcessing(): boolean {
return hasForceUpdate;
}
export function deferHiddenCallbacks<State>(
updateQueue: UpdateQueue<State>,
): void {
// When an update finishes on a hidden component, its callback should not
// be fired until/unless the component is made visible again. Stash the
// callback on the shared queue object so it can be fired later.
const newHiddenCallbacks = updateQueue.callbacks;
if (newHiddenCallbacks !== null) {
const existingHiddenCallbacks = updateQueue.shared.hiddenCallbacks;
if (existingHiddenCallbacks === null) {
updateQueue.shared.hiddenCallbacks = newHiddenCallbacks;
} else {
updateQueue.shared.hiddenCallbacks = existingHiddenCallbacks.concat(
newHiddenCallbacks,
);
}
}
}
export function commitHiddenCallbacks<State>(
updateQueue: UpdateQueue<State>,
context: any,
): void {
// This component is switching from hidden -> visible. Commit any callbacks
// that were previously deferred.
const hiddenCallbacks = updateQueue.shared.hiddenCallbacks;
if (hiddenCallbacks !== null) {
updateQueue.shared.hiddenCallbacks = null;
for (let i = 0; i < hiddenCallbacks.length; i++) {
const callback = hiddenCallbacks[i];
callCallback(callback, context);
}
}
}
export function commitCallbacks<State>(
updateQueue: UpdateQueue<State>,
context: any,
): void {
const callbacks = updateQueue.callbacks;
if (callbacks !== null) {
updateQueue.callbacks = null;
for (let i = 0; i < callbacks.length; i++) {
const callback = callbacks[i];
callCallback(callback, context);
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,288 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {
UpdateQueue as HookQueue,
Update as HookUpdate,
} from './ReactFiberHooks.new';
import type {
SharedQueue as ClassQueue,
Update as ClassUpdate,
} from './ReactFiberClassUpdateQueue.new';
import type {Lane, Lanes} from './ReactFiberLane.new';
import type {OffscreenInstance} from './ReactFiberOffscreenComponent';
import {
warnAboutUpdateOnNotYetMountedFiberInDEV,
throwIfInfiniteUpdateLoopDetected,
getWorkInProgressRoot,
} from './ReactFiberWorkLoop.new';
import {
NoLane,
NoLanes,
mergeLanes,
markHiddenUpdate,
} from './ReactFiberLane.new';
import {NoFlags, Placement, Hydrating} from './ReactFiberFlags';
import {HostRoot, OffscreenComponent} from './ReactWorkTags';
import {OffscreenVisible} from './ReactFiberOffscreenComponent';
export type ConcurrentUpdate = {
next: ConcurrentUpdate,
lane: Lane,
};
type ConcurrentQueue = {
pending: ConcurrentUpdate | null,
};
// If a render is in progress, and we receive an update from a concurrent event,
// we wait until the current render is over (either finished or interrupted)
// before adding it to the fiber/hook queue. Push to this array so we can
// access the queue, fiber, update, et al later.
const concurrentQueues: Array<any> = [];
let concurrentQueuesIndex = 0;
let concurrentlyUpdatedLanes: Lanes = NoLanes;
export function finishQueueingConcurrentUpdates(): void {
const endIndex = concurrentQueuesIndex;
concurrentQueuesIndex = 0;
concurrentlyUpdatedLanes = NoLanes;
let i = 0;
while (i < endIndex) {
const fiber: Fiber = concurrentQueues[i];
concurrentQueues[i++] = null;
const queue: ConcurrentQueue = concurrentQueues[i];
concurrentQueues[i++] = null;
const update: ConcurrentUpdate = concurrentQueues[i];
concurrentQueues[i++] = null;
const lane: Lane = concurrentQueues[i];
concurrentQueues[i++] = null;
if (queue !== null && update !== null) {
const pending = queue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
if (lane !== NoLane) {
markUpdateLaneFromFiberToRoot(fiber, update, lane);
}
}
}
export function getConcurrentlyUpdatedLanes(): Lanes {
return concurrentlyUpdatedLanes;
}
function enqueueUpdate(
fiber: Fiber,
queue: ConcurrentQueue | null,
update: ConcurrentUpdate | null,
lane: Lane,
) {
// Don't update the `childLanes` on the return path yet. If we already in
// the middle of rendering, wait until after it has completed.
concurrentQueues[concurrentQueuesIndex++] = fiber;
concurrentQueues[concurrentQueuesIndex++] = queue;
concurrentQueues[concurrentQueuesIndex++] = update;
concurrentQueues[concurrentQueuesIndex++] = lane;
concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);
// The fiber's `lane` field is used in some places to check if any work is
// scheduled, to perform an eager bailout, so we need to update it immediately.
// TODO: We should probably move this to the "shared" queue instead.
fiber.lanes = mergeLanes(fiber.lanes, lane);
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, lane);
}
}
export function enqueueConcurrentHookUpdate<S, A>(
fiber: Fiber,
queue: HookQueue<S, A>,
update: HookUpdate<S, A>,
lane: Lane,
): FiberRoot | null {
const concurrentQueue: ConcurrentQueue = (queue: any);
const concurrentUpdate: ConcurrentUpdate = (update: any);
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
return getRootForUpdatedFiber(fiber);
}
export function enqueueConcurrentHookUpdateAndEagerlyBailout<S, A>(
fiber: Fiber,
queue: HookQueue<S, A>,
update: HookUpdate<S, A>,
): void {
// This function is used to queue an update that doesn't need a rerender. The
// only reason we queue it is in case there's a subsequent higher priority
// update that causes it to be rebased.
const lane = NoLane;
const concurrentQueue: ConcurrentQueue = (queue: any);
const concurrentUpdate: ConcurrentUpdate = (update: any);
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
// Usually we can rely on the upcoming render phase to process the concurrent
// queue. However, since this is a bail out, we're not scheduling any work
// here. So the update we just queued will leak until something else happens
// to schedule work (if ever).
//
// Check if we're currently in the middle of rendering a tree, and if not,
// process the queue immediately to prevent a leak.
const isConcurrentlyRendering = getWorkInProgressRoot() !== null;
if (!isConcurrentlyRendering) {
finishQueueingConcurrentUpdates();
}
}
export function enqueueConcurrentClassUpdate<State>(
fiber: Fiber,
queue: ClassQueue<State>,
update: ClassUpdate<State>,
lane: Lane,
): FiberRoot | null {
const concurrentQueue: ConcurrentQueue = (queue: any);
const concurrentUpdate: ConcurrentUpdate = (update: any);
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
return getRootForUpdatedFiber(fiber);
}
export function enqueueConcurrentRenderForLane(
fiber: Fiber,
lane: Lane,
): FiberRoot | null {
enqueueUpdate(fiber, null, null, lane);
return getRootForUpdatedFiber(fiber);
}
// Calling this function outside this module should only be done for backwards
// compatibility and should always be accompanied by a warning.
export function unsafe_markUpdateLaneFromFiberToRoot(
sourceFiber: Fiber,
lane: Lane,
): FiberRoot | null {
// NOTE: For Hyrum's Law reasons, if an infinite update loop is detected, it
// should throw before `markUpdateLaneFromFiberToRoot` is called. But this is
// undefined behavior and we can change it if we need to; it just so happens
// that, at the time of this writing, there's an internal product test that
// happens to rely on this.
const root = getRootForUpdatedFiber(sourceFiber);
markUpdateLaneFromFiberToRoot(sourceFiber, null, lane);
return root;
}
function markUpdateLaneFromFiberToRoot(
sourceFiber: Fiber,
update: ConcurrentUpdate | null,
lane: Lane,
): void {
// Update the source fiber's lanes
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
let alternate = sourceFiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, lane);
}
// Walk the parent path to the root and update the child lanes.
let isHidden = false;
let parent = sourceFiber.return;
let node = sourceFiber;
while (parent !== null) {
parent.childLanes = mergeLanes(parent.childLanes, lane);
alternate = parent.alternate;
if (alternate !== null) {
alternate.childLanes = mergeLanes(alternate.childLanes, lane);
}
if (parent.tag === OffscreenComponent) {
// Check if this offscreen boundary is currently hidden.
//
// The instance may be null if the Offscreen parent was unmounted. Usually
// the parent wouldn't be reachable in that case because we disconnect
// fibers from the tree when they are deleted. However, there's a weird
// edge case where setState is called on a fiber that was interrupted
// before it ever mounted. Because it never mounts, it also never gets
// deleted. Because it never gets deleted, its return pointer never gets
// disconnected. Which means it may be attached to a deleted Offscreen
// parent node. (This discovery suggests it may be better for memory usage
// if we don't attach the `return` pointer until the commit phase, though
// in order to do that we'd need some other way to track the return
// pointer during the initial render, like on the stack.)
//
// This case is always accompanied by a warning, but we still need to
// account for it. (There may be other cases that we haven't discovered,
// too.)
const offscreenInstance: OffscreenInstance | null = parent.stateNode;
if (
offscreenInstance !== null &&
!(offscreenInstance._visibility & OffscreenVisible)
) {
isHidden = true;
}
}
node = parent;
parent = parent.return;
}
if (isHidden && update !== null && node.tag === HostRoot) {
const root: FiberRoot = node.stateNode;
markHiddenUpdate(root, update, lane);
}
}
function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null {
// TODO: We will detect and infinite update loop and throw even if this fiber
// has already unmounted. This isn't really necessary but it happens to be the
// current behavior we've used for several release cycles. Consider not
// performing this check if the updated fiber already unmounted, since it's
// not possible for that to cause an infinite update loop.
throwIfInfiniteUpdateLoopDetected();
// When a setState happens, we must ensure the root is scheduled. Because
// update queues do not have a backpointer to the root, the only way to do
// this currently is to walk up the return path. This used to not be a big
// deal because we would have to walk up the return path to set
// the `childLanes`, anyway, but now those two traversals happen at
// different times.
// TODO: Consider adding a `root` backpointer on the update queue.
detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber);
let node = sourceFiber;
let parent = node.return;
while (parent !== null) {
detectUpdateOnUnmountedFiber(sourceFiber, node);
node = parent;
parent = node.return;
}
return node.tag === HostRoot ? (node.stateNode: FiberRoot) : null;
}
function detectUpdateOnUnmountedFiber(sourceFiber: Fiber, parent: Fiber) {
if (__DEV__) {
const alternate = parent.alternate;
if (
alternate === null &&
(parent.flags & (Placement | Hydrating)) !== NoFlags
) {
warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber);
}
}
}

View File

@ -1,343 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber} from './ReactInternalTypes';
import type {StackCursor} from './ReactFiberStack.new';
import {isFiberMounted} from './ReactFiberTreeReflection';
import {disableLegacyContext} from 'shared/ReactFeatureFlags';
import {ClassComponent, HostRoot} from './ReactWorkTags';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import checkPropTypes from 'shared/checkPropTypes';
import {createCursor, push, pop} from './ReactFiberStack.new';
let warnedAboutMissingGetChildContext;
if (__DEV__) {
warnedAboutMissingGetChildContext = {};
}
// $FlowFixMe[incompatible-exact]
export const emptyContextObject: {} = {};
if (__DEV__) {
Object.freeze(emptyContextObject);
}
// A cursor to the current merged context object on the stack.
const contextStackCursor: StackCursor<Object> = createCursor(
emptyContextObject,
);
// A cursor to a boolean indicating whether the context has changed.
const didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
// Keep track of the previous context object that was on the stack.
// We use this to get access to the parent context after we have already
// pushed the next context provider, and now need to merge their contexts.
let previousContext: Object = emptyContextObject;
function getUnmaskedContext(
workInProgress: Fiber,
Component: Function,
didPushOwnContextIfProvider: boolean,
): Object {
if (disableLegacyContext) {
return emptyContextObject;
} else {
if (didPushOwnContextIfProvider && isContextProvider(Component)) {
// If the fiber is a context provider itself, when we read its context
// we may have already pushed its own child context on the stack. A context
// provider should not "see" its own child context. Therefore we read the
// previous (parent) context instead for a context provider.
return previousContext;
}
return contextStackCursor.current;
}
}
function cacheContext(
workInProgress: Fiber,
unmaskedContext: Object,
maskedContext: Object,
): void {
if (disableLegacyContext) {
return;
} else {
const instance = workInProgress.stateNode;
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
}
}
function getMaskedContext(
workInProgress: Fiber,
unmaskedContext: Object,
): Object {
if (disableLegacyContext) {
return emptyContextObject;
} else {
const type = workInProgress.type;
const contextTypes = type.contextTypes;
if (!contextTypes) {
return emptyContextObject;
}
// Avoid recreating masked context unless unmasked context has changed.
// Failing to do this will result in unnecessary calls to componentWillReceiveProps.
// This may trigger infinite loops if componentWillReceiveProps calls setState.
const instance = workInProgress.stateNode;
if (
instance &&
instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
) {
return instance.__reactInternalMemoizedMaskedChildContext;
}
const context = {};
for (const key in contextTypes) {
context[key] = unmaskedContext[key];
}
if (__DEV__) {
const name = getComponentNameFromFiber(workInProgress) || 'Unknown';
checkPropTypes(contextTypes, context, 'context', name);
}
// Cache unmasked context so we can avoid recreating masked context unless necessary.
// Context is created before the class component is instantiated so check for instance.
if (instance) {
cacheContext(workInProgress, unmaskedContext, context);
}
return context;
}
}
function hasContextChanged(): boolean {
if (disableLegacyContext) {
return false;
} else {
return didPerformWorkStackCursor.current;
}
}
function isContextProvider(type: Function): boolean {
if (disableLegacyContext) {
return false;
} else {
const childContextTypes = type.childContextTypes;
return childContextTypes !== null && childContextTypes !== undefined;
}
}
function popContext(fiber: Fiber): void {
if (disableLegacyContext) {
return;
} else {
pop(didPerformWorkStackCursor, fiber);
pop(contextStackCursor, fiber);
}
}
function popTopLevelContextObject(fiber: Fiber): void {
if (disableLegacyContext) {
return;
} else {
pop(didPerformWorkStackCursor, fiber);
pop(contextStackCursor, fiber);
}
}
function pushTopLevelContextObject(
fiber: Fiber,
context: Object,
didChange: boolean,
): void {
if (disableLegacyContext) {
return;
} else {
if (contextStackCursor.current !== emptyContextObject) {
throw new Error(
'Unexpected context found on stack. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
push(contextStackCursor, context, fiber);
push(didPerformWorkStackCursor, didChange, fiber);
}
}
function processChildContext(
fiber: Fiber,
type: any,
parentContext: Object,
): Object {
if (disableLegacyContext) {
return parentContext;
} else {
const instance = fiber.stateNode;
const childContextTypes = type.childContextTypes;
// TODO (bvaughn) Replace this behavior with an invariant() in the future.
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
if (typeof instance.getChildContext !== 'function') {
if (__DEV__) {
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
if (!warnedAboutMissingGetChildContext[componentName]) {
warnedAboutMissingGetChildContext[componentName] = true;
console.error(
'%s.childContextTypes is specified but there is no getChildContext() method ' +
'on the instance. You can either define getChildContext() on %s or remove ' +
'childContextTypes from it.',
componentName,
componentName,
);
}
}
return parentContext;
}
const childContext = instance.getChildContext();
for (const contextKey in childContext) {
if (!(contextKey in childContextTypes)) {
throw new Error(
`${getComponentNameFromFiber(fiber) ||
'Unknown'}.getChildContext(): key "${contextKey}" is not defined in childContextTypes.`,
);
}
}
if (__DEV__) {
const name = getComponentNameFromFiber(fiber) || 'Unknown';
checkPropTypes(childContextTypes, childContext, 'child context', name);
}
return {...parentContext, ...childContext};
}
}
function pushContextProvider(workInProgress: Fiber): boolean {
if (disableLegacyContext) {
return false;
} else {
const instance = workInProgress.stateNode;
// We push the context as early as possible to ensure stack integrity.
// If the instance does not exist yet, we will push null at first,
// and replace it on the stack later when invalidating the context.
const memoizedMergedChildContext =
(instance && instance.__reactInternalMemoizedMergedChildContext) ||
emptyContextObject;
// Remember the parent context so we can merge with it later.
// Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
previousContext = contextStackCursor.current;
push(contextStackCursor, memoizedMergedChildContext, workInProgress);
push(
didPerformWorkStackCursor,
didPerformWorkStackCursor.current,
workInProgress,
);
return true;
}
}
function invalidateContextProvider(
workInProgress: Fiber,
type: any,
didChange: boolean,
): void {
if (disableLegacyContext) {
return;
} else {
const instance = workInProgress.stateNode;
if (!instance) {
throw new Error(
'Expected to have an instance by this point. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
if (didChange) {
// Merge parent and own context.
// Skip this if we're not updating due to sCU.
// This avoids unnecessarily recomputing memoized values.
const mergedContext = processChildContext(
workInProgress,
type,
previousContext,
);
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
// Replace the old (or empty) context with the new one.
// It is important to unwind the context in the reverse order.
pop(didPerformWorkStackCursor, workInProgress);
pop(contextStackCursor, workInProgress);
// Now push the new context and mark that it has changed.
push(contextStackCursor, mergedContext, workInProgress);
push(didPerformWorkStackCursor, didChange, workInProgress);
} else {
pop(didPerformWorkStackCursor, workInProgress);
push(didPerformWorkStackCursor, didChange, workInProgress);
}
}
}
function findCurrentUnmaskedContext(fiber: Fiber): Object {
if (disableLegacyContext) {
return emptyContextObject;
} else {
// Currently this is only used with renderSubtreeIntoContainer; not sure if it
// makes sense elsewhere
if (!isFiberMounted(fiber) || fiber.tag !== ClassComponent) {
throw new Error(
'Expected subtree parent to be a mounted class component. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
let node: Fiber = fiber;
do {
switch (node.tag) {
case HostRoot:
return node.stateNode.context;
case ClassComponent: {
const Component = node.type;
if (isContextProvider(Component)) {
return node.stateNode.__reactInternalMemoizedMergedChildContext;
}
break;
}
}
// $FlowFixMe[incompatible-type] we bail out when we get a null
node = node.return;
} while (node !== null);
throw new Error(
'Found unexpected detached subtree parent. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
}
export {
getUnmaskedContext,
cacheContext,
getMaskedContext,
hasContextChanged,
popContext,
popTopLevelContextObject,
pushTopLevelContextObject,
processChildContext,
isContextProvider,
pushContextProvider,
invalidateContextProvider,
findCurrentUnmaskedContext,
};

View File

@ -1,540 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Lane, Lanes} from './ReactFiberLane.new';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {ReactNodeList, Wakeable} from 'shared/ReactTypes';
import type {EventPriority} from './ReactEventPriorities.new';
// import type {DevToolsProfilingHooks} from 'react-devtools-shared/src/backend/types';
// TODO: This import doesn't work because the DevTools depend on the DOM version of React
// and to properly type check against DOM React we can't also type check again non-DOM
// React which this hook might be in.
type DevToolsProfilingHooks = any;
import {
getLabelForLane,
TotalLanes,
} from 'react-reconciler/src/ReactFiberLane.new';
import {DidCapture} from './ReactFiberFlags';
import {
consoleManagedByDevToolsDuringStrictMode,
enableProfilerTimer,
enableSchedulingProfiler,
} from 'shared/ReactFeatureFlags';
import {
DiscreteEventPriority,
ContinuousEventPriority,
DefaultEventPriority,
IdleEventPriority,
} from './ReactEventPriorities.new';
import {
ImmediatePriority as ImmediateSchedulerPriority,
UserBlockingPriority as UserBlockingSchedulerPriority,
NormalPriority as NormalSchedulerPriority,
IdlePriority as IdleSchedulerPriority,
unstable_yieldValue,
unstable_setDisableYieldValue,
} from './Scheduler';
import {setSuppressWarning} from 'shared/consoleWithStackDev';
import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void;
let rendererID = null;
let injectedHook = null;
let injectedProfilingHooks: DevToolsProfilingHooks | null = null;
let hasLoggedError = false;
export const isDevToolsPresent =
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined';
export function injectInternals(internals: Object): boolean {
if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') {
// No DevTools
return false;
}
const hook = __REACT_DEVTOOLS_GLOBAL_HOOK__;
if (hook.isDisabled) {
// This isn't a real property on the hook, but it can be set to opt out
// of DevTools integration and associated warnings and logs.
// https://github.com/facebook/react/issues/3877
return true;
}
if (!hook.supportsFiber) {
if (__DEV__) {
console.error(
'The installed version of React DevTools is too old and will not work ' +
'with the current version of React. Please update React DevTools. ' +
'https://reactjs.org/link/react-devtools',
);
}
// DevTools exists, even though it doesn't support Fiber.
return true;
}
try {
if (enableSchedulingProfiler) {
// Conditionally inject these hooks only if Timeline profiler is supported by this build.
// This gives DevTools a way to feature detect that isn't tied to version number
// (since profiling and timeline are controlled by different feature flags).
internals = {
...internals,
getLaneLabelMap,
injectProfilingHooks,
};
}
rendererID = hook.inject(internals);
// We have successfully injected, so now it is safe to set up hooks.
injectedHook = hook;
} catch (err) {
// Catch all errors because it is unsafe to throw during initialization.
if (__DEV__) {
console.error('React instrumentation encountered an error: %s.', err);
}
}
if (hook.checkDCE) {
// This is the real DevTools.
return true;
} else {
// This is likely a hook installed by Fast Refresh runtime.
return false;
}
}
export function onScheduleRoot(root: FiberRoot, children: ReactNodeList) {
if (__DEV__) {
if (
injectedHook &&
typeof injectedHook.onScheduleFiberRoot === 'function'
) {
try {
injectedHook.onScheduleFiberRoot(rendererID, root, children);
} catch (err) {
if (__DEV__ && !hasLoggedError) {
hasLoggedError = true;
console.error('React instrumentation encountered an error: %s', err);
}
}
}
}
}
export function onCommitRoot(root: FiberRoot, eventPriority: EventPriority) {
if (injectedHook && typeof injectedHook.onCommitFiberRoot === 'function') {
try {
const didError = (root.current.flags & DidCapture) === DidCapture;
if (enableProfilerTimer) {
let schedulerPriority;
switch (eventPriority) {
case DiscreteEventPriority:
schedulerPriority = ImmediateSchedulerPriority;
break;
case ContinuousEventPriority:
schedulerPriority = UserBlockingSchedulerPriority;
break;
case DefaultEventPriority:
schedulerPriority = NormalSchedulerPriority;
break;
case IdleEventPriority:
schedulerPriority = IdleSchedulerPriority;
break;
default:
schedulerPriority = NormalSchedulerPriority;
break;
}
injectedHook.onCommitFiberRoot(
rendererID,
root,
schedulerPriority,
didError,
);
} else {
injectedHook.onCommitFiberRoot(rendererID, root, undefined, didError);
}
} catch (err) {
if (__DEV__) {
if (!hasLoggedError) {
hasLoggedError = true;
console.error('React instrumentation encountered an error: %s', err);
}
}
}
}
}
export function onPostCommitRoot(root: FiberRoot) {
if (
injectedHook &&
typeof injectedHook.onPostCommitFiberRoot === 'function'
) {
try {
injectedHook.onPostCommitFiberRoot(rendererID, root);
} catch (err) {
if (__DEV__) {
if (!hasLoggedError) {
hasLoggedError = true;
console.error('React instrumentation encountered an error: %s', err);
}
}
}
}
}
export function onCommitUnmount(fiber: Fiber) {
if (injectedHook && typeof injectedHook.onCommitFiberUnmount === 'function') {
try {
injectedHook.onCommitFiberUnmount(rendererID, fiber);
} catch (err) {
if (__DEV__) {
if (!hasLoggedError) {
hasLoggedError = true;
console.error('React instrumentation encountered an error: %s', err);
}
}
}
}
}
export function setIsStrictModeForDevtools(newIsStrictMode: boolean) {
if (consoleManagedByDevToolsDuringStrictMode) {
if (typeof unstable_yieldValue === 'function') {
// We're in a test because Scheduler.unstable_yieldValue only exists
// in SchedulerMock. To reduce the noise in strict mode tests,
// suppress warnings and disable scheduler yielding during the double render
unstable_setDisableYieldValue(newIsStrictMode);
setSuppressWarning(newIsStrictMode);
}
if (injectedHook && typeof injectedHook.setStrictMode === 'function') {
try {
injectedHook.setStrictMode(rendererID, newIsStrictMode);
} catch (err) {
if (__DEV__) {
if (!hasLoggedError) {
hasLoggedError = true;
console.error(
'React instrumentation encountered an error: %s',
err,
);
}
}
}
}
} else {
if (newIsStrictMode) {
disableLogs();
} else {
reenableLogs();
}
}
}
// Profiler API hooks
function injectProfilingHooks(profilingHooks: DevToolsProfilingHooks): void {
injectedProfilingHooks = profilingHooks;
}
function getLaneLabelMap(): Map<Lane, string> | null {
if (enableSchedulingProfiler) {
const map: Map<Lane, string> = new Map();
let lane = 1;
for (let index = 0; index < TotalLanes; index++) {
const label = ((getLabelForLane(lane): any): string);
map.set(lane, label);
lane *= 2;
}
return map;
} else {
return null;
}
}
export function markCommitStarted(lanes: Lanes): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markCommitStarted === 'function'
) {
injectedProfilingHooks.markCommitStarted(lanes);
}
}
}
export function markCommitStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markCommitStopped === 'function'
) {
injectedProfilingHooks.markCommitStopped();
}
}
}
export function markComponentRenderStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentRenderStarted === 'function'
) {
injectedProfilingHooks.markComponentRenderStarted(fiber);
}
}
}
export function markComponentRenderStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentRenderStopped === 'function'
) {
injectedProfilingHooks.markComponentRenderStopped();
}
}
}
export function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentPassiveEffectMountStarted ===
'function'
) {
injectedProfilingHooks.markComponentPassiveEffectMountStarted(fiber);
}
}
}
export function markComponentPassiveEffectMountStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentPassiveEffectMountStopped ===
'function'
) {
injectedProfilingHooks.markComponentPassiveEffectMountStopped();
}
}
}
export function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentPassiveEffectUnmountStarted ===
'function'
) {
injectedProfilingHooks.markComponentPassiveEffectUnmountStarted(fiber);
}
}
}
export function markComponentPassiveEffectUnmountStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentPassiveEffectUnmountStopped ===
'function'
) {
injectedProfilingHooks.markComponentPassiveEffectUnmountStopped();
}
}
}
export function markComponentLayoutEffectMountStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentLayoutEffectMountStarted ===
'function'
) {
injectedProfilingHooks.markComponentLayoutEffectMountStarted(fiber);
}
}
}
export function markComponentLayoutEffectMountStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentLayoutEffectMountStopped ===
'function'
) {
injectedProfilingHooks.markComponentLayoutEffectMountStopped();
}
}
}
export function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentLayoutEffectUnmountStarted ===
'function'
) {
injectedProfilingHooks.markComponentLayoutEffectUnmountStarted(fiber);
}
}
}
export function markComponentLayoutEffectUnmountStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentLayoutEffectUnmountStopped ===
'function'
) {
injectedProfilingHooks.markComponentLayoutEffectUnmountStopped();
}
}
}
export function markComponentErrored(
fiber: Fiber,
thrownValue: mixed,
lanes: Lanes,
): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentErrored === 'function'
) {
injectedProfilingHooks.markComponentErrored(fiber, thrownValue, lanes);
}
}
}
export function markComponentSuspended(
fiber: Fiber,
wakeable: Wakeable,
lanes: Lanes,
): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markComponentSuspended === 'function'
) {
injectedProfilingHooks.markComponentSuspended(fiber, wakeable, lanes);
}
}
}
export function markLayoutEffectsStarted(lanes: Lanes): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markLayoutEffectsStarted === 'function'
) {
injectedProfilingHooks.markLayoutEffectsStarted(lanes);
}
}
}
export function markLayoutEffectsStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markLayoutEffectsStopped === 'function'
) {
injectedProfilingHooks.markLayoutEffectsStopped();
}
}
}
export function markPassiveEffectsStarted(lanes: Lanes): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markPassiveEffectsStarted === 'function'
) {
injectedProfilingHooks.markPassiveEffectsStarted(lanes);
}
}
}
export function markPassiveEffectsStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markPassiveEffectsStopped === 'function'
) {
injectedProfilingHooks.markPassiveEffectsStopped();
}
}
}
export function markRenderStarted(lanes: Lanes): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markRenderStarted === 'function'
) {
injectedProfilingHooks.markRenderStarted(lanes);
}
}
}
export function markRenderYielded(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markRenderYielded === 'function'
) {
injectedProfilingHooks.markRenderYielded();
}
}
}
export function markRenderStopped(): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markRenderStopped === 'function'
) {
injectedProfilingHooks.markRenderStopped();
}
}
}
export function markRenderScheduled(lane: Lane): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markRenderScheduled === 'function'
) {
injectedProfilingHooks.markRenderScheduled(lane);
}
}
}
export function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markForceUpdateScheduled === 'function'
) {
injectedProfilingHooks.markForceUpdateScheduled(fiber, lane);
}
}
}
export function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
if (enableSchedulingProfiler) {
if (
injectedProfilingHooks !== null &&
typeof injectedProfilingHooks.markStateUpdateScheduled === 'function'
) {
injectedProfilingHooks.markStateUpdateScheduled(fiber, lane);
}
}
}

View File

@ -1,71 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber} from './ReactInternalTypes';
import type {StackCursor} from './ReactFiberStack.new';
import type {Lanes} from './ReactFiberLane.new';
import {createCursor, push, pop} from './ReactFiberStack.new';
import {getRenderLanes, setRenderLanes} from './ReactFiberWorkLoop.new';
import {NoLanes, mergeLanes} from './ReactFiberLane.new';
// TODO: Remove `renderLanes` context in favor of hidden context
type HiddenContext = {
// Represents the lanes that must be included when processing updates in
// order to reveal the hidden content.
// TODO: Remove `subtreeLanes` context from work loop in favor of this one.
baseLanes: number,
...
};
// TODO: This isn't being used yet, but it's intended to replace the
// InvisibleParentContext that is currently managed by SuspenseContext.
export const currentTreeHiddenStackCursor: StackCursor<HiddenContext | null> = createCursor(
null,
);
export const prevRenderLanesStackCursor: StackCursor<Lanes> = createCursor(
NoLanes,
);
export function pushHiddenContext(fiber: Fiber, context: HiddenContext): void {
const prevRenderLanes = getRenderLanes();
push(prevRenderLanesStackCursor, prevRenderLanes, fiber);
push(currentTreeHiddenStackCursor, context, fiber);
// When rendering a subtree that's currently hidden, we must include all
// lanes that would have rendered if the hidden subtree hadn't been deferred.
// That is, in order to reveal content from hidden -> visible, we must commit
// all the updates that we skipped when we originally hid the tree.
setRenderLanes(mergeLanes(prevRenderLanes, context.baseLanes));
}
export function reuseHiddenContextOnStack(fiber: Fiber): void {
// This subtree is not currently hidden, so we don't need to add any lanes
// to the render lanes. But we still need to push something to avoid a
// context mismatch. Reuse the existing context on the stack.
push(prevRenderLanesStackCursor, getRenderLanes(), fiber);
push(
currentTreeHiddenStackCursor,
currentTreeHiddenStackCursor.current,
fiber,
);
}
export function popHiddenContext(fiber: Fiber): void {
// Restore the previous render lanes from the stack
setRenderLanes(prevRenderLanesStackCursor.current);
pop(currentTreeHiddenStackCursor, fiber);
pop(prevRenderLanesStackCursor, fiber);
}
export function isCurrentTreeHidden(): boolean {
return currentTreeHiddenStackCursor.current !== null;
}

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,6 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
enableDebugTracing,
enableSchedulingProfiler,
enableNewReconciler,
enableCache,
enableUseRefAccessWarning,
enableLazyContextPropagation,
@ -2758,8 +2757,6 @@ export const ContextOnlyDispatcher: Dispatcher = {
useMutableSource: throwInvalidHookError,
useSyncExternalStore: throwInvalidHookError,
useId: throwInvalidHookError,
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(ContextOnlyDispatcher: Dispatcher).useCacheRefresh = throwInvalidHookError;
@ -2793,8 +2790,6 @@ const HooksDispatcherOnMount: Dispatcher = {
useMutableSource: mountMutableSource,
useSyncExternalStore: mountSyncExternalStore,
useId: mountId,
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
// $FlowFixMe[escaped-generic] discovered when updating Flow
@ -2828,8 +2823,6 @@ const HooksDispatcherOnUpdate: Dispatcher = {
useMutableSource: updateMutableSource,
useSyncExternalStore: updateSyncExternalStore,
useId: updateId,
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnUpdate: Dispatcher).useCacheRefresh = updateRefresh;
@ -2863,8 +2856,6 @@ const HooksDispatcherOnRerender: Dispatcher = {
useMutableSource: updateMutableSource,
useSyncExternalStore: updateSyncExternalStore,
useId: updateId,
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnRerender: Dispatcher).useCacheRefresh = updateRefresh;
@ -3041,8 +3032,6 @@ if (__DEV__) {
mountHookTypesDev();
return mountId();
},
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnMountInDEV: Dispatcher).useCacheRefresh = function useCacheRefresh() {
@ -3198,8 +3187,6 @@ if (__DEV__) {
updateHookTypesDev();
return mountId();
},
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useCacheRefresh = function useCacheRefresh() {
@ -3355,8 +3342,6 @@ if (__DEV__) {
updateHookTypesDev();
return updateId();
},
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnUpdateInDEV: Dispatcher).useCacheRefresh = function useCacheRefresh() {
@ -3513,8 +3498,6 @@ if (__DEV__) {
updateHookTypesDev();
return updateId();
},
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(HooksDispatcherOnRerenderInDEV: Dispatcher).useCacheRefresh = function useCacheRefresh() {
@ -3687,8 +3670,6 @@ if (__DEV__) {
mountHookTypesDev();
return mountId();
},
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useCacheRefresh = function useCacheRefresh() {
@ -3872,8 +3853,6 @@ if (__DEV__) {
updateHookTypesDev();
return updateId();
},
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useCacheRefresh = function useCacheRefresh() {
@ -4058,8 +4037,6 @@ if (__DEV__) {
updateHookTypesDev();
return updateId();
},
unstable_isNewReconciler: enableNewReconciler,
};
if (enableCache) {
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useCacheRefresh = function useCacheRefresh() {

View File

@ -1,31 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Container, HostContext} from './ReactFiberHostConfig';
import {enableNewReconciler} from 'shared/ReactFeatureFlags';
import {
getCurrentRootHostContainer as getCurrentRootHostContainer_old,
getHostContext as getHostContext_old,
} from './ReactFiberHostContext.old';
import {
getCurrentRootHostContainer as getCurrentRootHostContainer_new,
getHostContext as getHostContext_new,
} from './ReactFiberHostContext.new';
export function getCurrentRootHostContainer(): null | Container {
return enableNewReconciler
? getCurrentRootHostContainer_new()
: getCurrentRootHostContainer_old();
}
export function getHostContext(): HostContext {
return enableNewReconciler ? getHostContext_new() : getHostContext_old();
}

View File

@ -1,109 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber} from './ReactInternalTypes';
import type {StackCursor} from './ReactFiberStack.new';
import type {Container, HostContext} from './ReactFiberHostConfig';
import {getChildHostContext, getRootHostContext} from './ReactFiberHostConfig';
import {createCursor, push, pop} from './ReactFiberStack.new';
const contextStackCursor: StackCursor<HostContext | null> = createCursor(null);
const contextFiberStackCursor: StackCursor<Fiber | null> = createCursor(null);
const rootInstanceStackCursor: StackCursor<Container | null> = createCursor(
null,
);
function requiredContext<Value>(c: Value | null): Value {
if (__DEV__) {
if (c === null) {
console.error(
'Expected host context to exist. This error is likely caused by a bug ' +
'in React. Please file an issue.',
);
}
}
return (c: any);
}
function getCurrentRootHostContainer(): null | Container {
return rootInstanceStackCursor.current;
}
function getRootHostContainer(): Container {
const rootInstance = requiredContext(rootInstanceStackCursor.current);
return rootInstance;
}
function pushHostContainer(fiber: Fiber, nextRootInstance: Container) {
// Push current root instance onto the stack;
// This allows us to reset root when portals are popped.
push(rootInstanceStackCursor, nextRootInstance, fiber);
// Track the context and the Fiber that provided it.
// This enables us to pop only Fibers that provide unique contexts.
push(contextFiberStackCursor, fiber, fiber);
// Finally, we need to push the host context to the stack.
// However, we can't just call getRootHostContext() and push it because
// we'd have a different number of entries on the stack depending on
// whether getRootHostContext() throws somewhere in renderer code or not.
// So we push an empty value first. This lets us safely unwind on errors.
push(contextStackCursor, null, fiber);
const nextRootContext = getRootHostContext(nextRootInstance);
// Now that we know this function doesn't throw, replace it.
pop(contextStackCursor, fiber);
push(contextStackCursor, nextRootContext, fiber);
}
function popHostContainer(fiber: Fiber) {
pop(contextStackCursor, fiber);
pop(contextFiberStackCursor, fiber);
pop(rootInstanceStackCursor, fiber);
}
function getHostContext(): HostContext {
const context = requiredContext(contextStackCursor.current);
return context;
}
function pushHostContext(fiber: Fiber): void {
const context: HostContext = requiredContext(contextStackCursor.current);
const nextContext = getChildHostContext(context, fiber.type);
// Don't push this Fiber's context unless it's unique.
if (context === nextContext) {
return;
}
// Track the context and the Fiber that provided it.
// This enables us to pop only Fibers that provide unique contexts.
push(contextFiberStackCursor, fiber, fiber);
push(contextStackCursor, nextContext, fiber);
}
function popHostContext(fiber: Fiber): void {
// Do not pop unless this Fiber provided the current context.
// pushHostContext() only pushes Fibers that provide unique contexts.
if (contextFiberStackCursor.current !== fiber) {
return;
}
pop(contextStackCursor, fiber);
pop(contextFiberStackCursor, fiber);
}
export {
getHostContext,
getCurrentRootHostContainer,
getRootHostContainer,
popHostContainer,
popHostContext,
pushHostContainer,
pushHostContext,
};

View File

@ -1,93 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {ReactElement} from '../../shared/ReactElementType';
import type {Instance} from './ReactFiberHostConfig';
import type {ReactNodeList} from 'shared/ReactTypes';
import {enableNewReconciler} from 'shared/ReactFeatureFlags';
import {
setRefreshHandler as setRefreshHandler_old,
resolveFunctionForHotReloading as resolveFunctionForHotReloading_old,
resolveClassForHotReloading as resolveClassForHotReloading_old,
resolveForwardRefForHotReloading as resolveForwardRefForHotReloading_old,
isCompatibleFamilyForHotReloading as isCompatibleFamilyForHotReloading_old,
markFailedErrorBoundaryForHotReloading as markFailedErrorBoundaryForHotReloading_old,
scheduleRefresh as scheduleRefresh_old,
scheduleRoot as scheduleRoot_old,
findHostInstancesForRefresh as findHostInstancesForRefresh_old,
} from './ReactFiberHotReloading.old';
import {
setRefreshHandler as setRefreshHandler_new,
resolveFunctionForHotReloading as resolveFunctionForHotReloading_new,
resolveClassForHotReloading as resolveClassForHotReloading_new,
resolveForwardRefForHotReloading as resolveForwardRefForHotReloading_new,
isCompatibleFamilyForHotReloading as isCompatibleFamilyForHotReloading_new,
markFailedErrorBoundaryForHotReloading as markFailedErrorBoundaryForHotReloading_new,
scheduleRefresh as scheduleRefresh_new,
scheduleRoot as scheduleRoot_new,
findHostInstancesForRefresh as findHostInstancesForRefresh_new,
} from './ReactFiberHotReloading.new';
export type Family = {
current: any,
};
export type RefreshUpdate = {
staleFamilies: Set<Family>,
updatedFamilies: Set<Family>,
};
// Resolves type to a family.
export type RefreshHandler = any => Family | void;
// Used by React Refresh runtime through DevTools Global Hook.
export type SetRefreshHandler = (handler: RefreshHandler | null) => void;
export type ScheduleRefresh = (root: FiberRoot, update: RefreshUpdate) => void;
export type ScheduleRoot = (root: FiberRoot, element: ReactNodeList) => void;
export type FindHostInstancesForRefresh = (
root: FiberRoot,
families: Array<Family>,
) => Set<Instance>;
export const setRefreshHandler: (
handler: RefreshHandler | null,
) => void = enableNewReconciler ? setRefreshHandler_new : setRefreshHandler_old;
export const resolveFunctionForHotReloading: typeof resolveFunctionForHotReloading_new = enableNewReconciler
? resolveFunctionForHotReloading_new
: resolveFunctionForHotReloading_old;
export const resolveClassForHotReloading: typeof resolveClassForHotReloading_new = enableNewReconciler
? resolveClassForHotReloading_new
: resolveClassForHotReloading_old;
export const resolveForwardRefForHotReloading: typeof resolveForwardRefForHotReloading_new = enableNewReconciler
? resolveForwardRefForHotReloading_new
: resolveForwardRefForHotReloading_old;
export const isCompatibleFamilyForHotReloading: (
fiber: Fiber,
element: ReactElement,
) => boolean = enableNewReconciler
? isCompatibleFamilyForHotReloading_new
: isCompatibleFamilyForHotReloading_old;
export const markFailedErrorBoundaryForHotReloading: (
fiber: Fiber,
) => void = enableNewReconciler
? markFailedErrorBoundaryForHotReloading_new
: markFailedErrorBoundaryForHotReloading_old;
export const scheduleRefresh: ScheduleRefresh = enableNewReconciler
? scheduleRefresh_new
: scheduleRefresh_old;
export const scheduleRoot: ScheduleRoot = enableNewReconciler
? scheduleRoot_new
: scheduleRoot_old;
export const findHostInstancesForRefresh: FindHostInstancesForRefresh = enableNewReconciler
? findHostInstancesForRefresh_new
: findHostInstancesForRefresh_old;

View File

@ -1,487 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
/* eslint-disable react-internal/prod-error-codes */
import type {ReactElement} from 'shared/ReactElementType';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {Instance} from './ReactFiberHostConfig';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {
Family,
FindHostInstancesForRefresh,
RefreshHandler,
RefreshUpdate,
ScheduleRefresh,
ScheduleRoot,
} from './ReactFiberHotReloading';
import {enableHostSingletons, enableFloat} from 'shared/ReactFeatureFlags';
import {
flushSync,
scheduleUpdateOnFiber,
flushPassiveEffects,
} from './ReactFiberWorkLoop.new';
import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates.new';
import {updateContainer} from './ReactFiberReconciler.new';
import {emptyContextObject} from './ReactFiberContext.new';
import {SyncLane, NoTimestamp} from './ReactFiberLane.new';
import {
ClassComponent,
FunctionComponent,
ForwardRef,
HostComponent,
HostResource,
HostSingleton,
HostPortal,
HostRoot,
MemoComponent,
SimpleMemoComponent,
} from './ReactWorkTags';
import {
REACT_FORWARD_REF_TYPE,
REACT_MEMO_TYPE,
REACT_LAZY_TYPE,
} from 'shared/ReactSymbols';
import {supportsSingletons} from './ReactFiberHostConfig';
let resolveFamily: RefreshHandler | null = null;
let failedBoundaries: WeakSet<Fiber> | null = null;
export const setRefreshHandler = (handler: RefreshHandler | null): void => {
if (__DEV__) {
resolveFamily = handler;
}
};
export function resolveFunctionForHotReloading(type: any): any {
if (__DEV__) {
if (resolveFamily === null) {
// Hot reloading is disabled.
return type;
}
const family = resolveFamily(type);
if (family === undefined) {
return type;
}
// Use the latest known implementation.
return family.current;
} else {
return type;
}
}
export function resolveClassForHotReloading(type: any): any {
// No implementation differences.
return resolveFunctionForHotReloading(type);
}
export function resolveForwardRefForHotReloading(type: any): any {
if (__DEV__) {
if (resolveFamily === null) {
// Hot reloading is disabled.
return type;
}
const family = resolveFamily(type);
if (family === undefined) {
// Check if we're dealing with a real forwardRef. Don't want to crash early.
if (
type !== null &&
type !== undefined &&
typeof type.render === 'function'
) {
// ForwardRef is special because its resolved .type is an object,
// but it's possible that we only have its inner render function in the map.
// If that inner render function is different, we'll build a new forwardRef type.
const currentRender = resolveFunctionForHotReloading(type.render);
if (type.render !== currentRender) {
const syntheticType = {
$$typeof: REACT_FORWARD_REF_TYPE,
render: currentRender,
};
if (type.displayName !== undefined) {
(syntheticType: any).displayName = type.displayName;
}
return syntheticType;
}
}
return type;
}
// Use the latest known implementation.
return family.current;
} else {
return type;
}
}
export function isCompatibleFamilyForHotReloading(
fiber: Fiber,
element: ReactElement,
): boolean {
if (__DEV__) {
if (resolveFamily === null) {
// Hot reloading is disabled.
return false;
}
const prevType = fiber.elementType;
const nextType = element.type;
// If we got here, we know types aren't === equal.
let needsCompareFamilies = false;
const $$typeofNextType =
typeof nextType === 'object' && nextType !== null
? nextType.$$typeof
: null;
switch (fiber.tag) {
case ClassComponent: {
if (typeof nextType === 'function') {
needsCompareFamilies = true;
}
break;
}
case FunctionComponent: {
if (typeof nextType === 'function') {
needsCompareFamilies = true;
} else if ($$typeofNextType === REACT_LAZY_TYPE) {
// We don't know the inner type yet.
// We're going to assume that the lazy inner type is stable,
// and so it is sufficient to avoid reconciling it away.
// We're not going to unwrap or actually use the new lazy type.
needsCompareFamilies = true;
}
break;
}
case ForwardRef: {
if ($$typeofNextType === REACT_FORWARD_REF_TYPE) {
needsCompareFamilies = true;
} else if ($$typeofNextType === REACT_LAZY_TYPE) {
needsCompareFamilies = true;
}
break;
}
case MemoComponent:
case SimpleMemoComponent: {
if ($$typeofNextType === REACT_MEMO_TYPE) {
// TODO: if it was but can no longer be simple,
// we shouldn't set this.
needsCompareFamilies = true;
} else if ($$typeofNextType === REACT_LAZY_TYPE) {
needsCompareFamilies = true;
}
break;
}
default:
return false;
}
// Check if both types have a family and it's the same one.
if (needsCompareFamilies) {
// Note: memo() and forwardRef() we'll compare outer rather than inner type.
// This means both of them need to be registered to preserve state.
// If we unwrapped and compared the inner types for wrappers instead,
// then we would risk falsely saying two separate memo(Foo)
// calls are equivalent because they wrap the same Foo function.
const prevFamily = resolveFamily(prevType);
// $FlowFixMe[not-a-function] found when upgrading Flow
if (prevFamily !== undefined && prevFamily === resolveFamily(nextType)) {
return true;
}
}
return false;
} else {
return false;
}
}
export function markFailedErrorBoundaryForHotReloading(fiber: Fiber) {
if (__DEV__) {
if (resolveFamily === null) {
// Hot reloading is disabled.
return;
}
if (typeof WeakSet !== 'function') {
return;
}
if (failedBoundaries === null) {
failedBoundaries = new WeakSet();
}
failedBoundaries.add(fiber);
}
}
export const scheduleRefresh: ScheduleRefresh = (
root: FiberRoot,
update: RefreshUpdate,
): void => {
if (__DEV__) {
if (resolveFamily === null) {
// Hot reloading is disabled.
return;
}
const {staleFamilies, updatedFamilies} = update;
flushPassiveEffects();
flushSync(() => {
scheduleFibersWithFamiliesRecursively(
root.current,
updatedFamilies,
staleFamilies,
);
});
}
};
export const scheduleRoot: ScheduleRoot = (
root: FiberRoot,
element: ReactNodeList,
): void => {
if (__DEV__) {
if (root.context !== emptyContextObject) {
// Super edge case: root has a legacy _renderSubtree context
// but we don't know the parentComponent so we can't pass it.
// Just ignore. We'll delete this with _renderSubtree code path later.
return;
}
flushPassiveEffects();
flushSync(() => {
updateContainer(element, root, null, null);
});
}
};
function scheduleFibersWithFamiliesRecursively(
fiber: Fiber,
updatedFamilies: Set<Family>,
staleFamilies: Set<Family>,
) {
if (__DEV__) {
const {alternate, child, sibling, tag, type} = fiber;
let candidateType = null;
switch (tag) {
case FunctionComponent:
case SimpleMemoComponent:
case ClassComponent:
candidateType = type;
break;
case ForwardRef:
candidateType = type.render;
break;
default:
break;
}
if (resolveFamily === null) {
throw new Error('Expected resolveFamily to be set during hot reload.');
}
let needsRender = false;
let needsRemount = false;
if (candidateType !== null) {
const family = resolveFamily(candidateType);
if (family !== undefined) {
if (staleFamilies.has(family)) {
needsRemount = true;
} else if (updatedFamilies.has(family)) {
if (tag === ClassComponent) {
needsRemount = true;
} else {
needsRender = true;
}
}
}
}
if (failedBoundaries !== null) {
if (
failedBoundaries.has(fiber) ||
// $FlowFixMe[incompatible-use] found when upgrading Flow
(alternate !== null && failedBoundaries.has(alternate))
) {
needsRemount = true;
}
}
if (needsRemount) {
fiber._debugNeedsRemount = true;
}
if (needsRemount || needsRender) {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
}
}
if (child !== null && !needsRemount) {
scheduleFibersWithFamiliesRecursively(
child,
updatedFamilies,
staleFamilies,
);
}
if (sibling !== null) {
scheduleFibersWithFamiliesRecursively(
sibling,
updatedFamilies,
staleFamilies,
);
}
}
}
export const findHostInstancesForRefresh: FindHostInstancesForRefresh = (
root: FiberRoot,
families: Array<Family>,
): Set<Instance> => {
if (__DEV__) {
const hostInstances = new Set();
const types = new Set(families.map(family => family.current));
findHostInstancesForMatchingFibersRecursively(
root.current,
types,
hostInstances,
);
return hostInstances;
} else {
throw new Error(
'Did not expect findHostInstancesForRefresh to be called in production.',
);
}
};
function findHostInstancesForMatchingFibersRecursively(
fiber: Fiber,
types: Set<any>,
hostInstances: Set<Instance>,
) {
if (__DEV__) {
const {child, sibling, tag, type} = fiber;
let candidateType = null;
switch (tag) {
case FunctionComponent:
case SimpleMemoComponent:
case ClassComponent:
candidateType = type;
break;
case ForwardRef:
candidateType = type.render;
break;
default:
break;
}
let didMatch = false;
if (candidateType !== null) {
if (types.has(candidateType)) {
didMatch = true;
}
}
if (didMatch) {
// We have a match. This only drills down to the closest host components.
// There's no need to search deeper because for the purpose of giving
// visual feedback, "flashing" outermost parent rectangles is sufficient.
findHostInstancesForFiberShallowly(fiber, hostInstances);
} else {
// If there's no match, maybe there will be one further down in the child tree.
if (child !== null) {
findHostInstancesForMatchingFibersRecursively(
child,
types,
hostInstances,
);
}
}
if (sibling !== null) {
findHostInstancesForMatchingFibersRecursively(
sibling,
types,
hostInstances,
);
}
}
}
function findHostInstancesForFiberShallowly(
fiber: Fiber,
hostInstances: Set<Instance>,
): void {
if (__DEV__) {
const foundHostInstances = findChildHostInstancesForFiberShallowly(
fiber,
hostInstances,
);
if (foundHostInstances) {
return;
}
// If we didn't find any host children, fallback to closest host parent.
let node = fiber;
while (true) {
switch (node.tag) {
case HostSingleton:
case HostComponent:
hostInstances.add(node.stateNode);
return;
case HostPortal:
hostInstances.add(node.stateNode.containerInfo);
return;
case HostRoot:
hostInstances.add(node.stateNode.containerInfo);
return;
}
if (node.return === null) {
throw new Error('Expected to reach root first.');
}
node = node.return;
}
}
}
function findChildHostInstancesForFiberShallowly(
fiber: Fiber,
hostInstances: Set<Instance>,
): boolean {
if (__DEV__) {
let node: Fiber = fiber;
let foundHostInstances = false;
while (true) {
if (
node.tag === HostComponent ||
(enableFloat ? node.tag === HostResource : false) ||
(enableHostSingletons && supportsSingletons
? node.tag === HostSingleton
: false)
) {
// We got a match.
foundHostInstances = true;
hostInstances.add(node.stateNode);
// There may still be more, so keep searching.
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === fiber) {
return foundHostInstances;
}
while (node.sibling === null) {
if (node.return === null || node.return === fiber) {
return foundHostInstances;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
return false;
}

View File

@ -14,15 +14,6 @@ import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {Instance} from './ReactFiberHostConfig';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {
Family,
FindHostInstancesForRefresh,
RefreshHandler,
RefreshUpdate,
ScheduleRefresh,
ScheduleRoot,
} from './ReactFiberHotReloading';
import {enableHostSingletons, enableFloat} from 'shared/ReactFeatureFlags';
import {
flushSync,
@ -52,6 +43,27 @@ import {
} from 'shared/ReactSymbols';
import {supportsSingletons} from './ReactFiberHostConfig';
export type Family = {
current: any,
};
export type RefreshUpdate = {
staleFamilies: Set<Family>,
updatedFamilies: Set<Family>,
};
// Resolves type to a family.
type RefreshHandler = any => Family | void;
// Used by React Refresh runtime through DevTools Global Hook.
export type SetRefreshHandler = (handler: RefreshHandler | null) => void;
export type ScheduleRefresh = (root: FiberRoot, update: RefreshUpdate) => void;
export type ScheduleRoot = (root: FiberRoot, element: ReactNodeList) => void;
export type FindHostInstancesForRefresh = (
root: FiberRoot,
families: Array<Family>,
) => Set<Instance>;
let resolveFamily: RefreshHandler | null = null;
let failedBoundaries: WeakSet<Fiber> | null = null;

View File

@ -1,771 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber} from './ReactInternalTypes';
import {NoMode, ConcurrentMode} from './ReactTypeOfMode';
import type {
Instance,
TextInstance,
HydratableInstance,
SuspenseInstance,
Container,
HostContext,
} from './ReactFiberHostConfig';
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
import type {TreeContext} from './ReactFiberTreeContext.new';
import type {CapturedValue} from './ReactCapturedValue';
import {
HostComponent,
HostSingleton,
HostText,
HostRoot,
SuspenseComponent,
} from './ReactWorkTags';
import {
ChildDeletion,
Placement,
Hydrating,
NoFlags,
DidCapture,
} from './ReactFiberFlags';
import {enableHostSingletons, enableFloat} from 'shared/ReactFeatureFlags';
import {
createFiberFromHostInstanceForDeletion,
createFiberFromDehydratedFragment,
} from './ReactFiber.new';
import {
shouldSetTextContent,
supportsHydration,
supportsSingletons,
isHydratable,
canHydrateInstance,
canHydrateTextInstance,
canHydrateSuspenseInstance,
getNextHydratableSibling,
getFirstHydratableChild,
getFirstHydratableChildWithinContainer,
getFirstHydratableChildWithinSuspenseInstance,
hydrateInstance,
hydrateTextInstance,
hydrateSuspenseInstance,
getNextHydratableInstanceAfterSuspenseInstance,
shouldDeleteUnhydratedTailInstances,
didNotMatchHydratedContainerTextInstance,
didNotMatchHydratedTextInstance,
didNotHydrateInstanceWithinContainer,
didNotHydrateInstanceWithinSuspenseInstance,
didNotHydrateInstance,
didNotFindHydratableInstanceWithinContainer,
didNotFindHydratableTextInstanceWithinContainer,
didNotFindHydratableSuspenseInstanceWithinContainer,
didNotFindHydratableInstanceWithinSuspenseInstance,
didNotFindHydratableTextInstanceWithinSuspenseInstance,
didNotFindHydratableSuspenseInstanceWithinSuspenseInstance,
didNotFindHydratableInstance,
didNotFindHydratableTextInstance,
didNotFindHydratableSuspenseInstance,
resolveSingletonInstance,
} from './ReactFiberHostConfig';
import {OffscreenLane} from './ReactFiberLane.new';
import {
getSuspendedTreeContext,
restoreSuspendedTreeContext,
} from './ReactFiberTreeContext.new';
import {queueRecoverableErrors} from './ReactFiberWorkLoop.new';
import {
getRootHostContainer,
getHostContext,
} from './ReactFiberHostContext.new';
// The deepest Fiber on the stack involved in a hydration context.
// This may have been an insertion or a hydration.
let hydrationParentFiber: null | Fiber = null;
let nextHydratableInstance: null | HydratableInstance = null;
let isHydrating: boolean = false;
// This flag allows for warning supression when we expect there to be mismatches
// due to earlier mismatches or a suspended fiber.
let didSuspendOrErrorDEV: boolean = false;
// Hydration errors that were thrown inside this boundary
let hydrationErrors: Array<CapturedValue<mixed>> | null = null;
function warnIfHydrating() {
if (__DEV__) {
if (isHydrating) {
console.error(
'We should not be hydrating here. This is a bug in React. Please file a bug.',
);
}
}
}
export function markDidThrowWhileHydratingDEV() {
if (__DEV__) {
didSuspendOrErrorDEV = true;
}
}
export function didSuspendOrErrorWhileHydratingDEV(): boolean {
if (__DEV__) {
return didSuspendOrErrorDEV;
}
return false;
}
function enterHydrationState(fiber: Fiber): boolean {
if (!supportsHydration) {
return false;
}
const parentInstance: Container = fiber.stateNode.containerInfo;
nextHydratableInstance = getFirstHydratableChildWithinContainer(
parentInstance,
);
hydrationParentFiber = fiber;
isHydrating = true;
hydrationErrors = null;
didSuspendOrErrorDEV = false;
return true;
}
function reenterHydrationStateFromDehydratedSuspenseInstance(
fiber: Fiber,
suspenseInstance: SuspenseInstance,
treeContext: TreeContext | null,
): boolean {
if (!supportsHydration) {
return false;
}
nextHydratableInstance = getFirstHydratableChildWithinSuspenseInstance(
suspenseInstance,
);
hydrationParentFiber = fiber;
isHydrating = true;
hydrationErrors = null;
didSuspendOrErrorDEV = false;
if (treeContext !== null) {
restoreSuspendedTreeContext(fiber, treeContext);
}
return true;
}
function warnUnhydratedInstance(
returnFiber: Fiber,
instance: HydratableInstance,
) {
if (__DEV__) {
switch (returnFiber.tag) {
case HostRoot: {
didNotHydrateInstanceWithinContainer(
returnFiber.stateNode.containerInfo,
instance,
);
break;
}
case HostSingleton:
case HostComponent: {
const isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode;
didNotHydrateInstance(
returnFiber.type,
returnFiber.memoizedProps,
returnFiber.stateNode,
instance,
// TODO: Delete this argument when we remove the legacy root API.
isConcurrentMode,
);
break;
}
case SuspenseComponent: {
const suspenseState: SuspenseState = returnFiber.memoizedState;
if (suspenseState.dehydrated !== null)
didNotHydrateInstanceWithinSuspenseInstance(
suspenseState.dehydrated,
instance,
);
break;
}
}
}
}
function deleteHydratableInstance(
returnFiber: Fiber,
instance: HydratableInstance,
) {
warnUnhydratedInstance(returnFiber, instance);
const childToDelete = createFiberFromHostInstanceForDeletion();
childToDelete.stateNode = instance;
childToDelete.return = returnFiber;
const deletions = returnFiber.deletions;
if (deletions === null) {
returnFiber.deletions = [childToDelete];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(childToDelete);
}
}
function warnNonhydratedInstance(returnFiber: Fiber, fiber: Fiber) {
if (__DEV__) {
if (didSuspendOrErrorDEV) {
// Inside a boundary that already suspended. We're currently rendering the
// siblings of a suspended node. The mismatch may be due to the missing
// data, so it's probably a false positive.
return;
}
switch (returnFiber.tag) {
case HostRoot: {
const parentContainer = returnFiber.stateNode.containerInfo;
switch (fiber.tag) {
case HostSingleton:
case HostComponent:
const type = fiber.type;
const props = fiber.pendingProps;
didNotFindHydratableInstanceWithinContainer(
parentContainer,
type,
props,
);
break;
case HostText:
const text = fiber.pendingProps;
didNotFindHydratableTextInstanceWithinContainer(
parentContainer,
text,
);
break;
case SuspenseComponent:
didNotFindHydratableSuspenseInstanceWithinContainer(
parentContainer,
);
break;
}
break;
}
case HostSingleton:
case HostComponent: {
const parentType = returnFiber.type;
const parentProps = returnFiber.memoizedProps;
const parentInstance = returnFiber.stateNode;
switch (fiber.tag) {
case HostSingleton:
case HostComponent: {
const type = fiber.type;
const props = fiber.pendingProps;
const isConcurrentMode =
(returnFiber.mode & ConcurrentMode) !== NoMode;
didNotFindHydratableInstance(
parentType,
parentProps,
parentInstance,
type,
props,
// TODO: Delete this argument when we remove the legacy root API.
isConcurrentMode,
);
break;
}
case HostText: {
const text = fiber.pendingProps;
const isConcurrentMode =
(returnFiber.mode & ConcurrentMode) !== NoMode;
didNotFindHydratableTextInstance(
parentType,
parentProps,
parentInstance,
text,
// TODO: Delete this argument when we remove the legacy root API.
isConcurrentMode,
);
break;
}
case SuspenseComponent: {
didNotFindHydratableSuspenseInstance(
parentType,
parentProps,
parentInstance,
);
break;
}
}
break;
}
case SuspenseComponent: {
const suspenseState: SuspenseState = returnFiber.memoizedState;
const parentInstance = suspenseState.dehydrated;
if (parentInstance !== null)
switch (fiber.tag) {
case HostSingleton:
case HostComponent:
const type = fiber.type;
const props = fiber.pendingProps;
didNotFindHydratableInstanceWithinSuspenseInstance(
parentInstance,
type,
props,
);
break;
case HostText:
const text = fiber.pendingProps;
didNotFindHydratableTextInstanceWithinSuspenseInstance(
parentInstance,
text,
);
break;
case SuspenseComponent:
didNotFindHydratableSuspenseInstanceWithinSuspenseInstance(
parentInstance,
);
break;
}
break;
}
default:
return;
}
}
}
function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) {
fiber.flags = (fiber.flags & ~Hydrating) | Placement;
warnNonhydratedInstance(returnFiber, fiber);
}
function tryHydrate(fiber, nextInstance) {
switch (fiber.tag) {
// HostSingleton is intentionally omitted. the hydration pathway for singletons is non-fallible
// you can find it inlined in claimHydratableSingleton
case HostComponent: {
const type = fiber.type;
const props = fiber.pendingProps;
const instance = canHydrateInstance(nextInstance, type, props);
if (instance !== null) {
fiber.stateNode = (instance: Instance);
hydrationParentFiber = fiber;
nextHydratableInstance = getFirstHydratableChild(instance);
return true;
}
return false;
}
case HostText: {
const text = fiber.pendingProps;
const textInstance = canHydrateTextInstance(nextInstance, text);
if (textInstance !== null) {
fiber.stateNode = (textInstance: TextInstance);
hydrationParentFiber = fiber;
// Text Instances don't have children so there's nothing to hydrate.
nextHydratableInstance = null;
return true;
}
return false;
}
case SuspenseComponent: {
const suspenseInstance: null | SuspenseInstance = canHydrateSuspenseInstance(
nextInstance,
);
if (suspenseInstance !== null) {
const suspenseState: SuspenseState = {
dehydrated: suspenseInstance,
treeContext: getSuspendedTreeContext(),
retryLane: OffscreenLane,
};
fiber.memoizedState = suspenseState;
// Store the dehydrated fragment as a child fiber.
// This simplifies the code for getHostSibling and deleting nodes,
// since it doesn't have to consider all Suspense boundaries and
// check if they're dehydrated ones or not.
const dehydratedFragment = createFiberFromDehydratedFragment(
suspenseInstance,
);
dehydratedFragment.return = fiber;
fiber.child = dehydratedFragment;
hydrationParentFiber = fiber;
// While a Suspense Instance does have children, we won't step into
// it during the first pass. Instead, we'll reenter it later.
nextHydratableInstance = null;
return true;
}
return false;
}
default:
return false;
}
}
function shouldClientRenderOnMismatch(fiber: Fiber) {
return (
(fiber.mode & ConcurrentMode) !== NoMode &&
(fiber.flags & DidCapture) === NoFlags
);
}
function throwOnHydrationMismatch(fiber: Fiber) {
throw new Error(
'Hydration failed because the initial UI does not match what was ' +
'rendered on the server.',
);
}
function claimHydratableSingleton(fiber: Fiber): void {
if (enableHostSingletons && supportsSingletons) {
if (!isHydrating) {
return;
}
const currentRootContainer = getRootHostContainer();
const currentHostContext = getHostContext();
const instance = (fiber.stateNode = resolveSingletonInstance(
fiber.type,
fiber.pendingProps,
currentRootContainer,
currentHostContext,
false,
));
hydrationParentFiber = fiber;
nextHydratableInstance = getFirstHydratableChild(instance);
}
}
function tryToClaimNextHydratableInstance(fiber: Fiber): void {
if (!isHydrating) {
return;
}
if (enableFloat && !isHydratable(fiber.type, fiber.pendingProps)) {
// This fiber never hydrates from the DOM and always does an insert
fiber.flags = (fiber.flags & ~Hydrating) | Placement;
isHydrating = false;
hydrationParentFiber = fiber;
return;
}
let nextInstance = nextHydratableInstance;
if (!nextInstance) {
if (shouldClientRenderOnMismatch(fiber)) {
warnNonhydratedInstance((hydrationParentFiber: any), fiber);
throwOnHydrationMismatch(fiber);
}
// Nothing to hydrate. Make it an insertion.
insertNonHydratedInstance((hydrationParentFiber: any), fiber);
isHydrating = false;
hydrationParentFiber = fiber;
return;
}
const firstAttemptedInstance = nextInstance;
if (!tryHydrate(fiber, nextInstance)) {
if (shouldClientRenderOnMismatch(fiber)) {
warnNonhydratedInstance((hydrationParentFiber: any), fiber);
throwOnHydrationMismatch(fiber);
}
// If we can't hydrate this instance let's try the next one.
// We use this as a heuristic. It's based on intuition and not data so it
// might be flawed or unnecessary.
nextInstance = getNextHydratableSibling(firstAttemptedInstance);
const prevHydrationParentFiber: Fiber = (hydrationParentFiber: any);
if (!nextInstance || !tryHydrate(fiber, nextInstance)) {
// Nothing to hydrate. Make it an insertion.
insertNonHydratedInstance((hydrationParentFiber: any), fiber);
isHydrating = false;
hydrationParentFiber = fiber;
return;
}
// We matched the next one, we'll now assume that the first one was
// superfluous and we'll delete it. Since we can't eagerly delete it
// we'll have to schedule a deletion. To do that, this node needs a dummy
// fiber associated with it.
deleteHydratableInstance(prevHydrationParentFiber, firstAttemptedInstance);
}
}
function prepareToHydrateHostInstance(
fiber: Fiber,
hostContext: HostContext,
): boolean {
if (!supportsHydration) {
throw new Error(
'Expected prepareToHydrateHostInstance() to never be called. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
const instance: Instance = fiber.stateNode;
const shouldWarnIfMismatchDev = !didSuspendOrErrorDEV;
const updatePayload = hydrateInstance(
instance,
fiber.type,
fiber.memoizedProps,
hostContext,
fiber,
shouldWarnIfMismatchDev,
);
// TODO: Type this specific to this type of component.
fiber.updateQueue = (updatePayload: any);
// If the update payload indicates that there is a change or if there
// is a new ref we mark this as an update.
if (updatePayload !== null) {
return true;
}
return false;
}
function prepareToHydrateHostTextInstance(fiber: Fiber): boolean {
if (!supportsHydration) {
throw new Error(
'Expected prepareToHydrateHostTextInstance() to never be called. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
const textInstance: TextInstance = fiber.stateNode;
const textContent: string = fiber.memoizedProps;
const shouldWarnIfMismatchDev = !didSuspendOrErrorDEV;
const shouldUpdate = hydrateTextInstance(
textInstance,
textContent,
fiber,
shouldWarnIfMismatchDev,
);
if (shouldUpdate) {
// We assume that prepareToHydrateHostTextInstance is called in a context where the
// hydration parent is the parent host component of this host text.
const returnFiber = hydrationParentFiber;
if (returnFiber !== null) {
switch (returnFiber.tag) {
case HostRoot: {
const parentContainer = returnFiber.stateNode.containerInfo;
const isConcurrentMode =
(returnFiber.mode & ConcurrentMode) !== NoMode;
didNotMatchHydratedContainerTextInstance(
parentContainer,
textInstance,
textContent,
// TODO: Delete this argument when we remove the legacy root API.
isConcurrentMode,
shouldWarnIfMismatchDev,
);
break;
}
case HostSingleton:
case HostComponent: {
const parentType = returnFiber.type;
const parentProps = returnFiber.memoizedProps;
const parentInstance = returnFiber.stateNode;
const isConcurrentMode =
(returnFiber.mode & ConcurrentMode) !== NoMode;
didNotMatchHydratedTextInstance(
parentType,
parentProps,
parentInstance,
textInstance,
textContent,
// TODO: Delete this argument when we remove the legacy root API.
isConcurrentMode,
shouldWarnIfMismatchDev,
);
break;
}
}
}
}
return shouldUpdate;
}
function prepareToHydrateHostSuspenseInstance(fiber: Fiber): void {
if (!supportsHydration) {
throw new Error(
'Expected prepareToHydrateHostSuspenseInstance() to never be called. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
const suspenseState: null | SuspenseState = fiber.memoizedState;
const suspenseInstance: null | SuspenseInstance =
suspenseState !== null ? suspenseState.dehydrated : null;
if (!suspenseInstance) {
throw new Error(
'Expected to have a hydrated suspense instance. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
hydrateSuspenseInstance(suspenseInstance, fiber);
}
function skipPastDehydratedSuspenseInstance(
fiber: Fiber,
): null | HydratableInstance {
if (!supportsHydration) {
throw new Error(
'Expected skipPastDehydratedSuspenseInstance() to never be called. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
const suspenseState: null | SuspenseState = fiber.memoizedState;
const suspenseInstance: null | SuspenseInstance =
suspenseState !== null ? suspenseState.dehydrated : null;
if (!suspenseInstance) {
throw new Error(
'Expected to have a hydrated suspense instance. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
return getNextHydratableInstanceAfterSuspenseInstance(suspenseInstance);
}
function popToNextHostParent(fiber: Fiber): void {
let parent = fiber.return;
while (
parent !== null &&
parent.tag !== HostComponent &&
parent.tag !== HostRoot &&
parent.tag !== SuspenseComponent &&
(!(enableHostSingletons && supportsSingletons)
? true
: parent.tag !== HostSingleton)
) {
parent = parent.return;
}
hydrationParentFiber = parent;
}
function popHydrationState(fiber: Fiber): boolean {
if (!supportsHydration) {
return false;
}
if (fiber !== hydrationParentFiber) {
// We're deeper than the current hydration context, inside an inserted
// tree.
return false;
}
if (!isHydrating) {
// If we're not currently hydrating but we're in a hydration context, then
// we were an insertion and now need to pop up reenter hydration of our
// siblings.
popToNextHostParent(fiber);
isHydrating = true;
return false;
}
let shouldClear = false;
if (enableHostSingletons && supportsSingletons) {
// With float we never clear the Root, or Singleton instances. We also do not clear Instances
// that have singleton text content
if (
fiber.tag !== HostRoot &&
fiber.tag !== HostSingleton &&
!(
fiber.tag === HostComponent &&
shouldSetTextContent(fiber.type, fiber.memoizedProps)
)
) {
shouldClear = true;
}
} else {
// If we have any remaining hydratable nodes, we need to delete them now.
// We only do this deeper than head and body since they tend to have random
// other nodes in them. We also ignore components with pure text content in
// side of them. We also don't delete anything inside the root container.
if (
fiber.tag !== HostRoot &&
(fiber.tag !== HostComponent ||
(shouldDeleteUnhydratedTailInstances(fiber.type) &&
!shouldSetTextContent(fiber.type, fiber.memoizedProps)))
) {
shouldClear = true;
}
}
if (shouldClear) {
let nextInstance = nextHydratableInstance;
if (nextInstance) {
if (shouldClientRenderOnMismatch(fiber)) {
warnIfUnhydratedTailNodes(fiber);
throwOnHydrationMismatch(fiber);
} else {
while (nextInstance) {
deleteHydratableInstance(fiber, nextInstance);
nextInstance = getNextHydratableSibling(nextInstance);
}
}
}
}
popToNextHostParent(fiber);
if (fiber.tag === SuspenseComponent) {
nextHydratableInstance = skipPastDehydratedSuspenseInstance(fiber);
} else {
nextHydratableInstance = hydrationParentFiber
? getNextHydratableSibling(fiber.stateNode)
: null;
}
return true;
}
function hasUnhydratedTailNodes(): boolean {
return isHydrating && nextHydratableInstance !== null;
}
function warnIfUnhydratedTailNodes(fiber: Fiber) {
let nextInstance = nextHydratableInstance;
while (nextInstance) {
warnUnhydratedInstance(fiber, nextInstance);
nextInstance = getNextHydratableSibling(nextInstance);
}
}
function resetHydrationState(): void {
if (!supportsHydration) {
return;
}
hydrationParentFiber = null;
nextHydratableInstance = null;
isHydrating = false;
didSuspendOrErrorDEV = false;
}
export function upgradeHydrationErrorsToRecoverable(): void {
if (hydrationErrors !== null) {
// Successfully completed a forced client render. The errors that occurred
// during the hydration attempt are now recovered. We will log them in
// commit phase, once the entire tree has finished.
queueRecoverableErrors(hydrationErrors);
hydrationErrors = null;
}
}
function getIsHydrating(): boolean {
return isHydrating;
}
export function queueHydrationError(error: CapturedValue<mixed>): void {
if (hydrationErrors === null) {
hydrationErrors = [error];
} else {
hydrationErrors.push(error);
}
}
export {
warnIfHydrating,
enterHydrationState,
getIsHydrating,
reenterHydrationStateFromDehydratedSuspenseInstance,
resetHydrationState,
claimHydratableSingleton,
tryToClaimNextHydratableInstance,
prepareToHydrateHostInstance,
prepareToHydrateHostTextInstance,
prepareToHydrateHostSuspenseInstance,
popHydrationState,
hasUnhydratedTailNodes,
warnIfUnhydratedTailNodes,
};

View File

@ -1,924 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {Transition} from './ReactFiberTracingMarkerComponent.new';
import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates.new';
// TODO: Ideally these types would be opaque but that doesn't work well with
// our reconciler fork infra, since these leak into non-reconciler packages.
export type Lanes = number;
export type Lane = number;
export type LaneMap<T> = Array<T>;
import {
enableSchedulingProfiler,
enableUpdaterTracking,
allowConcurrentByDefault,
enableTransitionTracing,
} from 'shared/ReactFeatureFlags';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
import {ConcurrentUpdatesByDefaultMode, NoMode} from './ReactTypeOfMode';
import {clz32} from './clz32';
// Lane values below should be kept in sync with getLabelForLane(), used by react-devtools-timeline.
// If those values are changed that package should be rebuilt and redeployed.
export const TotalLanes = 31;
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
export const SyncHydrationLane: Lane = /* */ 0b0000000000000000000000000000001;
export const SyncLane: Lane = /* */ 0b0000000000000000000000000000010;
export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;
export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000001000;
export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000010000;
export const DefaultLane: Lane = /* */ 0b0000000000000000000000000100000;
const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000001000000;
const TransitionLanes: Lanes = /* */ 0b0000000011111111111111110000000;
const TransitionLane1: Lane = /* */ 0b0000000000000000000000010000000;
const TransitionLane2: Lane = /* */ 0b0000000000000000000000100000000;
const TransitionLane3: Lane = /* */ 0b0000000000000000000001000000000;
const TransitionLane4: Lane = /* */ 0b0000000000000000000010000000000;
const TransitionLane5: Lane = /* */ 0b0000000000000000000100000000000;
const TransitionLane6: Lane = /* */ 0b0000000000000000001000000000000;
const TransitionLane7: Lane = /* */ 0b0000000000000000010000000000000;
const TransitionLane8: Lane = /* */ 0b0000000000000000100000000000000;
const TransitionLane9: Lane = /* */ 0b0000000000000001000000000000000;
const TransitionLane10: Lane = /* */ 0b0000000000000010000000000000000;
const TransitionLane11: Lane = /* */ 0b0000000000000100000000000000000;
const TransitionLane12: Lane = /* */ 0b0000000000001000000000000000000;
const TransitionLane13: Lane = /* */ 0b0000000000010000000000000000000;
const TransitionLane14: Lane = /* */ 0b0000000000100000000000000000000;
const TransitionLane15: Lane = /* */ 0b0000000001000000000000000000000;
const TransitionLane16: Lane = /* */ 0b0000000010000000000000000000000;
const RetryLanes: Lanes = /* */ 0b0000111100000000000000000000000;
const RetryLane1: Lane = /* */ 0b0000000100000000000000000000000;
const RetryLane2: Lane = /* */ 0b0000001000000000000000000000000;
const RetryLane3: Lane = /* */ 0b0000010000000000000000000000000;
const RetryLane4: Lane = /* */ 0b0000100000000000000000000000000;
export const SomeRetryLane: Lane = RetryLane1;
export const SelectiveHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
const NonIdleLanes: Lanes = /* */ 0b0001111111111111111111111111111;
export const IdleHydrationLane: Lane = /* */ 0b0010000000000000000000000000000;
export const IdleLane: Lane = /* */ 0b0100000000000000000000000000000;
export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
// This function is used for the experimental timeline (react-devtools-timeline)
// It should be kept in sync with the Lanes values above.
export function getLabelForLane(lane: Lane): string | void {
if (enableSchedulingProfiler) {
if (lane & SyncHydrationLane) {
return 'SyncHydrationLane';
}
if (lane & SyncLane) {
return 'Sync';
}
if (lane & InputContinuousHydrationLane) {
return 'InputContinuousHydration';
}
if (lane & InputContinuousLane) {
return 'InputContinuous';
}
if (lane & DefaultHydrationLane) {
return 'DefaultHydration';
}
if (lane & DefaultLane) {
return 'Default';
}
if (lane & TransitionHydrationLane) {
return 'TransitionHydration';
}
if (lane & TransitionLanes) {
return 'Transition';
}
if (lane & RetryLanes) {
return 'Retry';
}
if (lane & SelectiveHydrationLane) {
return 'SelectiveHydration';
}
if (lane & IdleHydrationLane) {
return 'IdleHydration';
}
if (lane & IdleLane) {
return 'Idle';
}
if (lane & OffscreenLane) {
return 'Offscreen';
}
}
}
export const NoTimestamp = -1;
let nextTransitionLane: Lane = TransitionLane1;
let nextRetryLane: Lane = RetryLane1;
function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
switch (getHighestPriorityLane(lanes)) {
case SyncHydrationLane:
return SyncHydrationLane;
case SyncLane:
return SyncLane;
case InputContinuousHydrationLane:
return InputContinuousHydrationLane;
case InputContinuousLane:
return InputContinuousLane;
case DefaultHydrationLane:
return DefaultHydrationLane;
case DefaultLane:
return DefaultLane;
case TransitionHydrationLane:
return TransitionHydrationLane;
case TransitionLane1:
case TransitionLane2:
case TransitionLane3:
case TransitionLane4:
case TransitionLane5:
case TransitionLane6:
case TransitionLane7:
case TransitionLane8:
case TransitionLane9:
case TransitionLane10:
case TransitionLane11:
case TransitionLane12:
case TransitionLane13:
case TransitionLane14:
case TransitionLane15:
case TransitionLane16:
return lanes & TransitionLanes;
case RetryLane1:
case RetryLane2:
case RetryLane3:
case RetryLane4:
return lanes & RetryLanes;
case SelectiveHydrationLane:
return SelectiveHydrationLane;
case IdleHydrationLane:
return IdleHydrationLane;
case IdleLane:
return IdleLane;
case OffscreenLane:
return OffscreenLane;
default:
if (__DEV__) {
console.error(
'Should have found matching lanes. This is a bug in React.',
);
}
// This shouldn't be reachable, but as a fallback, return the entire bitmask.
return lanes;
}
}
export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
// Early bailout if there's no pending work left.
const pendingLanes = root.pendingLanes;
if (pendingLanes === NoLanes) {
return NoLanes;
}
let nextLanes = NoLanes;
const suspendedLanes = root.suspendedLanes;
const pingedLanes = root.pingedLanes;
// Do not work on any idle work until all the non-idle work has finished,
// even if the work is suspended.
const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
if (nonIdlePendingLanes !== NoLanes) {
const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
if (nonIdleUnblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
} else {
const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
if (nonIdlePingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
}
}
} else {
// The only remaining work is Idle.
const unblockedLanes = pendingLanes & ~suspendedLanes;
if (unblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(unblockedLanes);
} else {
if (pingedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(pingedLanes);
}
}
}
if (nextLanes === NoLanes) {
// This should only be reachable if we're suspended
// TODO: Consider warning in this path if a fallback timer is not scheduled.
return NoLanes;
}
// If we're already in the middle of a render, switching lanes will interrupt
// it and we'll lose our progress. We should only do this if the new lanes are
// higher priority.
if (
wipLanes !== NoLanes &&
wipLanes !== nextLanes &&
// If we already suspended with a delay, then interrupting is fine. Don't
// bother waiting until the root is complete.
(wipLanes & suspendedLanes) === NoLanes
) {
const nextLane = getHighestPriorityLane(nextLanes);
const wipLane = getHighestPriorityLane(wipLanes);
if (
// Tests whether the next lane is equal or lower priority than the wip
// one. This works because the bits decrease in priority as you go left.
nextLane >= wipLane ||
// Default priority updates should not interrupt transition updates. The
// only difference between default updates and transition updates is that
// default updates do not support refresh transitions.
(nextLane === DefaultLane && (wipLane & TransitionLanes) !== NoLanes)
) {
// Keep working on the existing in-progress tree. Do not interrupt.
return wipLanes;
}
}
if (
allowConcurrentByDefault &&
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
) {
// Do nothing, use the lanes as they were assigned.
} else if ((nextLanes & InputContinuousLane) !== NoLanes) {
// When updates are sync by default, we entangle continuous priority updates
// and default updates, so they render in the same batch. The only reason
// they use separate lanes is because continuous updates should interrupt
// transitions, but default updates should not.
nextLanes |= pendingLanes & DefaultLane;
}
// Check for entangled lanes and add them to the batch.
//
// A lane is said to be entangled with another when it's not allowed to render
// in a batch that does not also include the other lane. Typically we do this
// when multiple updates have the same source, and we only want to respond to
// the most recent event from that source.
//
// Note that we apply entanglements *after* checking for partial work above.
// This means that if a lane is entangled during an interleaved event while
// it's already rendering, we won't interrupt it. This is intentional, since
// entanglement is usually "best effort": we'll try our best to render the
// lanes in the same batch, but it's not worth throwing out partially
// completed work in order to do it.
// TODO: Reconsider this. The counter-argument is that the partial work
// represents an intermediate state, which we don't want to show to the user.
// And by spending extra time finishing it, we're increasing the amount of
// time it takes to show the final state, which is what they are actually
// waiting for.
//
// For those exceptions where entanglement is semantically important, like
// useMutableSource, we should ensure that there is no partial work at the
// time we apply the entanglement.
const entangledLanes = root.entangledLanes;
if (entangledLanes !== NoLanes) {
const entanglements = root.entanglements;
let lanes = nextLanes & entangledLanes;
while (lanes > 0) {
const index = pickArbitraryLaneIndex(lanes);
const lane = 1 << index;
nextLanes |= entanglements[index];
lanes &= ~lane;
}
}
return nextLanes;
}
export function getMostRecentEventTime(root: FiberRoot, lanes: Lanes): number {
const eventTimes = root.eventTimes;
let mostRecentEventTime = NoTimestamp;
while (lanes > 0) {
const index = pickArbitraryLaneIndex(lanes);
const lane = 1 << index;
const eventTime = eventTimes[index];
if (eventTime > mostRecentEventTime) {
mostRecentEventTime = eventTime;
}
lanes &= ~lane;
}
return mostRecentEventTime;
}
function computeExpirationTime(lane: Lane, currentTime: number) {
switch (lane) {
case SyncHydrationLane:
case SyncLane:
case InputContinuousHydrationLane:
case InputContinuousLane:
// User interactions should expire slightly more quickly.
//
// NOTE: This is set to the corresponding constant as in Scheduler.js.
// When we made it larger, a product metric in www regressed, suggesting
// there's a user interaction that's being starved by a series of
// synchronous updates. If that theory is correct, the proper solution is
// to fix the starvation. However, this scenario supports the idea that
// expiration times are an important safeguard when starvation
// does happen.
return currentTime + 250;
case DefaultHydrationLane:
case DefaultLane:
case TransitionHydrationLane:
case TransitionLane1:
case TransitionLane2:
case TransitionLane3:
case TransitionLane4:
case TransitionLane5:
case TransitionLane6:
case TransitionLane7:
case TransitionLane8:
case TransitionLane9:
case TransitionLane10:
case TransitionLane11:
case TransitionLane12:
case TransitionLane13:
case TransitionLane14:
case TransitionLane15:
case TransitionLane16:
return currentTime + 5000;
case RetryLane1:
case RetryLane2:
case RetryLane3:
case RetryLane4:
// TODO: Retries should be allowed to expire if they are CPU bound for
// too long, but when I made this change it caused a spike in browser
// crashes. There must be some other underlying bug; not super urgent but
// ideally should figure out why and fix it. Unfortunately we don't have
// a repro for the crashes, only detected via production metrics.
return NoTimestamp;
case SelectiveHydrationLane:
case IdleHydrationLane:
case IdleLane:
case OffscreenLane:
// Anything idle priority or lower should never expire.
return NoTimestamp;
default:
if (__DEV__) {
console.error(
'Should have found matching lanes. This is a bug in React.',
);
}
return NoTimestamp;
}
}
export function markStarvedLanesAsExpired(
root: FiberRoot,
currentTime: number,
): void {
// TODO: This gets called every time we yield. We can optimize by storing
// the earliest expiration time on the root. Then use that to quickly bail out
// of this function.
const pendingLanes = root.pendingLanes;
const suspendedLanes = root.suspendedLanes;
const pingedLanes = root.pingedLanes;
const expirationTimes = root.expirationTimes;
// Iterate through the pending lanes and check if we've reached their
// expiration time. If so, we'll assume the update is being starved and mark
// it as expired to force it to finish.
//
// We exclude retry lanes because those must always be time sliced, in order
// to unwrap uncached promises.
// TODO: Write a test for this
let lanes = pendingLanes & ~RetryLanes;
while (lanes > 0) {
const index = pickArbitraryLaneIndex(lanes);
const lane = 1 << index;
const expirationTime = expirationTimes[index];
if (expirationTime === NoTimestamp) {
// Found a pending lane with no expiration time. If it's not suspended, or
// if it's pinged, assume it's CPU-bound. Compute a new expiration time
// using the current time.
if (
(lane & suspendedLanes) === NoLanes ||
(lane & pingedLanes) !== NoLanes
) {
// Assumes timestamps are monotonically increasing.
expirationTimes[index] = computeExpirationTime(lane, currentTime);
}
} else if (expirationTime <= currentTime) {
// This lane expired
root.expiredLanes |= lane;
}
lanes &= ~lane;
}
}
// This returns the highest priority pending lanes regardless of whether they
// are suspended.
export function getHighestPriorityPendingLanes(root: FiberRoot): Lanes {
return getHighestPriorityLanes(root.pendingLanes);
}
export function getLanesToRetrySynchronouslyOnError(
root: FiberRoot,
originallyAttemptedLanes: Lanes,
): Lanes {
if (root.errorRecoveryDisabledLanes & originallyAttemptedLanes) {
// The error recovery mechanism is disabled until these lanes are cleared.
return NoLanes;
}
const everythingButOffscreen = root.pendingLanes & ~OffscreenLane;
if (everythingButOffscreen !== NoLanes) {
return everythingButOffscreen;
}
if (everythingButOffscreen & OffscreenLane) {
return OffscreenLane;
}
return NoLanes;
}
export function includesSyncLane(lanes: Lanes): boolean {
return (lanes & (SyncLane | SyncHydrationLane)) !== NoLanes;
}
export function includesNonIdleWork(lanes: Lanes): boolean {
return (lanes & NonIdleLanes) !== NoLanes;
}
export function includesOnlyRetries(lanes: Lanes): boolean {
return (lanes & RetryLanes) === lanes;
}
export function includesOnlyNonUrgentLanes(lanes: Lanes): boolean {
// TODO: Should hydration lanes be included here? This function is only
// used in `updateDeferredValueImpl`.
const UrgentLanes = SyncLane | InputContinuousLane | DefaultLane;
return (lanes & UrgentLanes) === NoLanes;
}
export function includesOnlyTransitions(lanes: Lanes): boolean {
return (lanes & TransitionLanes) === lanes;
}
export function includesBlockingLane(root: FiberRoot, lanes: Lanes): boolean {
if (
allowConcurrentByDefault &&
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
) {
// Concurrent updates by default always use time slicing.
return false;
}
const SyncDefaultLanes =
InputContinuousHydrationLane |
InputContinuousLane |
DefaultHydrationLane |
DefaultLane;
return (lanes & SyncDefaultLanes) !== NoLanes;
}
export function includesExpiredLane(root: FiberRoot, lanes: Lanes): boolean {
// This is a separate check from includesBlockingLane because a lane can
// expire after a render has already started.
return (lanes & root.expiredLanes) !== NoLanes;
}
export function isTransitionLane(lane: Lane): boolean {
return (lane & TransitionLanes) !== NoLanes;
}
export function claimNextTransitionLane(): Lane {
// Cycle through the lanes, assigning each new transition to the next lane.
// In most cases, this means every transition gets its own lane, until we
// run out of lanes and cycle back to the beginning.
const lane = nextTransitionLane;
nextTransitionLane <<= 1;
if ((nextTransitionLane & TransitionLanes) === NoLanes) {
nextTransitionLane = TransitionLane1;
}
return lane;
}
export function claimNextRetryLane(): Lane {
const lane = nextRetryLane;
nextRetryLane <<= 1;
if ((nextRetryLane & RetryLanes) === NoLanes) {
nextRetryLane = RetryLane1;
}
return lane;
}
export function getHighestPriorityLane(lanes: Lanes): Lane {
return lanes & -lanes;
}
export function pickArbitraryLane(lanes: Lanes): Lane {
// This wrapper function gets inlined. Only exists so to communicate that it
// doesn't matter which bit is selected; you can pick any bit without
// affecting the algorithms where its used. Here I'm using
// getHighestPriorityLane because it requires the fewest operations.
return getHighestPriorityLane(lanes);
}
function pickArbitraryLaneIndex(lanes: Lanes) {
return 31 - clz32(lanes);
}
function laneToIndex(lane: Lane) {
return pickArbitraryLaneIndex(lane);
}
export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane): boolean {
return (a & b) !== NoLanes;
}
export function isSubsetOfLanes(set: Lanes, subset: Lanes | Lane): boolean {
return (set & subset) === subset;
}
export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
return a | b;
}
export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {
return set & ~subset;
}
export function intersectLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
return a & b;
}
// Seems redundant, but it changes the type from a single lane (used for
// updates) to a group of lanes (used for flushing work).
export function laneToLanes(lane: Lane): Lanes {
return lane;
}
export function higherPriorityLane(a: Lane, b: Lane): Lane {
// This works because the bit ranges decrease in priority as you go left.
return a !== NoLane && a < b ? a : b;
}
export function createLaneMap<T>(initial: T): LaneMap<T> {
// Intentionally pushing one by one.
// https://v8.dev/blog/elements-kinds#avoid-creating-holes
const laneMap = [];
for (let i = 0; i < TotalLanes; i++) {
laneMap.push(initial);
}
return laneMap;
}
export function markRootUpdated(
root: FiberRoot,
updateLane: Lane,
eventTime: number,
) {
root.pendingLanes |= updateLane;
// If there are any suspended transitions, it's possible this new update
// could unblock them. Clear the suspended lanes so that we can try rendering
// them again.
//
// TODO: We really only need to unsuspend only lanes that are in the
// `subtreeLanes` of the updated fiber, or the update lanes of the return
// path. This would exclude suspended updates in an unrelated sibling tree,
// since there's no way for this update to unblock it.
//
// We don't do this if the incoming update is idle, because we never process
// idle updates until after all the regular updates have finished; there's no
// way it could unblock a transition.
if (updateLane !== IdleLane) {
root.suspendedLanes = NoLanes;
root.pingedLanes = NoLanes;
}
const eventTimes = root.eventTimes;
const index = laneToIndex(updateLane);
// We can always overwrite an existing timestamp because we prefer the most
// recent event, and we assume time is monotonically increasing.
eventTimes[index] = eventTime;
}
export function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) {
root.suspendedLanes |= suspendedLanes;
root.pingedLanes &= ~suspendedLanes;
// The suspended lanes are no longer CPU-bound. Clear their expiration times.
const expirationTimes = root.expirationTimes;
let lanes = suspendedLanes;
while (lanes > 0) {
const index = pickArbitraryLaneIndex(lanes);
const lane = 1 << index;
expirationTimes[index] = NoTimestamp;
lanes &= ~lane;
}
}
export function markRootPinged(
root: FiberRoot,
pingedLanes: Lanes,
eventTime: number,
) {
root.pingedLanes |= root.suspendedLanes & pingedLanes;
}
export function markRootMutableRead(root: FiberRoot, updateLane: Lane) {
root.mutableReadLanes |= updateLane & root.pendingLanes;
}
export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) {
const noLongerPendingLanes = root.pendingLanes & ~remainingLanes;
root.pendingLanes = remainingLanes;
// Let's try everything again
root.suspendedLanes = NoLanes;
root.pingedLanes = NoLanes;
root.expiredLanes &= remainingLanes;
root.mutableReadLanes &= remainingLanes;
root.entangledLanes &= remainingLanes;
root.errorRecoveryDisabledLanes &= remainingLanes;
const entanglements = root.entanglements;
const eventTimes = root.eventTimes;
const expirationTimes = root.expirationTimes;
const hiddenUpdates = root.hiddenUpdates;
// Clear the lanes that no longer have pending work
let lanes = noLongerPendingLanes;
while (lanes > 0) {
const index = pickArbitraryLaneIndex(lanes);
const lane = 1 << index;
entanglements[index] = NoLanes;
eventTimes[index] = NoTimestamp;
expirationTimes[index] = NoTimestamp;
const hiddenUpdatesForLane = hiddenUpdates[index];
if (hiddenUpdatesForLane !== null) {
hiddenUpdates[index] = null;
// "Hidden" updates are updates that were made to a hidden component. They
// have special logic associated with them because they may be entangled
// with updates that occur outside that tree. But once the outer tree
// commits, they behave like regular updates.
for (let i = 0; i < hiddenUpdatesForLane.length; i++) {
const update = hiddenUpdatesForLane[i];
if (update !== null) {
update.lane &= ~OffscreenLane;
}
}
}
lanes &= ~lane;
}
}
export function markRootEntangled(root: FiberRoot, entangledLanes: Lanes) {
// In addition to entangling each of the given lanes with each other, we also
// have to consider _transitive_ entanglements. For each lane that is already
// entangled with *any* of the given lanes, that lane is now transitively
// entangled with *all* the given lanes.
//
// Translated: If C is entangled with A, then entangling A with B also
// entangles C with B.
//
// If this is hard to grasp, it might help to intentionally break this
// function and look at the tests that fail in ReactTransition-test.js. Try
// commenting out one of the conditions below.
const rootEntangledLanes = (root.entangledLanes |= entangledLanes);
const entanglements = root.entanglements;
let lanes = rootEntangledLanes;
while (lanes) {
const index = pickArbitraryLaneIndex(lanes);
const lane = 1 << index;
if (
// Is this one of the newly entangled lanes?
(lane & entangledLanes) |
// Is this lane transitively entangled with the newly entangled lanes?
(entanglements[index] & entangledLanes)
) {
entanglements[index] |= entangledLanes;
}
lanes &= ~lane;
}
}
export function markHiddenUpdate(
root: FiberRoot,
update: ConcurrentUpdate,
lane: Lane,
) {
const index = laneToIndex(lane);
const hiddenUpdates = root.hiddenUpdates;
const hiddenUpdatesForLane = hiddenUpdates[index];
if (hiddenUpdatesForLane === null) {
hiddenUpdates[index] = [update];
} else {
hiddenUpdatesForLane.push(update);
}
update.lane = lane | OffscreenLane;
}
export function getBumpedLaneForHydration(
root: FiberRoot,
renderLanes: Lanes,
): Lane {
const renderLane = getHighestPriorityLane(renderLanes);
let lane;
switch (renderLane) {
case SyncLane:
lane = SyncHydrationLane;
break;
case InputContinuousLane:
lane = InputContinuousHydrationLane;
break;
case DefaultLane:
lane = DefaultHydrationLane;
break;
case TransitionLane1:
case TransitionLane2:
case TransitionLane3:
case TransitionLane4:
case TransitionLane5:
case TransitionLane6:
case TransitionLane7:
case TransitionLane8:
case TransitionLane9:
case TransitionLane10:
case TransitionLane11:
case TransitionLane12:
case TransitionLane13:
case TransitionLane14:
case TransitionLane15:
case TransitionLane16:
case RetryLane1:
case RetryLane2:
case RetryLane3:
case RetryLane4:
lane = TransitionHydrationLane;
break;
case IdleLane:
lane = IdleHydrationLane;
break;
default:
// Everything else is already either a hydration lane, or shouldn't
// be retried at a hydration lane.
lane = NoLane;
break;
}
// Check if the lane we chose is suspended. If so, that indicates that we
// already attempted and failed to hydrate at that level. Also check if we're
// already rendering that lane, which is rare but could happen.
if ((lane & (root.suspendedLanes | renderLanes)) !== NoLane) {
// Give up trying to hydrate and fall back to client render.
return NoLane;
}
return lane;
}
export function addFiberToLanesMap(
root: FiberRoot,
fiber: Fiber,
lanes: Lanes | Lane,
) {
if (!enableUpdaterTracking) {
return;
}
if (!isDevToolsPresent) {
return;
}
const pendingUpdatersLaneMap = root.pendingUpdatersLaneMap;
while (lanes > 0) {
const index = laneToIndex(lanes);
const lane = 1 << index;
const updaters = pendingUpdatersLaneMap[index];
updaters.add(fiber);
lanes &= ~lane;
}
}
export function movePendingFibersToMemoized(root: FiberRoot, lanes: Lanes) {
if (!enableUpdaterTracking) {
return;
}
if (!isDevToolsPresent) {
return;
}
const pendingUpdatersLaneMap = root.pendingUpdatersLaneMap;
const memoizedUpdaters = root.memoizedUpdaters;
while (lanes > 0) {
const index = laneToIndex(lanes);
const lane = 1 << index;
const updaters = pendingUpdatersLaneMap[index];
if (updaters.size > 0) {
updaters.forEach(fiber => {
const alternate = fiber.alternate;
if (alternate === null || !memoizedUpdaters.has(alternate)) {
memoizedUpdaters.add(fiber);
}
});
updaters.clear();
}
lanes &= ~lane;
}
}
export function addTransitionToLanesMap(
root: FiberRoot,
transition: Transition,
lane: Lane,
) {
if (enableTransitionTracing) {
const transitionLanesMap = root.transitionLanes;
const index = laneToIndex(lane);
let transitions = transitionLanesMap[index];
if (transitions === null) {
transitions = new Set();
}
transitions.add(transition);
transitionLanesMap[index] = transitions;
}
}
export function getTransitionsForLanes(
root: FiberRoot,
lanes: Lane | Lanes,
): Array<Transition> | null {
if (!enableTransitionTracing) {
return null;
}
const transitionsForLanes = [];
while (lanes > 0) {
const index = laneToIndex(lanes);
const lane = 1 << index;
const transitions = root.transitionLanes[index];
if (transitions !== null) {
transitions.forEach(transition => {
transitionsForLanes.push(transition);
});
}
lanes &= ~lane;
}
if (transitionsForLanes.length === 0) {
return null;
}
return transitionsForLanes;
}
export function clearTransitionsForLanes(root: FiberRoot, lanes: Lane | Lanes) {
if (!enableTransitionTracing) {
return;
}
while (lanes > 0) {
const index = laneToIndex(lanes);
const lane = 1 << index;
const transitions = root.transitionLanes[index];
if (transitions !== null) {
root.transitionLanes[index] = null;
}
lanes &= ~lane;
}
}

View File

@ -1,25 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import assign from 'shared/assign';
export function resolveDefaultProps(Component: any, baseProps: Object): Object {
if (Component && Component.defaultProps) {
// Resolve default props. Taken from ReactElement
const props = assign({}, baseProps);
const defaultProps = Component.defaultProps;
for (const propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
return props;
}
return baseProps;
}

View File

@ -1,706 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ReactContext, ReactProviderType} from 'shared/ReactTypes';
import type {
Fiber,
ContextDependency,
Dependencies,
} from './ReactInternalTypes';
import type {StackCursor} from './ReactFiberStack.new';
import type {Lanes} from './ReactFiberLane.new';
import type {SharedQueue} from './ReactFiberClassUpdateQueue.new';
import {isPrimaryRenderer} from './ReactFiberHostConfig';
import {createCursor, push, pop} from './ReactFiberStack.new';
import {
ContextProvider,
ClassComponent,
DehydratedFragment,
} from './ReactWorkTags';
import {
NoLanes,
NoTimestamp,
isSubsetOfLanes,
includesSomeLane,
mergeLanes,
pickArbitraryLane,
} from './ReactFiberLane.new';
import {
NoFlags,
DidPropagateContext,
NeedsPropagation,
} from './ReactFiberFlags';
import is from 'shared/objectIs';
import {createUpdate, ForceUpdate} from './ReactFiberClassUpdateQueue.new';
import {markWorkInProgressReceivedUpdate} from './ReactFiberBeginWork.new';
import {
enableLazyContextPropagation,
enableServerContext,
} from 'shared/ReactFeatureFlags';
import {REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED} from 'shared/ReactSymbols';
const valueCursor: StackCursor<mixed> = createCursor(null);
let rendererSigil;
if (__DEV__) {
// Use this to detect multiple renderers using the same context
rendererSigil = {};
}
let currentlyRenderingFiber: Fiber | null = null;
let lastContextDependency: ContextDependency<mixed> | null = null;
let lastFullyObservedContext: ReactContext<any> | null = null;
let isDisallowedContextReadInDEV: boolean = false;
export function resetContextDependencies(): void {
// This is called right before React yields execution, to ensure `readContext`
// cannot be called outside the render phase.
currentlyRenderingFiber = null;
lastContextDependency = null;
lastFullyObservedContext = null;
if (__DEV__) {
isDisallowedContextReadInDEV = false;
}
}
export function enterDisallowedContextReadInDEV(): void {
if (__DEV__) {
isDisallowedContextReadInDEV = true;
}
}
export function exitDisallowedContextReadInDEV(): void {
if (__DEV__) {
isDisallowedContextReadInDEV = false;
}
}
export function pushProvider<T>(
providerFiber: Fiber,
context: ReactContext<T>,
nextValue: T,
): void {
if (isPrimaryRenderer) {
push(valueCursor, context._currentValue, providerFiber);
context._currentValue = nextValue;
if (__DEV__) {
if (
context._currentRenderer !== undefined &&
context._currentRenderer !== null &&
context._currentRenderer !== rendererSigil
) {
console.error(
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
}
context._currentRenderer = rendererSigil;
}
} else {
push(valueCursor, context._currentValue2, providerFiber);
context._currentValue2 = nextValue;
if (__DEV__) {
if (
context._currentRenderer2 !== undefined &&
context._currentRenderer2 !== null &&
context._currentRenderer2 !== rendererSigil
) {
console.error(
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
}
context._currentRenderer2 = rendererSigil;
}
}
}
export function popProvider(
context: ReactContext<any>,
providerFiber: Fiber,
): void {
const currentValue = valueCursor.current;
pop(valueCursor, providerFiber);
if (isPrimaryRenderer) {
if (
enableServerContext &&
currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
) {
context._currentValue = context._defaultValue;
} else {
context._currentValue = currentValue;
}
} else {
if (
enableServerContext &&
currentValue === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED
) {
context._currentValue2 = context._defaultValue;
} else {
context._currentValue2 = currentValue;
}
}
}
export function scheduleContextWorkOnParentPath(
parent: Fiber | null,
renderLanes: Lanes,
propagationRoot: Fiber,
) {
// Update the child lanes of all the ancestors, including the alternates.
let node = parent;
while (node !== null) {
const alternate = node.alternate;
if (!isSubsetOfLanes(node.childLanes, renderLanes)) {
node.childLanes = mergeLanes(node.childLanes, renderLanes);
if (alternate !== null) {
alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);
}
} else if (
alternate !== null &&
!isSubsetOfLanes(alternate.childLanes, renderLanes)
) {
alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes);
} else {
// Neither alternate was updated.
// Normally, this would mean that the rest of the
// ancestor path already has sufficient priority.
// However, this is not necessarily true inside offscreen
// or fallback trees because childLanes may be inconsistent
// with the surroundings. This is why we continue the loop.
}
if (node === propagationRoot) {
break;
}
node = node.return;
}
if (__DEV__) {
if (node !== propagationRoot) {
console.error(
'Expected to find the propagation root when scheduling context work. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
}
}
export function propagateContextChange<T>(
workInProgress: Fiber,
context: ReactContext<T>,
renderLanes: Lanes,
): void {
if (enableLazyContextPropagation) {
// TODO: This path is only used by Cache components. Update
// lazilyPropagateParentContextChanges to look for Cache components so they
// can take advantage of lazy propagation.
const forcePropagateEntireTree = true;
propagateContextChanges(
workInProgress,
[context],
renderLanes,
forcePropagateEntireTree,
);
} else {
propagateContextChange_eager(workInProgress, context, renderLanes);
}
}
function propagateContextChange_eager<T>(
workInProgress: Fiber,
context: ReactContext<T>,
renderLanes: Lanes,
): void {
// Only used by eager implementation
if (enableLazyContextPropagation) {
return;
}
let fiber = workInProgress.child;
if (fiber !== null) {
// Set the return pointer of the child to the work-in-progress fiber.
fiber.return = workInProgress;
}
while (fiber !== null) {
let nextFiber;
// Visit this fiber.
const list = fiber.dependencies;
if (list !== null) {
nextFiber = fiber.child;
let dependency = list.firstContext;
while (dependency !== null) {
// Check if the context matches.
if (dependency.context === context) {
// Match! Schedule an update on this fiber.
if (fiber.tag === ClassComponent) {
// Schedule a force update on the work-in-progress.
const lane = pickArbitraryLane(renderLanes);
const update = createUpdate(NoTimestamp, lane);
update.tag = ForceUpdate;
// TODO: Because we don't have a work-in-progress, this will add the
// update to the current fiber, too, which means it will persist even if
// this render is thrown away. Since it's a race condition, not sure it's
// worth fixing.
// Inlined `enqueueUpdate` to remove interleaved update check
const updateQueue = fiber.updateQueue;
if (updateQueue === null) {
// Only occurs if the fiber has been unmounted.
} else {
const sharedQueue: SharedQueue<any> = (updateQueue: any).shared;
const pending = sharedQueue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
}
}
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
scheduleContextWorkOnParentPath(
fiber.return,
renderLanes,
workInProgress,
);
// Mark the updated lanes on the list, too.
list.lanes = mergeLanes(list.lanes, renderLanes);
// Since we already found a match, we can stop traversing the
// dependency list.
break;
}
dependency = dependency.next;
}
} else if (fiber.tag === ContextProvider) {
// Don't scan deeper if this is a matching provider
nextFiber = fiber.type === workInProgress.type ? null : fiber.child;
} else if (fiber.tag === DehydratedFragment) {
// If a dehydrated suspense boundary is in this subtree, we don't know
// if it will have any context consumers in it. The best we can do is
// mark it as having updates.
const parentSuspense = fiber.return;
if (parentSuspense === null) {
throw new Error(
'We just came from a parent so we must have had a parent. This is a bug in React.',
);
}
parentSuspense.lanes = mergeLanes(parentSuspense.lanes, renderLanes);
const alternate = parentSuspense.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
// This is intentionally passing this fiber as the parent
// because we want to schedule this fiber as having work
// on its children. We'll use the childLanes on
// this fiber to indicate that a context has changed.
scheduleContextWorkOnParentPath(
parentSuspense,
renderLanes,
workInProgress,
);
nextFiber = fiber.sibling;
} else {
// Traverse down.
nextFiber = fiber.child;
}
if (nextFiber !== null) {
// Set the return pointer of the child to the work-in-progress fiber.
nextFiber.return = fiber;
} else {
// No child. Traverse to next sibling.
nextFiber = fiber;
while (nextFiber !== null) {
if (nextFiber === workInProgress) {
// We're back to the root of this subtree. Exit.
nextFiber = null;
break;
}
const sibling = nextFiber.sibling;
if (sibling !== null) {
// Set the return pointer of the sibling to the work-in-progress fiber.
sibling.return = nextFiber.return;
nextFiber = sibling;
break;
}
// No more siblings. Traverse up.
nextFiber = nextFiber.return;
}
}
fiber = nextFiber;
}
}
function propagateContextChanges<T>(
workInProgress: Fiber,
contexts: Array<any>,
renderLanes: Lanes,
forcePropagateEntireTree: boolean,
): void {
// Only used by lazy implementation
if (!enableLazyContextPropagation) {
return;
}
let fiber = workInProgress.child;
if (fiber !== null) {
// Set the return pointer of the child to the work-in-progress fiber.
fiber.return = workInProgress;
}
while (fiber !== null) {
let nextFiber;
// Visit this fiber.
const list = fiber.dependencies;
if (list !== null) {
nextFiber = fiber.child;
let dep = list.firstContext;
findChangedDep: while (dep !== null) {
// Assigning these to constants to help Flow
const dependency = dep;
const consumer = fiber;
findContext: for (let i = 0; i < contexts.length; i++) {
const context: ReactContext<T> = contexts[i];
// Check if the context matches.
// TODO: Compare selected values to bail out early.
if (dependency.context === context) {
// Match! Schedule an update on this fiber.
// In the lazy implementation, don't mark a dirty flag on the
// dependency itself. Not all changes are propagated, so we can't
// rely on the propagation function alone to determine whether
// something has changed; the consumer will check. In the future, we
// could add back a dirty flag as an optimization to avoid double
// checking, but until we have selectors it's not really worth
// the trouble.
consumer.lanes = mergeLanes(consumer.lanes, renderLanes);
const alternate = consumer.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
scheduleContextWorkOnParentPath(
consumer.return,
renderLanes,
workInProgress,
);
if (!forcePropagateEntireTree) {
// During lazy propagation, when we find a match, we can defer
// propagating changes to the children, because we're going to
// visit them during render. We should continue propagating the
// siblings, though
nextFiber = null;
}
// Since we already found a match, we can stop traversing the
// dependency list.
break findChangedDep;
}
}
dep = dependency.next;
}
} else if (fiber.tag === DehydratedFragment) {
// If a dehydrated suspense boundary is in this subtree, we don't know
// if it will have any context consumers in it. The best we can do is
// mark it as having updates.
const parentSuspense = fiber.return;
if (parentSuspense === null) {
throw new Error(
'We just came from a parent so we must have had a parent. This is a bug in React.',
);
}
parentSuspense.lanes = mergeLanes(parentSuspense.lanes, renderLanes);
const alternate = parentSuspense.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
// This is intentionally passing this fiber as the parent
// because we want to schedule this fiber as having work
// on its children. We'll use the childLanes on
// this fiber to indicate that a context has changed.
scheduleContextWorkOnParentPath(
parentSuspense,
renderLanes,
workInProgress,
);
nextFiber = null;
} else {
// Traverse down.
nextFiber = fiber.child;
}
if (nextFiber !== null) {
// Set the return pointer of the child to the work-in-progress fiber.
nextFiber.return = fiber;
} else {
// No child. Traverse to next sibling.
nextFiber = fiber;
while (nextFiber !== null) {
if (nextFiber === workInProgress) {
// We're back to the root of this subtree. Exit.
nextFiber = null;
break;
}
const sibling = nextFiber.sibling;
if (sibling !== null) {
// Set the return pointer of the sibling to the work-in-progress fiber.
sibling.return = nextFiber.return;
nextFiber = sibling;
break;
}
// No more siblings. Traverse up.
nextFiber = nextFiber.return;
}
}
fiber = nextFiber;
}
}
export function lazilyPropagateParentContextChanges(
current: Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const forcePropagateEntireTree = false;
propagateParentContextChanges(
current,
workInProgress,
renderLanes,
forcePropagateEntireTree,
);
}
// Used for propagating a deferred tree (Suspense, Offscreen). We must propagate
// to the entire subtree, because we won't revisit it until after the current
// render has completed, at which point we'll have lost track of which providers
// have changed.
export function propagateParentContextChangesToDeferredTree(
current: Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const forcePropagateEntireTree = true;
propagateParentContextChanges(
current,
workInProgress,
renderLanes,
forcePropagateEntireTree,
);
}
function propagateParentContextChanges(
current: Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
forcePropagateEntireTree: boolean,
) {
if (!enableLazyContextPropagation) {
return;
}
// Collect all the parent providers that changed. Since this is usually small
// number, we use an Array instead of Set.
let contexts = null;
let parent: null | Fiber = workInProgress;
let isInsidePropagationBailout = false;
while (parent !== null) {
if (!isInsidePropagationBailout) {
if ((parent.flags & NeedsPropagation) !== NoFlags) {
isInsidePropagationBailout = true;
} else if ((parent.flags & DidPropagateContext) !== NoFlags) {
break;
}
}
if (parent.tag === ContextProvider) {
const currentParent = parent.alternate;
if (currentParent === null) {
throw new Error('Should have a current fiber. This is a bug in React.');
}
const oldProps = currentParent.memoizedProps;
if (oldProps !== null) {
const providerType: ReactProviderType<any> = parent.type;
const context: ReactContext<any> = providerType._context;
const newProps = parent.pendingProps;
const newValue = newProps.value;
const oldValue = oldProps.value;
if (!is(newValue, oldValue)) {
if (contexts !== null) {
contexts.push(context);
} else {
contexts = [context];
}
}
}
}
parent = parent.return;
}
if (contexts !== null) {
// If there were any changed providers, search through the children and
// propagate their changes.
propagateContextChanges(
workInProgress,
contexts,
renderLanes,
forcePropagateEntireTree,
);
}
// This is an optimization so that we only propagate once per subtree. If a
// deeply nested child bails out, and it calls this propagation function, it
// uses this flag to know that the remaining ancestor providers have already
// been propagated.
//
// NOTE: This optimization is only necessary because we sometimes enter the
// begin phase of nodes that don't have any work scheduled on them —
// specifically, the siblings of a node that _does_ have scheduled work. The
// siblings will bail out and call this function again, even though we already
// propagated content changes to it and its subtree. So we use this flag to
// mark that the parent providers already propagated.
//
// Unfortunately, though, we need to ignore this flag when we're inside a
// tree whose context propagation was deferred — that's what the
// `NeedsPropagation` flag is for.
//
// If we could instead bail out before entering the siblings' begin phase,
// then we could remove both `DidPropagateContext` and `NeedsPropagation`.
// Consider this as part of the next refactor to the fiber tree structure.
workInProgress.flags |= DidPropagateContext;
}
export function checkIfContextChanged(
currentDependencies: Dependencies,
): boolean {
if (!enableLazyContextPropagation) {
return false;
}
// Iterate over the current dependencies to see if something changed. This
// only gets called if 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.
let dependency = currentDependencies.firstContext;
while (dependency !== null) {
const context = dependency.context;
const newValue = isPrimaryRenderer
? context._currentValue
: context._currentValue2;
const oldValue = dependency.memoizedValue;
if (!is(newValue, oldValue)) {
return true;
}
dependency = dependency.next;
}
return false;
}
export function prepareToReadContext(
workInProgress: Fiber,
renderLanes: Lanes,
): void {
currentlyRenderingFiber = workInProgress;
lastContextDependency = null;
lastFullyObservedContext = null;
const dependencies = workInProgress.dependencies;
if (dependencies !== null) {
if (enableLazyContextPropagation) {
// Reset the work-in-progress list
dependencies.firstContext = null;
} else {
const firstContext = dependencies.firstContext;
if (firstContext !== null) {
if (includesSomeLane(dependencies.lanes, renderLanes)) {
// Context list has a pending update. Mark that this fiber performed work.
markWorkInProgressReceivedUpdate();
}
// Reset the work-in-progress list
dependencies.firstContext = null;
}
}
}
}
export function readContext<T>(context: ReactContext<T>): T {
if (__DEV__) {
// This warning would fire if you read context inside a Hook like useMemo.
// Unlike the class check below, it's not enforced in production for perf.
if (isDisallowedContextReadInDEV) {
console.error(
'Context can only be read while React is rendering. ' +
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
'In function components, you can read it directly in the function body, but not ' +
'inside Hooks like useReducer() or useMemo().',
);
}
}
const value = isPrimaryRenderer
? context._currentValue
: context._currentValue2;
if (lastFullyObservedContext === context) {
// Nothing to do. We already observe everything in this context.
} else {
const contextItem = {
context: ((context: any): ReactContext<mixed>),
memoizedValue: value,
next: null,
};
if (lastContextDependency === null) {
if (currentlyRenderingFiber === null) {
throw new Error(
'Context can only be read while React is rendering. ' +
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
'In function components, you can read it directly in the function body, but not ' +
'inside Hooks like useReducer() or useMemo().',
);
}
// This is the first dependency for this component. Create a new list.
lastContextDependency = contextItem;
currentlyRenderingFiber.dependencies = {
lanes: NoLanes,
firstContext: contextItem,
};
if (enableLazyContextPropagation) {
currentlyRenderingFiber.flags |= NeedsPropagation;
}
} else {
// Append a new context item.
lastContextDependency = lastContextDependency.next = contextItem;
}
}
return value;
}

View File

@ -9,12 +9,12 @@
import type {ReactNodeList, OffscreenMode, Wakeable} from 'shared/ReactTypes';
import type {Lanes} from './ReactFiberLane.old';
import type {SpawnedCachePool} from './ReactFiberCacheComponent.new';
import type {SpawnedCachePool} from './ReactFiberCacheComponent.old';
import type {Fiber} from './ReactInternalTypes';
import type {
Transition,
TracingMarkerInstance,
} from './ReactFiberTracingMarkerComponent.new';
} from './ReactFiberTracingMarkerComponent.old';
export type OffscreenProps = {
// TODO: Pick an API before exposing the Offscreen type. I've chosen an enum

View File

@ -1,202 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import {enableNewReconciler} from 'shared/ReactFeatureFlags';
// The entry file imports either the old or new version of the reconciler.
// During build and testing, this indirection is always shimmed with the actual
// modules, otherwise both reconcilers would be initialized. So this is really
// only here for Flow purposes.
import {
createContainer as createContainer_old,
createHydrationContainer as createHydrationContainer_old,
updateContainer as updateContainer_old,
batchedUpdates as batchedUpdates_old,
deferredUpdates as deferredUpdates_old,
discreteUpdates as discreteUpdates_old,
flushControlled as flushControlled_old,
flushSync as flushSync_old,
isAlreadyRendering as isAlreadyRendering_old,
flushPassiveEffects as flushPassiveEffects_old,
getPublicRootInstance as getPublicRootInstance_old,
attemptSynchronousHydration as attemptSynchronousHydration_old,
attemptDiscreteHydration as attemptDiscreteHydration_old,
attemptContinuousHydration as attemptContinuousHydration_old,
attemptHydrationAtCurrentPriority as attemptHydrationAtCurrentPriority_old,
findHostInstance as findHostInstance_old,
findHostInstanceWithWarning as findHostInstanceWithWarning_old,
findHostInstanceWithNoPortals as findHostInstanceWithNoPortals_old,
shouldError as shouldError_old,
shouldSuspend as shouldSuspend_old,
injectIntoDevTools as injectIntoDevTools_old,
createPortal as createPortal_old,
createComponentSelector as createComponentSelector_old,
createHasPseudoClassSelector as createHasPseudoClassSelector_old,
createRoleSelector as createRoleSelector_old,
createTestNameSelector as createTestNameSelector_old,
createTextSelector as createTextSelector_old,
getFindAllNodesFailureDescription as getFindAllNodesFailureDescription_old,
findAllNodes as findAllNodes_old,
findBoundingRects as findBoundingRects_old,
focusWithin as focusWithin_old,
observeVisibleRects as observeVisibleRects_old,
registerMutableSourceForHydration as registerMutableSourceForHydration_old,
runWithPriority as runWithPriority_old,
getCurrentUpdatePriority as getCurrentUpdatePriority_old,
} from './ReactFiberReconciler.old';
import {
createContainer as createContainer_new,
createHydrationContainer as createHydrationContainer_new,
updateContainer as updateContainer_new,
batchedUpdates as batchedUpdates_new,
deferredUpdates as deferredUpdates_new,
discreteUpdates as discreteUpdates_new,
flushControlled as flushControlled_new,
flushSync as flushSync_new,
isAlreadyRendering as isAlreadyRendering_new,
flushPassiveEffects as flushPassiveEffects_new,
getPublicRootInstance as getPublicRootInstance_new,
attemptSynchronousHydration as attemptSynchronousHydration_new,
attemptDiscreteHydration as attemptDiscreteHydration_new,
attemptContinuousHydration as attemptContinuousHydration_new,
attemptHydrationAtCurrentPriority as attemptHydrationAtCurrentPriority_new,
findHostInstance as findHostInstance_new,
findHostInstanceWithWarning as findHostInstanceWithWarning_new,
findHostInstanceWithNoPortals as findHostInstanceWithNoPortals_new,
shouldError as shouldError_new,
shouldSuspend as shouldSuspend_new,
injectIntoDevTools as injectIntoDevTools_new,
createPortal as createPortal_new,
createComponentSelector as createComponentSelector_new,
createHasPseudoClassSelector as createHasPseudoClassSelector_new,
createRoleSelector as createRoleSelector_new,
createTestNameSelector as createTestNameSelector_new,
createTextSelector as createTextSelector_new,
getFindAllNodesFailureDescription as getFindAllNodesFailureDescription_new,
findAllNodes as findAllNodes_new,
findBoundingRects as findBoundingRects_new,
focusWithin as focusWithin_new,
observeVisibleRects as observeVisibleRects_new,
registerMutableSourceForHydration as registerMutableSourceForHydration_new,
runWithPriority as runWithPriority_new,
getCurrentUpdatePriority as getCurrentUpdatePriority_new,
} from './ReactFiberReconciler.new';
export const createContainer: typeof createContainer_new = enableNewReconciler
? createContainer_new
: createContainer_old;
export const createHydrationContainer: typeof createHydrationContainer_new = enableNewReconciler
? createHydrationContainer_new
: createHydrationContainer_old;
export const updateContainer: typeof updateContainer_new = enableNewReconciler
? updateContainer_new
: updateContainer_old;
export const batchedUpdates: typeof batchedUpdates_new = enableNewReconciler
? batchedUpdates_new
: batchedUpdates_old;
export const deferredUpdates: typeof deferredUpdates_new = enableNewReconciler
? deferredUpdates_new
: deferredUpdates_old;
export const discreteUpdates: typeof discreteUpdates_new = enableNewReconciler
? discreteUpdates_new
: discreteUpdates_old;
export const flushControlled: typeof flushControlled_new = enableNewReconciler
? flushControlled_new
: flushControlled_old;
export const flushSync: typeof flushSync_new = enableNewReconciler
? flushSync_new
: flushSync_old;
export const isAlreadyRendering: typeof isAlreadyRendering_new = enableNewReconciler
? isAlreadyRendering_new
: isAlreadyRendering_old;
export const flushPassiveEffects: typeof flushPassiveEffects_new = enableNewReconciler
? flushPassiveEffects_new
: flushPassiveEffects_old;
export const getPublicRootInstance: typeof getPublicRootInstance_new = enableNewReconciler
? getPublicRootInstance_new
: getPublicRootInstance_old;
export const attemptSynchronousHydration: typeof attemptSynchronousHydration_new = enableNewReconciler
? attemptSynchronousHydration_new
: attemptSynchronousHydration_old;
export const attemptDiscreteHydration: typeof attemptDiscreteHydration_new = enableNewReconciler
? attemptDiscreteHydration_new
: attemptDiscreteHydration_old;
export const attemptContinuousHydration: typeof attemptContinuousHydration_new = enableNewReconciler
? attemptContinuousHydration_new
: attemptContinuousHydration_old;
export const attemptHydrationAtCurrentPriority: typeof attemptHydrationAtCurrentPriority_new = enableNewReconciler
? attemptHydrationAtCurrentPriority_new
: attemptHydrationAtCurrentPriority_old;
export const getCurrentUpdatePriority: typeof getCurrentUpdatePriority_new = enableNewReconciler
? getCurrentUpdatePriority_new
: /* $FlowFixMe[incompatible-type] opaque types EventPriority from new and old
* are incompatible. */
getCurrentUpdatePriority_old;
export const findHostInstance: typeof findHostInstance_new = enableNewReconciler
? findHostInstance_new
: findHostInstance_old;
export const findHostInstanceWithWarning: typeof findHostInstanceWithWarning_new = enableNewReconciler
? findHostInstanceWithWarning_new
: findHostInstanceWithWarning_old;
export const findHostInstanceWithNoPortals: typeof findHostInstanceWithNoPortals_new = enableNewReconciler
? findHostInstanceWithNoPortals_new
: findHostInstanceWithNoPortals_old;
export const shouldError: typeof shouldError_new = enableNewReconciler
? shouldError_new
: shouldError_old;
export const shouldSuspend: typeof shouldSuspend_new = enableNewReconciler
? shouldSuspend_new
: shouldSuspend_old;
export const injectIntoDevTools: typeof injectIntoDevTools_new = enableNewReconciler
? injectIntoDevTools_new
: injectIntoDevTools_old;
export const createPortal: typeof createPortal_new = enableNewReconciler
? createPortal_new
: createPortal_old;
export const createComponentSelector: typeof createComponentSelector_new = enableNewReconciler
? createComponentSelector_new
: createComponentSelector_old;
export const createHasPseudoClassSelector: typeof createHasPseudoClassSelector_new = enableNewReconciler
? createHasPseudoClassSelector_new
: createHasPseudoClassSelector_old;
export const createRoleSelector: typeof createRoleSelector_new = enableNewReconciler
? createRoleSelector_new
: createRoleSelector_old;
export const createTextSelector: typeof createTextSelector_new = enableNewReconciler
? createTextSelector_new
: createTextSelector_old;
export const createTestNameSelector: typeof createTestNameSelector_new = enableNewReconciler
? createTestNameSelector_new
: createTestNameSelector_old;
export const getFindAllNodesFailureDescription: typeof getFindAllNodesFailureDescription_new = enableNewReconciler
? getFindAllNodesFailureDescription_new
: getFindAllNodesFailureDescription_old;
export const findAllNodes: typeof findAllNodes_new = enableNewReconciler
? findAllNodes_new
: findAllNodes_old;
export const findBoundingRects: typeof findBoundingRects_new = enableNewReconciler
? findBoundingRects_new
: findBoundingRects_old;
export const focusWithin: typeof focusWithin_new = enableNewReconciler
? focusWithin_new
: focusWithin_old;
export const observeVisibleRects: typeof observeVisibleRects_new = enableNewReconciler
? observeVisibleRects_new
: observeVisibleRects_old;
export const registerMutableSourceForHydration: typeof registerMutableSourceForHydration_new = enableNewReconciler
? registerMutableSourceForHydration_new
: registerMutableSourceForHydration_old;
/* $FlowFixMe[incompatible-type] opaque types EventPriority from new and old
* are incompatible. */
export const runWithPriority: typeof runWithPriority_new = enableNewReconciler
? runWithPriority_new
: runWithPriority_old;

View File

@ -1,845 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {
Fiber,
FiberRoot,
SuspenseHydrationCallbacks,
TransitionTracingCallbacks,
} from './ReactInternalTypes';
import type {RootTag} from './ReactRootTags';
import type {
Instance,
TextInstance,
Container,
PublicInstance,
RendererInspectionConfig,
} from './ReactFiberHostConfig';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {Lane} from './ReactFiberLane.new';
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
import {
findCurrentHostFiber,
findCurrentHostFiberWithNoPortals,
} from './ReactFiberTreeReflection';
import {get as getInstance} from 'shared/ReactInstanceMap';
import {
HostComponent,
HostSingleton,
ClassComponent,
HostRoot,
SuspenseComponent,
} from './ReactWorkTags';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import isArray from 'shared/isArray';
import {enableSchedulingProfiler} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {getPublicInstance} from './ReactFiberHostConfig';
import {
findCurrentUnmaskedContext,
processChildContext,
emptyContextObject,
isContextProvider as isLegacyContextProvider,
} from './ReactFiberContext.new';
import {createFiberRoot} from './ReactFiberRoot.new';
import {isRootDehydrated} from './ReactFiberShellHydration';
import {
injectInternals,
markRenderScheduled,
onScheduleRoot,
} from './ReactFiberDevToolsHook.new';
import {
requestEventTime,
requestUpdateLane,
scheduleUpdateOnFiber,
scheduleInitialHydrationOnRoot,
flushRoot,
batchedUpdates,
flushSync,
isAlreadyRendering,
flushControlled,
deferredUpdates,
discreteUpdates,
flushPassiveEffects,
} from './ReactFiberWorkLoop.new';
import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates.new';
import {
createUpdate,
enqueueUpdate,
entangleTransitions,
} from './ReactFiberClassUpdateQueue.new';
import {
isRendering as ReactCurrentFiberIsRendering,
current as ReactCurrentFiberCurrent,
resetCurrentFiber as resetCurrentDebugFiberInDEV,
setCurrentFiber as setCurrentDebugFiberInDEV,
} from './ReactCurrentFiber';
import {StrictLegacyMode} from './ReactTypeOfMode';
import {
SyncLane,
SelectiveHydrationLane,
NoTimestamp,
getHighestPriorityPendingLanes,
higherPriorityLane,
} from './ReactFiberLane.new';
import {
getCurrentUpdatePriority,
runWithPriority,
} from './ReactEventPriorities.new';
import {
scheduleRefresh,
scheduleRoot,
setRefreshHandler,
findHostInstancesForRefresh,
} from './ReactFiberHotReloading.new';
import ReactVersion from 'shared/ReactVersion';
export {registerMutableSourceForHydration} from './ReactMutableSource.new';
export {createPortal} from './ReactPortal';
export {
createComponentSelector,
createHasPseudoClassSelector,
createRoleSelector,
createTestNameSelector,
createTextSelector,
getFindAllNodesFailureDescription,
findAllNodes,
findBoundingRects,
focusWithin,
observeVisibleRects,
} from './ReactTestSelectors';
type OpaqueRoot = FiberRoot;
// 0 is PROD, 1 is DEV.
// Might add PROFILE later.
type BundleType = 0 | 1;
type DevToolsConfig = {
bundleType: BundleType,
version: string,
rendererPackageName: string,
// Note: this actually *does* depend on Fiber internal fields.
// Used by "inspect clicked DOM element" in React DevTools.
findFiberByHostInstance?: (instance: Instance | TextInstance) => Fiber | null,
rendererConfig?: RendererInspectionConfig,
};
let didWarnAboutNestedUpdates;
let didWarnAboutFindNodeInStrictMode;
if (__DEV__) {
didWarnAboutNestedUpdates = false;
didWarnAboutFindNodeInStrictMode = {};
}
function getContextForSubtree(
parentComponent: ?React$Component<any, any>,
): Object {
if (!parentComponent) {
return emptyContextObject;
}
const fiber = getInstance(parentComponent);
const parentContext = findCurrentUnmaskedContext(fiber);
if (fiber.tag === ClassComponent) {
const Component = fiber.type;
if (isLegacyContextProvider(Component)) {
return processChildContext(fiber, Component, parentContext);
}
}
return parentContext;
}
function findHostInstance(component: Object): PublicInstance | null {
const fiber = getInstance(component);
if (fiber === undefined) {
if (typeof component.render === 'function') {
throw new Error('Unable to find node on an unmounted component.');
} else {
const keys = Object.keys(component).join(',');
throw new Error(
`Argument appears to not be a ReactComponent. Keys: ${keys}`,
);
}
}
const hostFiber = findCurrentHostFiber(fiber);
if (hostFiber === null) {
return null;
}
return hostFiber.stateNode;
}
function findHostInstanceWithWarning(
component: Object,
methodName: string,
): PublicInstance | null {
if (__DEV__) {
const fiber = getInstance(component);
if (fiber === undefined) {
if (typeof component.render === 'function') {
throw new Error('Unable to find node on an unmounted component.');
} else {
const keys = Object.keys(component).join(',');
throw new Error(
`Argument appears to not be a ReactComponent. Keys: ${keys}`,
);
}
}
const hostFiber = findCurrentHostFiber(fiber);
if (hostFiber === null) {
return null;
}
if (hostFiber.mode & StrictLegacyMode) {
const componentName = getComponentNameFromFiber(fiber) || 'Component';
if (!didWarnAboutFindNodeInStrictMode[componentName]) {
didWarnAboutFindNodeInStrictMode[componentName] = true;
const previousFiber = ReactCurrentFiberCurrent;
try {
setCurrentDebugFiberInDEV(hostFiber);
if (fiber.mode & StrictLegacyMode) {
console.error(
'%s is deprecated in StrictMode. ' +
'%s was passed an instance of %s which is inside StrictMode. ' +
'Instead, add a ref directly to the element you want to reference. ' +
'Learn more about using refs safely here: ' +
'https://reactjs.org/link/strict-mode-find-node',
methodName,
methodName,
componentName,
);
} else {
console.error(
'%s is deprecated in StrictMode. ' +
'%s was passed an instance of %s which renders StrictMode children. ' +
'Instead, add a ref directly to the element you want to reference. ' +
'Learn more about using refs safely here: ' +
'https://reactjs.org/link/strict-mode-find-node',
methodName,
methodName,
componentName,
);
}
} finally {
// Ideally this should reset to previous but this shouldn't be called in
// render and there's another warning for that anyway.
if (previousFiber) {
setCurrentDebugFiberInDEV(previousFiber);
} else {
resetCurrentDebugFiberInDEV();
}
}
}
}
return hostFiber.stateNode;
}
return findHostInstance(component);
}
export function createContainer(
containerInfo: Container,
tag: RootTag,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
identifierPrefix: string,
onRecoverableError: (error: mixed) => void,
transitionCallbacks: null | TransitionTracingCallbacks,
): OpaqueRoot {
const hydrate = false;
const initialChildren = null;
return createFiberRoot(
containerInfo,
tag,
hydrate,
initialChildren,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onRecoverableError,
transitionCallbacks,
);
}
export function createHydrationContainer(
initialChildren: ReactNodeList,
// TODO: Remove `callback` when we delete legacy mode.
callback: ?Function,
containerInfo: Container,
tag: RootTag,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
identifierPrefix: string,
onRecoverableError: (error: mixed) => void,
transitionCallbacks: null | TransitionTracingCallbacks,
): OpaqueRoot {
const hydrate = true;
const root = createFiberRoot(
containerInfo,
tag,
hydrate,
initialChildren,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onRecoverableError,
transitionCallbacks,
);
// TODO: Move this to FiberRoot constructor
root.context = getContextForSubtree(null);
// Schedule the initial render. In a hydration root, this is different from
// a regular update because the initial render must match was was rendered
// on the server.
// NOTE: This update intentionally doesn't have a payload. We're only using
// the update to schedule work on the root fiber (and, for legacy roots, to
// enqueue the callback if one is provided).
const current = root.current;
const eventTime = requestEventTime();
const lane = requestUpdateLane(current);
const update = createUpdate(eventTime, lane);
update.callback =
callback !== undefined && callback !== null ? callback : null;
enqueueUpdate(current, update, lane);
scheduleInitialHydrationOnRoot(root, lane, eventTime);
return root;
}
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): Lane {
if (__DEV__) {
onScheduleRoot(container, element);
}
const current = container.current;
const eventTime = requestEventTime();
const lane = requestUpdateLane(current);
if (enableSchedulingProfiler) {
markRenderScheduled(lane);
}
const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
if (__DEV__) {
if (
ReactCurrentFiberIsRendering &&
ReactCurrentFiberCurrent !== null &&
!didWarnAboutNestedUpdates
) {
didWarnAboutNestedUpdates = true;
console.error(
'Render methods should be a pure function of props and state; ' +
'triggering nested component updates from render is not allowed. ' +
'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
'Check the render method of %s.',
getComponentNameFromFiber(ReactCurrentFiberCurrent) || 'Unknown',
);
}
}
const update = createUpdate(eventTime, lane);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element};
callback = callback === undefined ? null : callback;
if (callback !== null) {
if (__DEV__) {
if (typeof callback !== 'function') {
console.error(
'render(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callback,
);
}
}
update.callback = callback;
}
const root = enqueueUpdate(current, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, current, lane, eventTime);
entangleTransitions(root, current, lane);
}
return lane;
}
export {
batchedUpdates,
deferredUpdates,
discreteUpdates,
flushControlled,
flushSync,
isAlreadyRendering,
flushPassiveEffects,
};
export function getPublicRootInstance(
container: OpaqueRoot,
): React$Component<any, any> | PublicInstance | null {
const containerFiber = container.current;
if (!containerFiber.child) {
return null;
}
switch (containerFiber.child.tag) {
case HostSingleton:
case HostComponent:
return getPublicInstance(containerFiber.child.stateNode);
default:
return containerFiber.child.stateNode;
}
}
export function attemptSynchronousHydration(fiber: Fiber): void {
switch (fiber.tag) {
case HostRoot: {
const root: FiberRoot = fiber.stateNode;
if (isRootDehydrated(root)) {
// Flush the first scheduled "update".
const lanes = getHighestPriorityPendingLanes(root);
flushRoot(root, lanes);
}
break;
}
case SuspenseComponent: {
flushSync(() => {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, SyncLane, eventTime);
}
});
// If we're still blocked after this, we need to increase
// the priority of any promises resolving within this
// boundary so that they next attempt also has higher pri.
const retryLane = SyncLane;
markRetryLaneIfNotHydrated(fiber, retryLane);
break;
}
}
}
function markRetryLaneImpl(fiber: Fiber, retryLane: Lane) {
const suspenseState: null | SuspenseState = fiber.memoizedState;
if (suspenseState !== null && suspenseState.dehydrated !== null) {
suspenseState.retryLane = higherPriorityLane(
suspenseState.retryLane,
retryLane,
);
}
}
// Increases the priority of thenables when they resolve within this boundary.
function markRetryLaneIfNotHydrated(fiber: Fiber, retryLane: Lane) {
markRetryLaneImpl(fiber, retryLane);
const alternate = fiber.alternate;
if (alternate) {
markRetryLaneImpl(alternate, retryLane);
}
}
export function attemptDiscreteHydration(fiber: Fiber): void {
if (fiber.tag !== SuspenseComponent) {
// We ignore HostRoots here because we can't increase
// their priority and they should not suspend on I/O,
// since you have to wrap anything that might suspend in
// Suspense.
return;
}
const lane = SyncLane;
const root = enqueueConcurrentRenderForLane(fiber, lane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
}
markRetryLaneIfNotHydrated(fiber, lane);
}
export function attemptContinuousHydration(fiber: Fiber): void {
if (fiber.tag !== SuspenseComponent) {
// We ignore HostRoots here because we can't increase
// their priority and they should not suspend on I/O,
// since you have to wrap anything that might suspend in
// Suspense.
return;
}
const lane = SelectiveHydrationLane;
const root = enqueueConcurrentRenderForLane(fiber, lane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
}
markRetryLaneIfNotHydrated(fiber, lane);
}
export function attemptHydrationAtCurrentPriority(fiber: Fiber): void {
if (fiber.tag !== SuspenseComponent) {
// We ignore HostRoots here because we can't increase
// their priority other than synchronously flush it.
return;
}
const lane = requestUpdateLane(fiber);
const root = enqueueConcurrentRenderForLane(fiber, lane);
if (root !== null) {
const eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
}
markRetryLaneIfNotHydrated(fiber, lane);
}
export {getCurrentUpdatePriority, runWithPriority};
export {findHostInstance};
export {findHostInstanceWithWarning};
export function findHostInstanceWithNoPortals(
fiber: Fiber,
): PublicInstance | null {
const hostFiber = findCurrentHostFiberWithNoPortals(fiber);
if (hostFiber === null) {
return null;
}
return hostFiber.stateNode;
}
let shouldErrorImpl: Fiber => ?boolean = fiber => null;
export function shouldError(fiber: Fiber): ?boolean {
return shouldErrorImpl(fiber);
}
let shouldSuspendImpl = fiber => false;
export function shouldSuspend(fiber: Fiber): boolean {
return shouldSuspendImpl(fiber);
}
let overrideHookState = null;
let overrideHookStateDeletePath = null;
let overrideHookStateRenamePath = null;
let overrideProps = null;
let overridePropsDeletePath = null;
let overridePropsRenamePath = null;
let scheduleUpdate = null;
let setErrorHandler = null;
let setSuspenseHandler = null;
if (__DEV__) {
const copyWithDeleteImpl = (
obj: Object | Array<any>,
path: Array<string | number>,
index: number,
) => {
const key = path[index];
const updated = isArray(obj) ? obj.slice() : {...obj};
if (index + 1 === path.length) {
if (isArray(updated)) {
updated.splice(((key: any): number), 1);
} else {
delete updated[key];
}
return updated;
}
// $FlowFixMe number or string is fine here
updated[key] = copyWithDeleteImpl(obj[key], path, index + 1);
return updated;
};
const copyWithDelete = (
obj: Object | Array<any>,
path: Array<string | number>,
): Object | Array<any> => {
return copyWithDeleteImpl(obj, path, 0);
};
const copyWithRenameImpl = (
obj: Object | Array<any>,
oldPath: Array<string | number>,
newPath: Array<string | number>,
index: number,
) => {
const oldKey = oldPath[index];
const updated = isArray(obj) ? obj.slice() : {...obj};
if (index + 1 === oldPath.length) {
const newKey = newPath[index];
// $FlowFixMe number or string is fine here
updated[newKey] = updated[oldKey];
if (isArray(updated)) {
updated.splice(((oldKey: any): number), 1);
} else {
delete updated[oldKey];
}
} else {
// $FlowFixMe number or string is fine here
updated[oldKey] = copyWithRenameImpl(
// $FlowFixMe number or string is fine here
obj[oldKey],
oldPath,
newPath,
index + 1,
);
}
return updated;
};
const copyWithRename = (
obj: Object | Array<any>,
oldPath: Array<string | number>,
newPath: Array<string | number>,
): Object | Array<any> => {
if (oldPath.length !== newPath.length) {
console.warn('copyWithRename() expects paths of the same length');
return;
} else {
for (let i = 0; i < newPath.length - 1; i++) {
if (oldPath[i] !== newPath[i]) {
console.warn(
'copyWithRename() expects paths to be the same except for the deepest key',
);
return;
}
}
}
return copyWithRenameImpl(obj, oldPath, newPath, 0);
};
const copyWithSetImpl = (
obj: Object | Array<any>,
path: Array<string | number>,
index: number,
value: any,
) => {
if (index >= path.length) {
return value;
}
const key = path[index];
const updated = isArray(obj) ? obj.slice() : {...obj};
// $FlowFixMe number or string is fine here
updated[key] = copyWithSetImpl(obj[key], path, index + 1, value);
return updated;
};
const copyWithSet = (
obj: Object | Array<any>,
path: Array<string | number>,
value: any,
): Object | Array<any> => {
return copyWithSetImpl(obj, path, 0, value);
};
const findHook = (fiber: Fiber, id: number) => {
// For now, the "id" of stateful hooks is just the stateful hook index.
// This may change in the future with e.g. nested hooks.
let currentHook = fiber.memoizedState;
while (currentHook !== null && id > 0) {
currentHook = currentHook.next;
id--;
}
return currentHook;
};
// Support DevTools editable values for useState and useReducer.
overrideHookState = (
fiber: Fiber,
id: number,
path: Array<string | number>,
value: any,
) => {
const hook = findHook(fiber, id);
if (hook !== null) {
const newState = copyWithSet(hook.memoizedState, path, value);
hook.memoizedState = newState;
hook.baseState = newState;
// We aren't actually adding an update to the queue,
// because there is no update we can add for useReducer hooks that won't trigger an error.
// (There's no appropriate action type for DevTools overrides.)
// As a result though, React will see the scheduled update as a noop and bailout.
// Shallow cloning props works as a workaround for now to bypass the bailout check.
fiber.memoizedProps = {...fiber.memoizedProps};
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
}
}
};
overrideHookStateDeletePath = (
fiber: Fiber,
id: number,
path: Array<string | number>,
) => {
const hook = findHook(fiber, id);
if (hook !== null) {
const newState = copyWithDelete(hook.memoizedState, path);
hook.memoizedState = newState;
hook.baseState = newState;
// We aren't actually adding an update to the queue,
// because there is no update we can add for useReducer hooks that won't trigger an error.
// (There's no appropriate action type for DevTools overrides.)
// As a result though, React will see the scheduled update as a noop and bailout.
// Shallow cloning props works as a workaround for now to bypass the bailout check.
fiber.memoizedProps = {...fiber.memoizedProps};
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
}
}
};
overrideHookStateRenamePath = (
fiber: Fiber,
id: number,
oldPath: Array<string | number>,
newPath: Array<string | number>,
) => {
const hook = findHook(fiber, id);
if (hook !== null) {
const newState = copyWithRename(hook.memoizedState, oldPath, newPath);
hook.memoizedState = newState;
hook.baseState = newState;
// We aren't actually adding an update to the queue,
// because there is no update we can add for useReducer hooks that won't trigger an error.
// (There's no appropriate action type for DevTools overrides.)
// As a result though, React will see the scheduled update as a noop and bailout.
// Shallow cloning props works as a workaround for now to bypass the bailout check.
fiber.memoizedProps = {...fiber.memoizedProps};
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
}
}
};
// Support DevTools props for function components, forwardRef, memo, host components, etc.
overrideProps = (fiber: Fiber, path: Array<string | number>, value: any) => {
fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value);
if (fiber.alternate) {
fiber.alternate.pendingProps = fiber.pendingProps;
}
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
}
};
overridePropsDeletePath = (fiber: Fiber, path: Array<string | number>) => {
fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path);
if (fiber.alternate) {
fiber.alternate.pendingProps = fiber.pendingProps;
}
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
}
};
overridePropsRenamePath = (
fiber: Fiber,
oldPath: Array<string | number>,
newPath: Array<string | number>,
) => {
fiber.pendingProps = copyWithRename(fiber.memoizedProps, oldPath, newPath);
if (fiber.alternate) {
fiber.alternate.pendingProps = fiber.pendingProps;
}
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
}
};
scheduleUpdate = (fiber: Fiber) => {
const root = enqueueConcurrentRenderForLane(fiber, SyncLane);
if (root !== null) {
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp);
}
};
setErrorHandler = (newShouldErrorImpl: Fiber => ?boolean) => {
shouldErrorImpl = newShouldErrorImpl;
};
setSuspenseHandler = (newShouldSuspendImpl: Fiber => boolean) => {
shouldSuspendImpl = newShouldSuspendImpl;
};
}
function findHostInstanceByFiber(fiber: Fiber): Instance | TextInstance | null {
const hostFiber = findCurrentHostFiber(fiber);
if (hostFiber === null) {
return null;
}
return hostFiber.stateNode;
}
function emptyFindFiberByHostInstance(
instance: Instance | TextInstance,
): Fiber | null {
return null;
}
function getCurrentFiberForDevTools() {
return ReactCurrentFiberCurrent;
}
export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean {
const {findFiberByHostInstance} = devToolsConfig;
const {ReactCurrentDispatcher} = ReactSharedInternals;
return injectInternals({
bundleType: devToolsConfig.bundleType,
version: devToolsConfig.version,
rendererPackageName: devToolsConfig.rendererPackageName,
rendererConfig: devToolsConfig.rendererConfig,
overrideHookState,
overrideHookStateDeletePath,
overrideHookStateRenamePath,
overrideProps,
overridePropsDeletePath,
overridePropsRenamePath,
setErrorHandler,
setSuspenseHandler,
scheduleUpdate,
currentDispatcherRef: ReactCurrentDispatcher,
findHostInstanceByFiber,
findFiberByHostInstance:
findFiberByHostInstance || emptyFindFiberByHostInstance,
// React Refresh
findHostInstancesForRefresh: __DEV__ ? findHostInstancesForRefresh : null,
scheduleRefresh: __DEV__ ? scheduleRefresh : null,
scheduleRoot: __DEV__ ? scheduleRoot : null,
setRefreshHandler: __DEV__ ? setRefreshHandler : null,
// Enables DevTools to append owner stacks to error messages in DEV mode.
getCurrentFiber: __DEV__ ? getCurrentFiberForDevTools : null,
// Enables DevTools to detect reconciler version rather than renderer version
// which may not match for third party renderers.
reconcilerVersion: ReactVersion,
});
}

View File

@ -1,204 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ReactNodeList} from 'shared/ReactTypes';
import type {
FiberRoot,
SuspenseHydrationCallbacks,
TransitionTracingCallbacks,
} from './ReactInternalTypes';
import type {RootTag} from './ReactRootTags';
import type {Cache} from './ReactFiberCacheComponent.new';
import type {Container} from './ReactFiberHostConfig';
import {noTimeout, supportsHydration} from './ReactFiberHostConfig';
import {createHostRootFiber} from './ReactFiber.new';
import {
NoLane,
NoLanes,
NoTimestamp,
TotalLanes,
createLaneMap,
} from './ReactFiberLane.new';
import {
enableSuspenseCallback,
enableCache,
enableProfilerCommitHooks,
enableProfilerTimer,
enableUpdaterTracking,
enableTransitionTracing,
} from 'shared/ReactFeatureFlags';
import {initializeUpdateQueue} from './ReactFiberClassUpdateQueue.new';
import {LegacyRoot, ConcurrentRoot} from './ReactRootTags';
import {createCache, retainCache} from './ReactFiberCacheComponent.new';
export type RootState = {
element: any,
isDehydrated: boolean,
cache: Cache,
};
function FiberRootNode(
containerInfo,
tag,
hydrate,
identifierPrefix,
onRecoverableError,
) {
this.tag = tag;
this.containerInfo = containerInfo;
this.pendingChildren = null;
this.current = null;
this.pingCache = null;
this.finishedWork = null;
this.timeoutHandle = noTimeout;
this.context = null;
this.pendingContext = null;
this.callbackNode = null;
this.callbackPriority = NoLane;
this.eventTimes = createLaneMap(NoLanes);
this.expirationTimes = createLaneMap(NoTimestamp);
this.pendingLanes = NoLanes;
this.suspendedLanes = NoLanes;
this.pingedLanes = NoLanes;
this.expiredLanes = NoLanes;
this.mutableReadLanes = NoLanes;
this.finishedLanes = NoLanes;
this.errorRecoveryDisabledLanes = NoLanes;
this.entangledLanes = NoLanes;
this.entanglements = createLaneMap(NoLanes);
this.hiddenUpdates = createLaneMap(null);
this.identifierPrefix = identifierPrefix;
this.onRecoverableError = onRecoverableError;
if (enableCache) {
this.pooledCache = null;
this.pooledCacheLanes = NoLanes;
}
if (supportsHydration) {
this.mutableSourceEagerHydrationData = null;
}
if (enableSuspenseCallback) {
this.hydrationCallbacks = null;
}
this.incompleteTransitions = new Map();
if (enableTransitionTracing) {
this.transitionCallbacks = null;
const transitionLanesMap = (this.transitionLanes = []);
for (let i = 0; i < TotalLanes; i++) {
transitionLanesMap.push(null);
}
}
if (enableProfilerTimer && enableProfilerCommitHooks) {
this.effectDuration = 0;
this.passiveEffectDuration = 0;
}
if (enableUpdaterTracking) {
this.memoizedUpdaters = new Set();
const pendingUpdatersLaneMap = (this.pendingUpdatersLaneMap = []);
for (let i = 0; i < TotalLanes; i++) {
pendingUpdatersLaneMap.push(new Set());
}
}
if (__DEV__) {
switch (tag) {
case ConcurrentRoot:
this._debugRootType = hydrate ? 'hydrateRoot()' : 'createRoot()';
break;
case LegacyRoot:
this._debugRootType = hydrate ? 'hydrate()' : 'render()';
break;
}
}
}
export function createFiberRoot(
containerInfo: Container,
tag: RootTag,
hydrate: boolean,
initialChildren: ReactNodeList,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
// TODO: We have several of these arguments that are conceptually part of the
// host config, but because they are passed in at runtime, we have to thread
// them through the root constructor. Perhaps we should put them all into a
// single type, like a DynamicHostConfig that is defined by the renderer.
identifierPrefix: string,
onRecoverableError: null | ((error: mixed) => void),
transitionCallbacks: null | TransitionTracingCallbacks,
): FiberRoot {
// $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
const root: FiberRoot = (new FiberRootNode(
containerInfo,
tag,
hydrate,
identifierPrefix,
onRecoverableError,
): any);
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
}
if (enableTransitionTracing) {
root.transitionCallbacks = transitionCallbacks;
}
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber(
tag,
isStrictMode,
concurrentUpdatesByDefaultOverride,
);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
if (enableCache) {
const initialCache = createCache();
retainCache(initialCache);
// The pooledCache is a fresh cache instance that is used temporarily
// for newly mounted boundaries during a render. In general, the
// pooledCache is always cleared from the root at the end of a render:
// it is either released when render commits, or moved to an Offscreen
// component if rendering suspends. Because the lifetime of the pooled
// cache is distinct from the main memoizedState.cache, it must be
// retained separately.
root.pooledCache = initialCache;
retainCache(initialCache);
const initialState: RootState = {
element: initialChildren,
isDehydrated: hydrate,
cache: initialCache,
};
uninitializedFiber.memoizedState = initialState;
} else {
const initialState: RootState = {
element: initialChildren,
isDehydrated: hydrate,
cache: (null: any), // not enabled yet
};
uninitializedFiber.memoizedState = initialState;
}
initializeUpdateQueue(uninitializedFiber);
return root;
}

View File

@ -1,199 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber} from './ReactInternalTypes';
import type {
ReactScopeInstance,
ReactContext,
ReactScopeQuery,
} from 'shared/ReactTypes';
import {
getPublicInstance,
getInstanceFromNode,
getInstanceFromScope,
} from './ReactFiberHostConfig';
import {isFiberSuspenseAndTimedOut} from './ReactFiberTreeReflection';
import {HostComponent, ScopeComponent, ContextProvider} from './ReactWorkTags';
import {enableScopeAPI} from 'shared/ReactFeatureFlags';
function getSuspenseFallbackChild(fiber: Fiber): Fiber | null {
return ((((fiber.child: any): Fiber).sibling: any): Fiber).child;
}
const emptyObject = {};
function collectScopedNodes(
node: Fiber,
fn: ReactScopeQuery,
scopedNodes: Array<any>,
): void {
if (enableScopeAPI) {
if (node.tag === HostComponent) {
const {type, memoizedProps, stateNode} = node;
const instance = getPublicInstance(stateNode);
if (
instance !== null &&
fn(type, memoizedProps || emptyObject, instance) === true
) {
scopedNodes.push(instance);
}
}
let child = node.child;
if (isFiberSuspenseAndTimedOut(node)) {
child = getSuspenseFallbackChild(node);
}
if (child !== null) {
collectScopedNodesFromChildren(child, fn, scopedNodes);
}
}
}
function collectFirstScopedNode(
node: Fiber,
fn: ReactScopeQuery,
): null | Object {
if (enableScopeAPI) {
if (node.tag === HostComponent) {
const {type, memoizedProps, stateNode} = node;
const instance = getPublicInstance(stateNode);
if (instance !== null && fn(type, memoizedProps, instance) === true) {
return instance;
}
}
let child = node.child;
if (isFiberSuspenseAndTimedOut(node)) {
child = getSuspenseFallbackChild(node);
}
if (child !== null) {
return collectFirstScopedNodeFromChildren(child, fn);
}
}
return null;
}
function collectScopedNodesFromChildren(
startingChild: Fiber,
fn: ReactScopeQuery,
scopedNodes: Array<any>,
): void {
let child: null | Fiber = startingChild;
while (child !== null) {
collectScopedNodes(child, fn, scopedNodes);
child = child.sibling;
}
}
function collectFirstScopedNodeFromChildren(
startingChild: Fiber,
fn: ReactScopeQuery,
): Object | null {
let child: null | Fiber = startingChild;
while (child !== null) {
const scopedNode = collectFirstScopedNode(child, fn);
if (scopedNode !== null) {
return scopedNode;
}
child = child.sibling;
}
return null;
}
function collectNearestContextValues<T>(
node: Fiber,
context: ReactContext<T>,
childContextValues: Array<T>,
): void {
if (node.tag === ContextProvider && node.type._context === context) {
const contextValue = node.memoizedProps.value;
childContextValues.push(contextValue);
} else {
let child = node.child;
if (isFiberSuspenseAndTimedOut(node)) {
child = getSuspenseFallbackChild(node);
}
if (child !== null) {
collectNearestChildContextValues(child, context, childContextValues);
}
}
}
function collectNearestChildContextValues<T>(
startingChild: Fiber | null,
context: ReactContext<T>,
childContextValues: Array<T>,
): void {
let child = startingChild;
while (child !== null) {
collectNearestContextValues(child, context, childContextValues);
child = child.sibling;
}
}
function DO_NOT_USE_queryAllNodes(fn: ReactScopeQuery): null | Array<Object> {
const currentFiber = getInstanceFromScope(this);
if (currentFiber === null) {
return null;
}
const child = currentFiber.child;
const scopedNodes = [];
if (child !== null) {
collectScopedNodesFromChildren(child, fn, scopedNodes);
}
return scopedNodes.length === 0 ? null : scopedNodes;
}
function DO_NOT_USE_queryFirstNode(fn: ReactScopeQuery): null | Object {
const currentFiber = getInstanceFromScope(this);
if (currentFiber === null) {
return null;
}
const child = currentFiber.child;
if (child !== null) {
return collectFirstScopedNodeFromChildren(child, fn);
}
return null;
}
function containsNode(node: Object): boolean {
let fiber = getInstanceFromNode(node);
while (fiber !== null) {
if (fiber.tag === ScopeComponent && fiber.stateNode === this) {
return true;
}
fiber = fiber.return;
}
return false;
}
function getChildContextValues<T>(context: ReactContext<T>): Array<T> {
const currentFiber = getInstanceFromScope(this);
if (currentFiber === null) {
return [];
}
const child = currentFiber.child;
const childContextValues = [];
if (child !== null) {
collectNearestChildContextValues(child, context, childContextValues);
}
return childContextValues;
}
export function createScopeInstance(): ReactScopeInstance {
return {
DO_NOT_USE_queryAllNodes,
DO_NOT_USE_queryFirstNode,
containsNode,
getChildContextValues,
};
}

View File

@ -8,7 +8,7 @@
*/
import type {FiberRoot} from './ReactInternalTypes';
import type {RootState} from './ReactFiberRoot.new';
import type {RootState} from './ReactFiberRoot.old';
// This is imported by the event replaying implementation in React DOM. It's
// in a separate file to break a circular dependency between the renderer and

View File

@ -1,97 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber} from './ReactInternalTypes';
export type StackCursor<T> = {current: T};
const valueStack: Array<any> = [];
let fiberStack: Array<Fiber | null>;
if (__DEV__) {
fiberStack = [];
}
let index = -1;
function createCursor<T>(defaultValue: T): StackCursor<T> {
return {
current: defaultValue,
};
}
function isEmpty(): boolean {
return index === -1;
}
function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
if (index < 0) {
if (__DEV__) {
console.error('Unexpected pop.');
}
return;
}
if (__DEV__) {
if (fiber !== fiberStack[index]) {
console.error('Unexpected Fiber popped.');
}
}
cursor.current = valueStack[index];
valueStack[index] = null;
if (__DEV__) {
fiberStack[index] = null;
}
index--;
}
function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
index++;
valueStack[index] = cursor.current;
if (__DEV__) {
fiberStack[index] = fiber;
}
cursor.current = value;
}
function checkThatStackIsEmpty() {
if (__DEV__) {
if (index !== -1) {
console.error(
'Expected an empty stack. Something was not reset properly.',
);
}
}
}
function resetStackAfterFatalErrorInDev() {
if (__DEV__) {
index = -1;
valueStack.length = 0;
fiberStack.length = 0;
}
}
export {
createCursor,
isEmpty,
pop,
push,
// DEV only:
checkThatStackIsEmpty,
resetStackAfterFatalErrorInDev,
};

View File

@ -1,113 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ReactNodeList, Wakeable} from 'shared/ReactTypes';
import type {Fiber} from './ReactInternalTypes';
import type {SuspenseInstance} from './ReactFiberHostConfig';
import type {Lane} from './ReactFiberLane.new';
import type {TreeContext} from './ReactFiberTreeContext.new';
import {SuspenseComponent, SuspenseListComponent} from './ReactWorkTags';
import {NoFlags, DidCapture} from './ReactFiberFlags';
import {
isSuspenseInstancePending,
isSuspenseInstanceFallback,
} from './ReactFiberHostConfig';
export type SuspenseProps = {
children?: ReactNodeList,
fallback?: ReactNodeList,
// TODO: Add "unstable_" prefix?
suspenseCallback?: (Set<Wakeable> | null) => mixed,
unstable_avoidThisFallback?: boolean,
unstable_expectedLoadTime?: number,
unstable_name?: string,
};
// A null SuspenseState represents an unsuspended normal Suspense boundary.
// A non-null SuspenseState means that it is blocked for one reason or another.
// - A non-null dehydrated field means it's blocked pending hydration.
// - A non-null dehydrated field can use isSuspenseInstancePending or
// isSuspenseInstanceFallback to query the reason for being dehydrated.
// - A null dehydrated field means it's blocked by something suspending and
// we're currently showing a fallback instead.
export type SuspenseState = {
// If this boundary is still dehydrated, we store the SuspenseInstance
// here to indicate that it is dehydrated (flag) and for quick access
// to check things like isSuspenseInstancePending.
dehydrated: null | SuspenseInstance,
treeContext: null | TreeContext,
// Represents the lane we should attempt to hydrate a dehydrated boundary at.
// OffscreenLane is the default for dehydrated boundaries.
// NoLane is the default for normal boundaries, which turns into "normal" pri.
retryLane: Lane,
};
export type SuspenseListTailMode = 'collapsed' | 'hidden' | void;
export type SuspenseListRenderState = {
isBackwards: boolean,
// The currently rendering tail row.
rendering: null | Fiber,
// The absolute time when we started rendering the most recent tail row.
renderingStartTime: number,
// The last of the already rendered children.
last: null | Fiber,
// Remaining rows on the tail of the list.
tail: null | Fiber,
// Tail insertions setting.
tailMode: SuspenseListTailMode,
};
export function findFirstSuspended(row: Fiber): null | Fiber {
let node = row;
while (node !== null) {
if (node.tag === SuspenseComponent) {
const state: SuspenseState | null = node.memoizedState;
if (state !== null) {
const dehydrated: null | SuspenseInstance = state.dehydrated;
if (
dehydrated === null ||
isSuspenseInstancePending(dehydrated) ||
isSuspenseInstanceFallback(dehydrated)
) {
return node;
}
}
} else if (
node.tag === SuspenseListComponent &&
// revealOrder undefined can't be trusted because it don't
// keep track of whether it suspended or not.
node.memoizedProps.revealOrder !== undefined
) {
const didSuspend = (node.flags & DidCapture) !== NoFlags;
if (didSuspend) {
return node;
}
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === row) {
return null;
}
while (node.sibling === null) {
if (node.return === null || node.return === row) {
return null;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
return null;
}

View File

@ -1,182 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber} from './ReactInternalTypes';
import type {StackCursor} from './ReactFiberStack.new';
import type {
SuspenseState,
SuspenseProps,
} from './ReactFiberSuspenseComponent.new';
import {enableSuspenseAvoidThisFallback} from 'shared/ReactFeatureFlags';
import {createCursor, push, pop} from './ReactFiberStack.new';
import {isCurrentTreeHidden} from './ReactFiberHiddenContext.new';
import {SuspenseComponent, OffscreenComponent} from './ReactWorkTags';
// The Suspense handler is the boundary that should capture if something
// suspends, i.e. it's the nearest `catch` block on the stack.
const suspenseHandlerStackCursor: StackCursor<Fiber | null> = createCursor(
null,
);
function shouldAvoidedBoundaryCapture(
workInProgress: Fiber,
handlerOnStack: Fiber,
props: any,
): boolean {
if (enableSuspenseAvoidThisFallback) {
// If the parent is already showing content, and we're not inside a hidden
// tree, then we should show the avoided fallback.
if (handlerOnStack.alternate !== null && !isCurrentTreeHidden()) {
return true;
}
// If the handler on the stack is also an avoided boundary, then we should
// favor this inner one.
if (
handlerOnStack.tag === SuspenseComponent &&
handlerOnStack.memoizedProps.unstable_avoidThisFallback === true
) {
return true;
}
// If this avoided boundary is dehydrated, then it should capture.
const suspenseState: SuspenseState | null = workInProgress.memoizedState;
if (suspenseState !== null && suspenseState.dehydrated !== null) {
return true;
}
}
// If none of those cases apply, then we should avoid this fallback and show
// the outer one instead.
return false;
}
export function isBadSuspenseFallback(
current: Fiber | null,
nextProps: SuspenseProps,
): boolean {
// Check if this is a "bad" fallback state or a good one. A bad fallback state
// is one that we only show as a last resort; if this is a transition, we'll
// block it from displaying, and wait for more data to arrive.
if (current !== null) {
const prevState: SuspenseState = current.memoizedState;
const isShowingFallback = prevState !== null;
if (!isShowingFallback && !isCurrentTreeHidden()) {
// It's bad to switch to a fallback if content is already visible
return true;
}
}
if (
enableSuspenseAvoidThisFallback &&
nextProps.unstable_avoidThisFallback === true
) {
// Experimental: Some fallbacks are always bad
return true;
}
return false;
}
export function pushPrimaryTreeSuspenseHandler(handler: Fiber): void {
const props = handler.pendingProps;
const handlerOnStack = suspenseHandlerStackCursor.current;
if (
enableSuspenseAvoidThisFallback &&
props.unstable_avoidThisFallback === true &&
handlerOnStack !== null &&
!shouldAvoidedBoundaryCapture(handler, handlerOnStack, props)
) {
// This boundary should not capture if something suspends. Reuse the
// existing handler on the stack.
push(suspenseHandlerStackCursor, handlerOnStack, handler);
} else {
// Push this handler onto the stack.
push(suspenseHandlerStackCursor, handler, handler);
}
}
export function pushFallbackTreeSuspenseHandler(fiber: Fiber): void {
// We're about to render the fallback. If something in the fallback suspends,
// it's akin to throwing inside of a `catch` block. This boundary should not
// capture. Reuse the existing handler on the stack.
reuseSuspenseHandlerOnStack(fiber);
}
export function pushOffscreenSuspenseHandler(fiber: Fiber): void {
if (fiber.tag === OffscreenComponent) {
push(suspenseHandlerStackCursor, fiber, fiber);
} else {
// This is a LegacyHidden component.
reuseSuspenseHandlerOnStack(fiber);
}
}
export function reuseSuspenseHandlerOnStack(fiber: Fiber) {
push(suspenseHandlerStackCursor, getSuspenseHandler(), fiber);
}
export function getSuspenseHandler(): Fiber | null {
return suspenseHandlerStackCursor.current;
}
export function popSuspenseHandler(fiber: Fiber): void {
pop(suspenseHandlerStackCursor, fiber);
}
// SuspenseList context
// TODO: Move to a separate module? We may change the SuspenseList
// implementation to hide/show in the commit phase, anyway.
export opaque type SuspenseContext = number;
export opaque type SubtreeSuspenseContext: SuspenseContext = number;
export opaque type ShallowSuspenseContext: SuspenseContext = number;
const DefaultSuspenseContext: SuspenseContext = 0b00;
const SubtreeSuspenseContextMask: SuspenseContext = 0b01;
// ForceSuspenseFallback can be used by SuspenseList to force newly added
// items into their fallback state during one of the render passes.
export const ForceSuspenseFallback: ShallowSuspenseContext = 0b10;
export const suspenseStackCursor: StackCursor<SuspenseContext> = createCursor(
DefaultSuspenseContext,
);
export function hasSuspenseListContext(
parentContext: SuspenseContext,
flag: SuspenseContext,
): boolean {
return (parentContext & flag) !== 0;
}
export function setDefaultShallowSuspenseListContext(
parentContext: SuspenseContext,
): SuspenseContext {
return parentContext & SubtreeSuspenseContextMask;
}
export function setShallowSuspenseListContext(
parentContext: SuspenseContext,
shallowContext: ShallowSuspenseContext,
): SuspenseContext {
return (parentContext & SubtreeSuspenseContextMask) | shallowContext;
}
export function pushSuspenseListContext(
fiber: Fiber,
newContext: SuspenseContext,
): void {
push(suspenseStackCursor, newContext, fiber);
}
export function popSuspenseListContext(fiber: Fiber): void {
pop(suspenseStackCursor, fiber);
}

View File

@ -1,88 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {SchedulerCallback} from './Scheduler';
import {
DiscreteEventPriority,
getCurrentUpdatePriority,
setCurrentUpdatePriority,
} from './ReactEventPriorities.new';
import {ImmediatePriority, scheduleCallback} from './Scheduler';
let syncQueue: Array<SchedulerCallback> | null = null;
let includesLegacySyncCallbacks: boolean = false;
let isFlushingSyncQueue: boolean = false;
export function scheduleSyncCallback(callback: SchedulerCallback) {
// Push this callback into an internal queue. We'll flush these either in
// the next tick, or earlier if something calls `flushSyncCallbackQueue`.
if (syncQueue === null) {
syncQueue = [callback];
} else {
// Push onto existing queue. Don't need to schedule a callback because
// we already scheduled one when we created the queue.
syncQueue.push(callback);
}
}
export function scheduleLegacySyncCallback(callback: SchedulerCallback) {
includesLegacySyncCallbacks = true;
scheduleSyncCallback(callback);
}
export function flushSyncCallbacksOnlyInLegacyMode() {
// Only flushes the queue if there's a legacy sync callback scheduled.
// TODO: There's only a single type of callback: performSyncOnWorkOnRoot. So
// it might make more sense for the queue to be a list of roots instead of a
// list of generic callbacks. Then we can have two: one for legacy roots, one
// for concurrent roots. And this method would only flush the legacy ones.
if (includesLegacySyncCallbacks) {
flushSyncCallbacks();
}
}
export function flushSyncCallbacks(): null {
if (!isFlushingSyncQueue && syncQueue !== null) {
// Prevent re-entrance.
isFlushingSyncQueue = true;
let i = 0;
const previousUpdatePriority = getCurrentUpdatePriority();
try {
const isSync = true;
const queue = syncQueue;
// TODO: Is this necessary anymore? The only user code that runs in this
// queue is in the render or commit phases.
setCurrentUpdatePriority(DiscreteEventPriority);
// $FlowFixMe[incompatible-use] found when upgrading Flow
for (; i < queue.length; i++) {
// $FlowFixMe[incompatible-use] found when upgrading Flow
let callback: SchedulerCallback = queue[i];
do {
// $FlowFixMe[incompatible-type] we bail out when we get a null
callback = callback(isSync);
} while (callback !== null);
}
syncQueue = null;
includesLegacySyncCallbacks = false;
} catch (error) {
// If something throws, leave the remaining callbacks on the queue.
if (syncQueue !== null) {
syncQueue = syncQueue.slice(i + 1);
}
// Resume flushing in the next tick
scheduleCallback(ImmediatePriority, flushSyncCallbacks);
throw error;
} finally {
setCurrentUpdatePriority(previousUpdatePriority);
isFlushingSyncQueue = false;
}
}
return null;
}

View File

@ -1,175 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {
Thenable,
PendingThenable,
FulfilledThenable,
RejectedThenable,
} from 'shared/ReactTypes';
import ReactSharedInternals from 'shared/ReactSharedInternals';
const {ReactCurrentActQueue} = ReactSharedInternals;
export opaque type ThenableState = Array<Thenable<any>>;
// An error that is thrown (e.g. by `use`) to trigger Suspense. If we
// detect this is caught by userspace, we'll log a warning in development.
export const SuspenseException: mixed = new Error(
"Suspense Exception: This is not a real error! It's an implementation " +
'detail of `use` to interrupt the current render. You must either ' +
'rethrow it immediately, or move the `use` call outside of the ' +
'`try/catch` block. Capturing without rethrowing will lead to ' +
'unexpected behavior.\n\n' +
'To handle async errors, wrap your component in an error boundary, or ' +
"call the promise's `.catch` method and pass the result to `use`",
);
export function createThenableState(): ThenableState {
// The ThenableState is created the first time a component suspends. If it
// suspends again, we'll reuse the same state.
return [];
}
export function isThenableResolved(thenable: Thenable<mixed>): boolean {
const status = thenable.status;
return status === 'fulfilled' || status === 'rejected';
}
function noop(): void {}
export function trackUsedThenable<T>(
thenableState: ThenableState,
thenable: Thenable<T>,
index: number,
): T {
if (__DEV__ && ReactCurrentActQueue.current !== null) {
ReactCurrentActQueue.didUsePromise = true;
}
const previous = thenableState[index];
if (previous === undefined) {
thenableState.push(thenable);
} else {
if (previous !== thenable) {
// Reuse the previous thenable, and drop the new one. We can assume
// they represent the same value, because components are idempotent.
// Avoid an unhandled rejection errors for the Promises that we'll
// intentionally ignore.
thenable.then(noop, noop);
thenable = previous;
}
}
// We use an expando to track the status and result of a thenable so that we
// can synchronously unwrap the value. Think of this as an extension of the
// Promise API, or a custom interface that is a superset of Thenable.
//
// If the thenable doesn't have a status, set it to "pending" and attach
// a listener that will update its status and result when it resolves.
switch (thenable.status) {
case 'fulfilled': {
const fulfilledValue: T = thenable.value;
return fulfilledValue;
}
case 'rejected': {
const rejectedError = thenable.reason;
throw rejectedError;
}
default: {
if (typeof thenable.status === 'string') {
// Only instrument the thenable if the status if not defined. If
// it's defined, but an unknown value, assume it's been instrumented by
// some custom userspace implementation. We treat it as "pending".
} else {
const pendingThenable: PendingThenable<T> = (thenable: any);
pendingThenable.status = 'pending';
pendingThenable.then(
fulfilledValue => {
if (thenable.status === 'pending') {
const fulfilledThenable: FulfilledThenable<T> = (thenable: any);
fulfilledThenable.status = 'fulfilled';
fulfilledThenable.value = fulfilledValue;
}
},
(error: mixed) => {
if (thenable.status === 'pending') {
const rejectedThenable: RejectedThenable<T> = (thenable: any);
rejectedThenable.status = 'rejected';
rejectedThenable.reason = error;
}
},
);
// Check one more time in case the thenable resolved synchronously
switch (thenable.status) {
case 'fulfilled': {
const fulfilledThenable: FulfilledThenable<T> = (thenable: any);
return fulfilledThenable.value;
}
case 'rejected': {
const rejectedThenable: RejectedThenable<T> = (thenable: any);
throw rejectedThenable.reason;
}
}
}
// Suspend.
//
// Throwing here is an implementation detail that allows us to unwind the
// call stack. But we shouldn't allow it to leak into userspace. Throw an
// opaque placeholder value instead of the actual thenable. If it doesn't
// get captured by the work loop, log a warning, because that means
// something in userspace must have caught it.
suspendedThenable = thenable;
if (__DEV__) {
needsToResetSuspendedThenableDEV = true;
}
throw SuspenseException;
}
}
}
// This is used to track the actual thenable that suspended so it can be
// passed to the rest of the Suspense implementation — which, for historical
// reasons, expects to receive a thenable.
let suspendedThenable: Thenable<any> | null = null;
let needsToResetSuspendedThenableDEV = false;
export function getSuspendedThenable(): Thenable<mixed> {
// This is called right after `use` suspends by throwing an exception. `use`
// throws an opaque value instead of the thenable itself so that it can't be
// caught in userspace. Then the work loop accesses the actual thenable using
// this function.
if (suspendedThenable === null) {
throw new Error(
'Expected a suspended thenable. This is a bug in React. Please file ' +
'an issue.',
);
}
const thenable = suspendedThenable;
suspendedThenable = null;
if (__DEV__) {
needsToResetSuspendedThenableDEV = false;
}
return thenable;
}
export function checkIfUseWrappedInTryCatch(): boolean {
if (__DEV__) {
// This was set right before SuspenseException was thrown, and it should
// have been cleared when the exception was handled. If it wasn't,
// it must have been caught by userspace.
if (needsToResetSuspendedThenableDEV) {
needsToResetSuspendedThenableDEV = false;
return true;
}
}
return false;
}

View File

@ -1,529 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {Lane, Lanes} from './ReactFiberLane.new';
import type {CapturedValue} from './ReactCapturedValue';
import type {Update} from './ReactFiberClassUpdateQueue.new';
import type {Wakeable} from 'shared/ReactTypes';
import type {OffscreenQueue} from './ReactFiberOffscreenComponent';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import {
ClassComponent,
HostRoot,
IncompleteClassComponent,
FunctionComponent,
ForwardRef,
SimpleMemoComponent,
SuspenseComponent,
OffscreenComponent,
} from './ReactWorkTags';
import {
DidCapture,
Incomplete,
NoFlags,
ShouldCapture,
LifecycleEffectMask,
ForceUpdateForLegacySuspense,
ForceClientRender,
} from './ReactFiberFlags';
import {NoMode, ConcurrentMode, DebugTracingMode} from './ReactTypeOfMode';
import {
enableDebugTracing,
enableLazyContextPropagation,
enableUpdaterTracking,
} from 'shared/ReactFeatureFlags';
import {createCapturedValueAtFiber} from './ReactCapturedValue';
import {
enqueueCapturedUpdate,
createUpdate,
CaptureUpdate,
ForceUpdate,
enqueueUpdate,
} from './ReactFiberClassUpdateQueue.new';
import {markFailedErrorBoundaryForHotReloading} from './ReactFiberHotReloading.new';
import {getSuspenseHandler} from './ReactFiberSuspenseContext.new';
import {
renderDidError,
renderDidSuspendDelayIfPossible,
onUncaughtError,
markLegacyErrorBoundaryAsFailed,
isAlreadyFailedLegacyErrorBoundary,
attachPingListener,
restorePendingUpdaters,
} from './ReactFiberWorkLoop.new';
import {propagateParentContextChangesToDeferredTree} from './ReactFiberNewContext.new';
import {logCapturedError} from './ReactFiberErrorLogger';
import {logComponentSuspended} from './DebugTracing';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
import {
SyncLane,
NoTimestamp,
includesSomeLane,
mergeLanes,
pickArbitraryLane,
} from './ReactFiberLane.new';
import {
getIsHydrating,
markDidThrowWhileHydratingDEV,
queueHydrationError,
} from './ReactFiberHydrationContext.new';
import {ConcurrentRoot} from './ReactRootTags';
function createRootErrorUpdate(
fiber: Fiber,
errorInfo: CapturedValue<mixed>,
lane: Lane,
): Update<mixed> {
const update = createUpdate(NoTimestamp, lane);
// Unmount the root by rendering null.
update.tag = CaptureUpdate;
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element: null};
const error = errorInfo.value;
update.callback = () => {
onUncaughtError(error);
logCapturedError(fiber, errorInfo);
};
return update;
}
function createClassErrorUpdate(
fiber: Fiber,
errorInfo: CapturedValue<mixed>,
lane: Lane,
): Update<mixed> {
const update = createUpdate(NoTimestamp, lane);
update.tag = CaptureUpdate;
const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
if (typeof getDerivedStateFromError === 'function') {
const error = errorInfo.value;
update.payload = () => {
return getDerivedStateFromError(error);
};
update.callback = () => {
if (__DEV__) {
markFailedErrorBoundaryForHotReloading(fiber);
}
logCapturedError(fiber, errorInfo);
};
}
const inst = fiber.stateNode;
if (inst !== null && typeof inst.componentDidCatch === 'function') {
update.callback = function callback() {
if (__DEV__) {
markFailedErrorBoundaryForHotReloading(fiber);
}
logCapturedError(fiber, errorInfo);
if (typeof getDerivedStateFromError !== 'function') {
// To preserve the preexisting retry behavior of error boundaries,
// we keep track of which ones already failed during this batch.
// This gets reset before we yield back to the browser.
// TODO: Warn in strict mode if getDerivedStateFromError is
// not defined.
markLegacyErrorBoundaryAsFailed(this);
}
const error = errorInfo.value;
const stack = errorInfo.stack;
this.componentDidCatch(error, {
componentStack: stack !== null ? stack : '',
});
if (__DEV__) {
if (typeof getDerivedStateFromError !== 'function') {
// If componentDidCatch is the only error boundary method defined,
// then it needs to call setState to recover from errors.
// If no state update is scheduled then the boundary will swallow the error.
if (!includesSomeLane(fiber.lanes, (SyncLane: Lane))) {
console.error(
'%s: Error boundaries should implement getDerivedStateFromError(). ' +
'In that method, return a state update to display an error message or fallback UI.',
getComponentNameFromFiber(fiber) || 'Unknown',
);
}
}
}
};
}
return update;
}
function resetSuspendedComponent(sourceFiber: Fiber, rootRenderLanes: Lanes) {
if (enableLazyContextPropagation) {
const currentSourceFiber = sourceFiber.alternate;
if (currentSourceFiber !== null) {
// Since we never visited the children of the suspended component, we
// need to propagate the context change now, to ensure that we visit
// them during the retry.
//
// We don't have to do this for errors because we retry errors without
// committing in between. So this is specific to Suspense.
propagateParentContextChangesToDeferredTree(
currentSourceFiber,
sourceFiber,
rootRenderLanes,
);
}
}
// Reset the memoizedState to what it was before we attempted to render it.
// A legacy mode Suspense quirk, only relevant to hook components.
const tag = sourceFiber.tag;
if (
(sourceFiber.mode & ConcurrentMode) === NoMode &&
(tag === FunctionComponent ||
tag === ForwardRef ||
tag === SimpleMemoComponent)
) {
const currentSource = sourceFiber.alternate;
if (currentSource) {
sourceFiber.updateQueue = currentSource.updateQueue;
sourceFiber.memoizedState = currentSource.memoizedState;
sourceFiber.lanes = currentSource.lanes;
} else {
sourceFiber.updateQueue = null;
sourceFiber.memoizedState = null;
}
}
}
function markSuspenseBoundaryShouldCapture(
suspenseBoundary: Fiber,
returnFiber: Fiber,
sourceFiber: Fiber,
root: FiberRoot,
rootRenderLanes: Lanes,
): Fiber | null {
// This marks a Suspense boundary so that when we're unwinding the stack,
// it captures the suspended "exception" and does a second (fallback) pass.
if ((suspenseBoundary.mode & ConcurrentMode) === NoMode) {
// Legacy Mode Suspense
//
// If the boundary is in legacy mode, we should *not*
// suspend the commit. Pretend as if the suspended component rendered
// null and keep rendering. When the Suspense boundary completes,
// we'll do a second pass to render the fallback.
if (suspenseBoundary === returnFiber) {
// Special case where we suspended while reconciling the children of
// a Suspense boundary's inner Offscreen wrapper fiber. This happens
// when a React.lazy component is a direct child of a
// Suspense boundary.
//
// Suspense boundaries are implemented as multiple fibers, but they
// are a single conceptual unit. The legacy mode behavior where we
// pretend the suspended fiber committed as `null` won't work,
// because in this case the "suspended" fiber is the inner
// Offscreen wrapper.
//
// Because the contents of the boundary haven't started rendering
// yet (i.e. nothing in the tree has partially rendered) we can
// switch to the regular, concurrent mode behavior: mark the
// boundary with ShouldCapture and enter the unwind phase.
suspenseBoundary.flags |= ShouldCapture;
} else {
suspenseBoundary.flags |= DidCapture;
sourceFiber.flags |= ForceUpdateForLegacySuspense;
// We're going to commit this fiber even though it didn't complete.
// But we shouldn't call any lifecycle methods or callbacks. Remove
// all lifecycle effect tags.
sourceFiber.flags &= ~(LifecycleEffectMask | Incomplete);
if (sourceFiber.tag === ClassComponent) {
const currentSourceFiber = sourceFiber.alternate;
if (currentSourceFiber === null) {
// This is a new mount. Change the tag so it's not mistaken for a
// completed class component. For example, we should not call
// componentWillUnmount if it is deleted.
sourceFiber.tag = IncompleteClassComponent;
} else {
// When we try rendering again, we should not reuse the current fiber,
// since it's known to be in an inconsistent state. Use a force update to
// prevent a bail out.
const update = createUpdate(NoTimestamp, SyncLane);
update.tag = ForceUpdate;
enqueueUpdate(sourceFiber, update, SyncLane);
}
}
// The source fiber did not complete. Mark it with Sync priority to
// indicate that it still has pending work.
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, SyncLane);
}
return suspenseBoundary;
}
// Confirmed that the boundary is in a concurrent mode tree. Continue
// with the normal suspend path.
//
// After this we'll use a set of heuristics to determine whether this
// render pass will run to completion or restart or "suspend" the commit.
// The actual logic for this is spread out in different places.
//
// This first principle is that if we're going to suspend when we complete
// a root, then we should also restart if we get an update or ping that
// might unsuspend it, and vice versa. The only reason to suspend is
// because you think you might want to restart before committing. However,
// it doesn't make sense to restart only while in the period we're suspended.
//
// Restarting too aggressively is also not good because it starves out any
// intermediate loading state. So we use heuristics to determine when.
// Suspense Heuristics
//
// If nothing threw a Promise or all the same fallbacks are already showing,
// then don't suspend/restart.
//
// If this is an initial render of a new tree of Suspense boundaries and
// those trigger a fallback, then don't suspend/restart. We want to ensure
// that we can show the initial loading state as quickly as possible.
//
// If we hit a "Delayed" case, such as when we'd switch from content back into
// a fallback, then we should always suspend/restart. Transitions apply
// to this case. If none is defined, JND is used instead.
//
// If we're already showing a fallback and it gets "retried", allowing us to show
// another level, but there's still an inner boundary that would show a fallback,
// then we suspend/restart for 500ms since the last time we showed a fallback
// anywhere in the tree. This effectively throttles progressive loading into a
// consistent train of commits. This also gives us an opportunity to restart to
// get to the completed state slightly earlier.
//
// If there's ambiguity due to batching it's resolved in preference of:
// 1) "delayed", 2) "initial render", 3) "retry".
//
// We want to ensure that a "busy" state doesn't get force committed. We want to
// ensure that new initial loading states can commit as soon as possible.
suspenseBoundary.flags |= ShouldCapture;
// TODO: I think we can remove this, since we now use `DidCapture` in
// the begin phase to prevent an early bailout.
suspenseBoundary.lanes = rootRenderLanes;
return suspenseBoundary;
}
function throwException(
root: FiberRoot,
returnFiber: Fiber,
sourceFiber: Fiber,
value: mixed,
rootRenderLanes: Lanes,
): void {
// The source fiber did not complete.
sourceFiber.flags |= Incomplete;
if (enableUpdaterTracking) {
if (isDevToolsPresent) {
// If we have pending work still, restore the original updaters
restorePendingUpdaters(root, rootRenderLanes);
}
}
if (
value !== null &&
typeof value === 'object' &&
typeof value.then === 'function'
) {
// This is a wakeable. The component suspended.
const wakeable: Wakeable = (value: any);
resetSuspendedComponent(sourceFiber, rootRenderLanes);
if (__DEV__) {
if (getIsHydrating() && sourceFiber.mode & ConcurrentMode) {
markDidThrowWhileHydratingDEV();
}
}
if (__DEV__) {
if (enableDebugTracing) {
if (sourceFiber.mode & DebugTracingMode) {
const name = getComponentNameFromFiber(sourceFiber) || 'Unknown';
logComponentSuspended(name, wakeable);
}
}
}
// Schedule the nearest Suspense to re-render the timed out view.
const suspenseBoundary = getSuspenseHandler();
if (suspenseBoundary !== null) {
switch (suspenseBoundary.tag) {
case SuspenseComponent: {
suspenseBoundary.flags &= ~ForceClientRender;
markSuspenseBoundaryShouldCapture(
suspenseBoundary,
returnFiber,
sourceFiber,
root,
rootRenderLanes,
);
// Retry listener
//
// If the fallback does commit, we need to attach a different type of
// listener. This one schedules an update on the Suspense boundary to
// turn the fallback state off.
//
// Stash the wakeable on the boundary fiber so we can access it in the
// commit phase.
//
// When the wakeable resolves, we'll attempt to render the boundary
// again ("retry").
const wakeables: Set<Wakeable> | null = (suspenseBoundary.updateQueue: any);
if (wakeables === null) {
suspenseBoundary.updateQueue = new Set([wakeable]);
} else {
wakeables.add(wakeable);
}
break;
}
case OffscreenComponent: {
if (suspenseBoundary.mode & ConcurrentMode) {
suspenseBoundary.flags |= ShouldCapture;
const offscreenQueue: OffscreenQueue | null = (suspenseBoundary.updateQueue: any);
if (offscreenQueue === null) {
const newOffscreenQueue: OffscreenQueue = {
transitions: null,
markerInstances: null,
wakeables: new Set([wakeable]),
};
suspenseBoundary.updateQueue = newOffscreenQueue;
} else {
const wakeables = offscreenQueue.wakeables;
if (wakeables === null) {
offscreenQueue.wakeables = new Set([wakeable]);
} else {
wakeables.add(wakeable);
}
}
break;
}
}
// eslint-disable-next-line no-fallthrough
default: {
throw new Error(
`Unexpected Suspense handler tag (${suspenseBoundary.tag}). This ` +
'is a bug in React.',
);
}
}
// We only attach ping listeners in concurrent mode. Legacy Suspense always
// commits fallbacks synchronously, so there are no pings.
if (suspenseBoundary.mode & ConcurrentMode) {
attachPingListener(root, wakeable, rootRenderLanes);
}
return;
} else {
// No boundary was found. Unless this is a sync update, this is OK.
// We can suspend and wait for more data to arrive.
if (root.tag === ConcurrentRoot) {
// In a concurrent root, suspending without a Suspense boundary is
// allowed. It will suspend indefinitely without committing.
//
// TODO: Should we have different behavior for discrete updates? What
// about flushSync? Maybe it should put the tree into an inert state,
// and potentially log a warning. Revisit this for a future release.
attachPingListener(root, wakeable, rootRenderLanes);
renderDidSuspendDelayIfPossible();
return;
} else {
// In a legacy root, suspending without a boundary is always an error.
const uncaughtSuspenseError = new Error(
'A component suspended while responding to synchronous input. This ' +
'will cause the UI to be replaced with a loading indicator. To ' +
'fix, updates that suspend should be wrapped ' +
'with startTransition.',
);
value = uncaughtSuspenseError;
}
}
} else {
// This is a regular error, not a Suspense wakeable.
if (getIsHydrating() && sourceFiber.mode & ConcurrentMode) {
markDidThrowWhileHydratingDEV();
const suspenseBoundary = getSuspenseHandler();
// If the error was thrown during hydration, we may be able to recover by
// discarding the dehydrated content and switching to a client render.
// Instead of surfacing the error, find the nearest Suspense boundary
// and render it again without hydration.
if (suspenseBoundary !== null) {
if ((suspenseBoundary.flags & ShouldCapture) === NoFlags) {
// Set a flag to indicate that we should try rendering the normal
// children again, not the fallback.
suspenseBoundary.flags |= ForceClientRender;
}
markSuspenseBoundaryShouldCapture(
suspenseBoundary,
returnFiber,
sourceFiber,
root,
rootRenderLanes,
);
// Even though the user may not be affected by this error, we should
// still log it so it can be fixed.
queueHydrationError(createCapturedValueAtFiber(value, sourceFiber));
return;
}
} else {
// Otherwise, fall through to the error path.
}
}
value = createCapturedValueAtFiber(value, sourceFiber);
renderDidError(value);
// We didn't find a boundary that could handle this type of exception. Start
// over and traverse parent path again, this time treating the exception
// as an error.
let workInProgress: Fiber = returnFiber;
do {
switch (workInProgress.tag) {
case HostRoot: {
const errorInfo = value;
workInProgress.flags |= ShouldCapture;
const lane = pickArbitraryLane(rootRenderLanes);
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
const update = createRootErrorUpdate(workInProgress, errorInfo, lane);
enqueueCapturedUpdate(workInProgress, update);
return;
}
case ClassComponent:
// Capture and retry
const errorInfo = value;
const ctor = workInProgress.type;
const instance = workInProgress.stateNode;
if (
(workInProgress.flags & DidCapture) === NoFlags &&
(typeof ctor.getDerivedStateFromError === 'function' ||
(instance !== null &&
typeof instance.componentDidCatch === 'function' &&
!isAlreadyFailedLegacyErrorBoundary(instance)))
) {
workInProgress.flags |= ShouldCapture;
const lane = pickArbitraryLane(rootRenderLanes);
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
// Schedule the error boundary to re-render using updated state
const update = createClassErrorUpdate(
workInProgress,
errorInfo,
lane,
);
enqueueCapturedUpdate(workInProgress, update);
return;
}
break;
default:
break;
}
// $FlowFixMe[incompatible-type] we bail out when we get a null
workInProgress = workInProgress.return;
} while (workInProgress !== null);
}
export {throwException, createRootErrorUpdate, createClassErrorUpdate};

View File

@ -1,271 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {
TransitionTracingCallbacks,
Fiber,
FiberRoot,
} from './ReactInternalTypes';
import type {OffscreenInstance} from './ReactFiberOffscreenComponent';
import type {StackCursor} from './ReactFiberStack.new';
import {enableTransitionTracing} from 'shared/ReactFeatureFlags';
import {createCursor, push, pop} from './ReactFiberStack.new';
import {getWorkInProgressTransitions} from './ReactFiberWorkLoop.new';
export type SuspenseInfo = {name: string | null};
export type PendingTransitionCallbacks = {
transitionStart: Array<Transition> | null,
transitionProgress: Map<Transition, PendingBoundaries> | null,
transitionComplete: Array<Transition> | null,
markerProgress: Map<
string,
{pendingBoundaries: PendingBoundaries, transitions: Set<Transition>},
> | null,
markerIncomplete: Map<
string,
{aborts: Array<TransitionAbort>, transitions: Set<Transition>},
> | null,
markerComplete: Map<string, Set<Transition>> | null,
};
export type Transition = {
name: string,
startTime: number,
};
export type BatchConfigTransition = {
name?: string,
startTime?: number,
_updatedFibers?: Set<Fiber>,
};
// TODO: Is there a way to not include the tag or name here?
export type TracingMarkerInstance = {
tag?: TracingMarkerTag,
transitions: Set<Transition> | null,
pendingBoundaries: PendingBoundaries | null,
aborts: Array<TransitionAbort> | null,
name: string | null,
};
export type TransitionAbort = {
reason: 'error' | 'unknown' | 'marker' | 'suspense',
name?: string | null,
};
export const TransitionRoot = 0;
export const TransitionTracingMarker = 1;
export type TracingMarkerTag = 0 | 1;
export type PendingBoundaries = Map<OffscreenInstance, SuspenseInfo>;
export function processTransitionCallbacks(
pendingTransitions: PendingTransitionCallbacks,
endTime: number,
callbacks: TransitionTracingCallbacks,
): void {
if (enableTransitionTracing) {
if (pendingTransitions !== null) {
const transitionStart = pendingTransitions.transitionStart;
const onTransitionStart = callbacks.onTransitionStart;
if (transitionStart !== null && onTransitionStart != null) {
transitionStart.forEach(transition =>
onTransitionStart(transition.name, transition.startTime),
);
}
const markerProgress = pendingTransitions.markerProgress;
const onMarkerProgress = callbacks.onMarkerProgress;
if (onMarkerProgress != null && markerProgress !== null) {
markerProgress.forEach((markerInstance, markerName) => {
if (markerInstance.transitions !== null) {
// TODO: Clone the suspense object so users can't modify it
const pending =
markerInstance.pendingBoundaries !== null
? Array.from(markerInstance.pendingBoundaries.values())
: [];
markerInstance.transitions.forEach(transition => {
onMarkerProgress(
transition.name,
markerName,
transition.startTime,
endTime,
pending,
);
});
}
});
}
const markerComplete = pendingTransitions.markerComplete;
const onMarkerComplete = callbacks.onMarkerComplete;
if (markerComplete !== null && onMarkerComplete != null) {
markerComplete.forEach((transitions, markerName) => {
transitions.forEach(transition => {
onMarkerComplete(
transition.name,
markerName,
transition.startTime,
endTime,
);
});
});
}
const markerIncomplete = pendingTransitions.markerIncomplete;
const onMarkerIncomplete = callbacks.onMarkerIncomplete;
if (onMarkerIncomplete != null && markerIncomplete !== null) {
markerIncomplete.forEach(({transitions, aborts}, markerName) => {
transitions.forEach(transition => {
const filteredAborts = [];
aborts.forEach(abort => {
switch (abort.reason) {
case 'marker': {
filteredAborts.push({
type: 'marker',
name: abort.name,
endTime,
});
break;
}
case 'suspense': {
filteredAborts.push({
type: 'suspense',
name: abort.name,
endTime,
});
break;
}
default: {
break;
}
}
});
if (filteredAborts.length > 0) {
onMarkerIncomplete(
transition.name,
markerName,
transition.startTime,
filteredAborts,
);
}
});
});
}
const transitionProgress = pendingTransitions.transitionProgress;
const onTransitionProgress = callbacks.onTransitionProgress;
if (onTransitionProgress != null && transitionProgress !== null) {
transitionProgress.forEach((pending, transition) => {
onTransitionProgress(
transition.name,
transition.startTime,
endTime,
Array.from(pending.values()),
);
});
}
const transitionComplete = pendingTransitions.transitionComplete;
const onTransitionComplete = callbacks.onTransitionComplete;
if (transitionComplete !== null && onTransitionComplete != null) {
transitionComplete.forEach(transition =>
onTransitionComplete(transition.name, transition.startTime, endTime),
);
}
}
}
}
// For every tracing marker, store a pointer to it. We will later access it
// to get the set of suspense boundaries that need to resolve before the
// tracing marker can be logged as complete
// This code lives separate from the ReactFiberTransition code because
// we push and pop on the tracing marker, not the suspense boundary
const markerInstanceStack: StackCursor<Array<TracingMarkerInstance> | null> = createCursor(
null,
);
export function pushRootMarkerInstance(workInProgress: Fiber): void {
if (enableTransitionTracing) {
// On the root, every transition gets mapped to it's own map of
// suspense boundaries. The transition is marked as complete when
// the suspense boundaries map is empty. We do this because every
// transition completes at different times and depends on different
// suspense boundaries to complete. We store all the transitions
// along with its map of suspense boundaries in the root incomplete
// transitions map. Each entry in this map functions like a tracing
// marker does, so we can push it onto the marker instance stack
const transitions = getWorkInProgressTransitions();
const root: FiberRoot = workInProgress.stateNode;
if (transitions !== null) {
transitions.forEach(transition => {
if (!root.incompleteTransitions.has(transition)) {
const markerInstance: TracingMarkerInstance = {
tag: TransitionRoot,
transitions: new Set([transition]),
pendingBoundaries: null,
aborts: null,
name: null,
};
root.incompleteTransitions.set(transition, markerInstance);
}
});
}
const markerInstances = [];
// For ever transition on the suspense boundary, we push the transition
// along with its map of pending suspense boundaries onto the marker
// instance stack.
root.incompleteTransitions.forEach(markerInstance => {
markerInstances.push(markerInstance);
});
push(markerInstanceStack, markerInstances, workInProgress);
}
}
export function popRootMarkerInstance(workInProgress: Fiber) {
if (enableTransitionTracing) {
pop(markerInstanceStack, workInProgress);
}
}
export function pushMarkerInstance(
workInProgress: Fiber,
markerInstance: TracingMarkerInstance,
): void {
if (enableTransitionTracing) {
if (markerInstanceStack.current === null) {
push(markerInstanceStack, [markerInstance], workInProgress);
} else {
push(
markerInstanceStack,
markerInstanceStack.current.concat(markerInstance),
workInProgress,
);
}
}
}
export function popMarkerInstance(workInProgress: Fiber): void {
if (enableTransitionTracing) {
pop(markerInstanceStack, workInProgress);
}
}
export function getMarkerInstances(): Array<TracingMarkerInstance> | null {
if (enableTransitionTracing) {
return markerInstanceStack.current;
}
return null;
}

View File

@ -1,19 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import ReactSharedInternals from 'shared/ReactSharedInternals';
import type {Transition} from './ReactFiberTracingMarkerComponent.new';
const {ReactCurrentBatchConfig} = ReactSharedInternals;
export const NoTransition = null;
export function requestCurrentTransition(): Transition | null {
return ReactCurrentBatchConfig.transition;
}

View File

@ -1,201 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {Lanes} from './ReactFiberLane.new';
import type {StackCursor} from './ReactFiberStack.new';
import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.new';
import type {Transition} from './ReactFiberTracingMarkerComponent.new';
import {enableCache, enableTransitionTracing} from 'shared/ReactFeatureFlags';
import {isPrimaryRenderer} from './ReactFiberHostConfig';
import {createCursor, push, pop} from './ReactFiberStack.new';
import {
getWorkInProgressRoot,
getWorkInProgressTransitions,
} from './ReactFiberWorkLoop.new';
import {
createCache,
retainCache,
CacheContext,
} from './ReactFiberCacheComponent.new';
// When retrying a Suspense/Offscreen boundary, we restore the cache that was
// used during the previous render by placing it here, on the stack.
const resumedCache: StackCursor<Cache | null> = createCursor(null);
// During the render/synchronous commit phase, we don't actually process the
// transitions. Therefore, we want to lazily combine transitions. Instead of
// comparing the arrays of transitions when we combine them and storing them
// and filtering out the duplicates, we will instead store the unprocessed transitions
// in an array and actually filter them in the passive phase.
const transitionStack: StackCursor<Array<Transition> | null> = createCursor(
null,
);
function peekCacheFromPool(): Cache | null {
if (!enableCache) {
return (null: any);
}
// Check if the cache pool already has a cache we can use.
// If we're rendering inside a Suspense boundary that is currently hidden,
// we should use the same cache that we used during the previous render, if
// one exists.
const cacheResumedFromPreviousRender = resumedCache.current;
if (cacheResumedFromPreviousRender !== null) {
return cacheResumedFromPreviousRender;
}
// Otherwise, check the root's cache pool.
const root = (getWorkInProgressRoot(): any);
const cacheFromRootCachePool = root.pooledCache;
return cacheFromRootCachePool;
}
export function requestCacheFromPool(renderLanes: Lanes): Cache {
// Similar to previous function, except if there's not already a cache in the
// pool, we allocate a new one.
const cacheFromPool = peekCacheFromPool();
if (cacheFromPool !== null) {
return cacheFromPool;
}
// Create a fresh cache and add it to the root cache pool. A cache can have
// multiple owners:
// - A cache pool that lives on the FiberRoot. This is where all fresh caches
// are originally created (TODO: except during refreshes, until we implement
// this correctly). The root takes ownership immediately when the cache is
// created. Conceptually, root.pooledCache is an Option<Arc<Cache>> (owned),
// and the return value of this function is a &Arc<Cache> (borrowed).
// - One of several fiber types: host root, cache boundary, suspense
// component. These retain and release in the commit phase.
const root = (getWorkInProgressRoot(): any);
const freshCache = createCache();
root.pooledCache = freshCache;
retainCache(freshCache);
if (freshCache !== null) {
root.pooledCacheLanes |= renderLanes;
}
return freshCache;
}
export function pushRootTransition(
workInProgress: Fiber,
root: FiberRoot,
renderLanes: Lanes,
) {
if (enableTransitionTracing) {
const rootTransitions = getWorkInProgressTransitions();
push(transitionStack, rootTransitions, workInProgress);
}
}
export function popRootTransition(
workInProgress: Fiber,
root: FiberRoot,
renderLanes: Lanes,
) {
if (enableTransitionTracing) {
pop(transitionStack, workInProgress);
}
}
export function pushTransition(
offscreenWorkInProgress: Fiber,
prevCachePool: SpawnedCachePool | null,
newTransitions: Array<Transition> | null,
): void {
if (enableCache) {
if (prevCachePool === null) {
push(resumedCache, resumedCache.current, offscreenWorkInProgress);
} else {
push(resumedCache, prevCachePool.pool, offscreenWorkInProgress);
}
}
if (enableTransitionTracing) {
if (transitionStack.current === null) {
push(transitionStack, newTransitions, offscreenWorkInProgress);
} else if (newTransitions === null) {
push(transitionStack, transitionStack.current, offscreenWorkInProgress);
} else {
push(
transitionStack,
transitionStack.current.concat(newTransitions),
offscreenWorkInProgress,
);
}
}
}
export function popTransition(workInProgress: Fiber, current: Fiber | null) {
if (current !== null) {
if (enableTransitionTracing) {
pop(transitionStack, workInProgress);
}
if (enableCache) {
pop(resumedCache, workInProgress);
}
}
}
export function getPendingTransitions(): Array<Transition> | null {
if (!enableTransitionTracing) {
return null;
}
return transitionStack.current;
}
export function getSuspendedCache(): SpawnedCachePool | null {
if (!enableCache) {
return null;
}
// This function is called when a Suspense boundary suspends. It returns the
// cache that would have been used to render fresh data during this render,
// if there was any, so that we can resume rendering with the same cache when
// we receive more data.
const cacheFromPool = peekCacheFromPool();
if (cacheFromPool === null) {
return null;
}
return {
// We must also save the parent, so that when we resume we can detect
// a refresh.
parent: isPrimaryRenderer
? CacheContext._currentValue
: CacheContext._currentValue2,
pool: cacheFromPool,
};
}
export function getOffscreenDeferredCache(): SpawnedCachePool | null {
if (!enableCache) {
return null;
}
const cacheFromPool = peekCacheFromPool();
if (cacheFromPool === null) {
return null;
}
return {
// We must also store the parent, so that when we resume we can detect
// a refresh.
parent: isPrimaryRenderer
? CacheContext._currentValue
: CacheContext._currentValue2,
pool: cacheFromPool,
};
}

View File

@ -25,6 +25,16 @@ import {
CacheContext,
} from './ReactFiberCacheComponent.old';
import ReactSharedInternals from 'shared/ReactSharedInternals';
const {ReactCurrentBatchConfig} = ReactSharedInternals;
export const NoTransition = null;
export function requestCurrentTransition(): Transition | null {
return ReactCurrentBatchConfig.transition;
}
// When retrying a Suspense/Offscreen boundary, we restore the cache that was
// used during the previous render by placing it here, on the stack.
const resumedCache: StackCursor<Cache | null> = createCursor(null);

View File

@ -1,154 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {Lanes} from './ReactFiberLane.old';
import type {StackCursor} from './ReactFiberStack.old';
import type {Cache, SpawnedCachePool} from './ReactFiberCacheComponent.old';
import {enableCache} from 'shared/ReactFeatureFlags';
import {isPrimaryRenderer} from './ReactFiberHostConfig';
import {createCursor, push, pop} from './ReactFiberStack.old';
import {getWorkInProgressRoot} from './ReactFiberWorkLoop.old';
import {
createCache,
retainCache,
CacheContext,
} from './ReactFiberCacheComponent.old';
// When retrying a Suspense/Offscreen boundary, we restore the cache that was
// used during the previous render by placing it here, on the stack.
const resumedCache: StackCursor<Cache | null> = createCursor(null);
function peekCacheFromPool(): Cache | null {
if (!enableCache) {
return (null: any);
}
// Check if the cache pool already has a cache we can use.
// If we're rendering inside a Suspense boundary that is currently hidden,
// we should use the same cache that we used during the previous render, if
// one exists.
const cacheResumedFromPreviousRender = resumedCache.current;
if (cacheResumedFromPreviousRender !== null) {
return cacheResumedFromPreviousRender;
}
// Otherwise, check the root's cache pool.
const root = (getWorkInProgressRoot(): any);
const cacheFromRootCachePool = root.pooledCache;
return cacheFromRootCachePool;
}
export function requestCacheFromPool(renderLanes: Lanes): Cache {
// Similar to previous function, except if there's not already a cache in the
// pool, we allocate a new one.
const cacheFromPool = peekCacheFromPool();
if (cacheFromPool !== null) {
return cacheFromPool;
}
// Create a fresh cache and add it to the root cache pool. A cache can have
// multiple owners:
// - A cache pool that lives on the FiberRoot. This is where all fresh caches
// are originally created (TODO: except during refreshes, until we implement
// this correctly). The root takes ownership immediately when the cache is
// created. Conceptually, root.pooledCache is an Option<Arc<Cache>> (owned),
// and the return value of this function is a &Arc<Cache> (borrowed).
// - One of several fiber types: host root, cache boundary, suspense
// component. These retain and release in the commit phase.
const root = (getWorkInProgressRoot(): any);
const freshCache = createCache();
root.pooledCache = freshCache;
retainCache(freshCache);
if (freshCache !== null) {
root.pooledCacheLanes |= renderLanes;
}
return freshCache;
}
export function pushRootTransitionPool(root: FiberRoot) {
if (enableCache) {
return;
}
// Note: This function currently does nothing but I'll leave it here for
// code organization purposes in case that changes.
}
export function popRootTransitionPool(root: FiberRoot, renderLanes: Lanes) {
if (enableCache) {
return;
}
// Note: This function currently does nothing but I'll leave it here for
// code organization purposes in case that changes.
}
export function pushTransitionPool(
offscreenWorkInProgress: Fiber,
prevCachePool: SpawnedCachePool | null,
): void {
if (enableCache) {
if (prevCachePool === null) {
push(resumedCache, resumedCache.current, offscreenWorkInProgress);
} else {
push(resumedCache, prevCachePool.pool, offscreenWorkInProgress);
}
}
}
export function popTransitionPool(workInProgress: Fiber) {
if (enableCache) {
pop(resumedCache, workInProgress);
}
}
export function getSuspendedCachePool(): SpawnedCachePool | null {
if (!enableCache) {
return null;
}
// This function is called when a Suspense boundary suspends. It returns the
// cache that would have been used to render fresh data during this render,
// if there was any, so that we can resume rendering with the same cache when
// we receive more data.
const cacheFromPool = peekCacheFromPool();
if (cacheFromPool === null) {
return null;
}
return {
// We must also save the parent, so that when we resume we can detect
// a refresh.
parent: isPrimaryRenderer
? CacheContext._currentValue
: CacheContext._currentValue2,
pool: cacheFromPool,
};
}
export function getOffscreenDeferredCachePool(): SpawnedCachePool | null {
if (!enableCache) {
return null;
}
const cacheFromPool = peekCacheFromPool();
if (cacheFromPool === null) {
return null;
}
return {
// We must also store the parent, so that when we resume we can detect
// a refresh.
parent: isPrimaryRenderer
? CacheContext._currentValue
: CacheContext._currentValue2,
pool: cacheFromPool,
};
}

View File

@ -1,289 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
// Ids are base 32 strings whose binary representation corresponds to the
// position of a node in a tree.
// Every time the tree forks into multiple children, we add additional bits to
// the left of the sequence that represent the position of the child within the
// current level of children.
//
// 00101 00010001011010101
// ╰─┬─╯ ╰───────┬───────╯
// Fork 5 of 20 Parent id
//
// The leading 0s are important. In the above example, you only need 3 bits to
// represent slot 5. However, you need 5 bits to represent all the forks at
// the current level, so we must account for the empty bits at the end.
//
// For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise,
// the zeroth id at a level would be indistinguishable from its parent.
//
// If a node has only one child, and does not materialize an id (i.e. does not
// contain a useId hook), then we don't need to allocate any space in the
// sequence. It's treated as a transparent indirection. For example, these two
// trees produce the same ids:
//
// <> <>
// <Indirection> <A />
// <A /> <B />
// </Indirection> </>
// <B />
// </>
//
// However, we cannot skip any node that materializes an id. Otherwise, a parent
// id that does not fork would be indistinguishable from its child id. For
// example, this tree does not fork, but the parent and child must have
// different ids.
//
// <Parent>
// <Child />
// </Parent>
//
// To handle this scenario, every time we materialize an id, we allocate a
// new level with a single slot. You can think of this as a fork with only one
// prong, or an array of children with length 1.
//
// It's possible for the size of the sequence to exceed 32 bits, the max
// size for bitwise operations. When this happens, we make more room by
// converting the right part of the id to a string and storing it in an overflow
// variable. We use a base 32 string representation, because 32 is the largest
// power of 2 that is supported by toString(). We want the base to be large so
// that the resulting ids are compact, and we want the base to be a power of 2
// because every log2(base) bits corresponds to a single character, i.e. every
// log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without
// affecting the final result.
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import {getIsHydrating} from './ReactFiberHydrationContext.new';
import {clz32} from './clz32';
import {Forked, NoFlags} from './ReactFiberFlags';
export type TreeContext = {
id: number,
overflow: string,
};
// TODO: Use the unified fiber stack module instead of this local one?
// Intentionally not using it yet to derisk the initial implementation, because
// the way we push/pop these values is a bit unusual. If there's a mistake, I'd
// rather the ids be wrong than crash the whole reconciler.
const forkStack: Array<any> = [];
let forkStackIndex: number = 0;
let treeForkProvider: Fiber | null = null;
let treeForkCount: number = 0;
const idStack: Array<any> = [];
let idStackIndex: number = 0;
let treeContextProvider: Fiber | null = null;
let treeContextId: number = 1;
let treeContextOverflow: string = '';
export function isForkedChild(workInProgress: Fiber): boolean {
warnIfNotHydrating();
return (workInProgress.flags & Forked) !== NoFlags;
}
export function getForksAtLevel(workInProgress: Fiber): number {
warnIfNotHydrating();
return treeForkCount;
}
export function getTreeId(): string {
const overflow = treeContextOverflow;
const idWithLeadingBit = treeContextId;
const id = idWithLeadingBit & ~getLeadingBit(idWithLeadingBit);
return id.toString(32) + overflow;
}
export function pushTreeFork(
workInProgress: Fiber,
totalChildren: number,
): void {
// This is called right after we reconcile an array (or iterator) of child
// fibers, because that's the only place where we know how many children in
// the whole set without doing extra work later, or storing addtional
// information on the fiber.
//
// That's why this function is separate from pushTreeId — it's called during
// the render phase of the fork parent, not the child, which is where we push
// the other context values.
//
// In the Fizz implementation this is much simpler because the child is
// rendered in the same callstack as the parent.
//
// It might be better to just add a `forks` field to the Fiber type. It would
// make this module simpler.
warnIfNotHydrating();
forkStack[forkStackIndex++] = treeForkCount;
forkStack[forkStackIndex++] = treeForkProvider;
treeForkProvider = workInProgress;
treeForkCount = totalChildren;
}
export function pushTreeId(
workInProgress: Fiber,
totalChildren: number,
index: number,
) {
warnIfNotHydrating();
idStack[idStackIndex++] = treeContextId;
idStack[idStackIndex++] = treeContextOverflow;
idStack[idStackIndex++] = treeContextProvider;
treeContextProvider = workInProgress;
const baseIdWithLeadingBit = treeContextId;
const baseOverflow = treeContextOverflow;
// The leftmost 1 marks the end of the sequence, non-inclusive. It's not part
// of the id; we use it to account for leading 0s.
const baseLength = getBitLength(baseIdWithLeadingBit) - 1;
const baseId = baseIdWithLeadingBit & ~(1 << baseLength);
const slot = index + 1;
const length = getBitLength(totalChildren) + baseLength;
// 30 is the max length we can store without overflowing, taking into
// consideration the leading 1 we use to mark the end of the sequence.
if (length > 30) {
// We overflowed the bitwise-safe range. Fall back to slower algorithm.
// This branch assumes the length of the base id is greater than 5; it won't
// work for smaller ids, because you need 5 bits per character.
//
// We encode the id in multiple steps: first the base id, then the
// remaining digits.
//
// Each 5 bit sequence corresponds to a single base 32 character. So for
// example, if the current id is 23 bits long, we can convert 20 of those
// bits into a string of 4 characters, with 3 bits left over.
//
// First calculate how many bits in the base id represent a complete
// sequence of characters.
const numberOfOverflowBits = baseLength - (baseLength % 5);
// Then create a bitmask that selects only those bits.
const newOverflowBits = (1 << numberOfOverflowBits) - 1;
// Select the bits, and convert them to a base 32 string.
const newOverflow = (baseId & newOverflowBits).toString(32);
// Now we can remove those bits from the base id.
const restOfBaseId = baseId >> numberOfOverflowBits;
const restOfBaseLength = baseLength - numberOfOverflowBits;
// Finally, encode the rest of the bits using the normal algorithm. Because
// we made more room, this time it won't overflow.
const restOfLength = getBitLength(totalChildren) + restOfBaseLength;
const restOfNewBits = slot << restOfBaseLength;
const id = restOfNewBits | restOfBaseId;
const overflow = newOverflow + baseOverflow;
treeContextId = (1 << restOfLength) | id;
treeContextOverflow = overflow;
} else {
// Normal path
const newBits = slot << baseLength;
const id = newBits | baseId;
const overflow = baseOverflow;
treeContextId = (1 << length) | id;
treeContextOverflow = overflow;
}
}
export function pushMaterializedTreeId(workInProgress: Fiber) {
warnIfNotHydrating();
// This component materialized an id. This will affect any ids that appear
// in its children.
const returnFiber = workInProgress.return;
if (returnFiber !== null) {
const numberOfForks = 1;
const slotIndex = 0;
pushTreeFork(workInProgress, numberOfForks);
pushTreeId(workInProgress, numberOfForks, slotIndex);
}
}
function getBitLength(number: number): number {
return 32 - clz32(number);
}
function getLeadingBit(id: number) {
return 1 << (getBitLength(id) - 1);
}
export function popTreeContext(workInProgress: Fiber) {
// Restore the previous values.
// This is a bit more complicated than other context-like modules in Fiber
// because the same Fiber may appear on the stack multiple times and for
// different reasons. We have to keep popping until the work-in-progress is
// no longer at the top of the stack.
while (workInProgress === treeForkProvider) {
treeForkProvider = forkStack[--forkStackIndex];
forkStack[forkStackIndex] = null;
treeForkCount = forkStack[--forkStackIndex];
forkStack[forkStackIndex] = null;
}
while (workInProgress === treeContextProvider) {
treeContextProvider = idStack[--idStackIndex];
idStack[idStackIndex] = null;
treeContextOverflow = idStack[--idStackIndex];
idStack[idStackIndex] = null;
treeContextId = idStack[--idStackIndex];
idStack[idStackIndex] = null;
}
}
export function getSuspendedTreeContext(): TreeContext | null {
warnIfNotHydrating();
if (treeContextProvider !== null) {
return {
id: treeContextId,
overflow: treeContextOverflow,
};
} else {
return null;
}
}
export function restoreSuspendedTreeContext(
workInProgress: Fiber,
suspendedContext: TreeContext,
) {
warnIfNotHydrating();
idStack[idStackIndex++] = treeContextId;
idStack[idStackIndex++] = treeContextOverflow;
idStack[idStackIndex++] = treeContextProvider;
treeContextId = suspendedContext.id;
treeContextOverflow = suspendedContext.overflow;
treeContextProvider = workInProgress;
}
function warnIfNotHydrating() {
if (__DEV__) {
if (!getIsHydrating()) {
console.error(
'Expected to be hydrating. This is a bug in React. Please file ' +
'an issue.',
);
}
}
}

View File

@ -1,285 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ReactContext} from 'shared/ReactTypes';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {Lanes} from './ReactFiberLane.new';
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
import type {Cache} from './ReactFiberCacheComponent.new';
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
import {resetWorkInProgressVersions as resetMutableSourceWorkInProgressVersions} from './ReactMutableSource.new';
import {
ClassComponent,
HostRoot,
HostComponent,
HostResource,
HostSingleton,
HostPortal,
ContextProvider,
SuspenseComponent,
SuspenseListComponent,
OffscreenComponent,
LegacyHiddenComponent,
CacheComponent,
TracingMarkerComponent,
} from './ReactWorkTags';
import {DidCapture, NoFlags, ShouldCapture} from './ReactFiberFlags';
import {NoMode, ProfileMode} from './ReactTypeOfMode';
import {
enableProfilerTimer,
enableCache,
enableTransitionTracing,
} from 'shared/ReactFeatureFlags';
import {popHostContainer, popHostContext} from './ReactFiberHostContext.new';
import {
popSuspenseListContext,
popSuspenseHandler,
} from './ReactFiberSuspenseContext.new';
import {popHiddenContext} from './ReactFiberHiddenContext.new';
import {resetHydrationState} from './ReactFiberHydrationContext.new';
import {
isContextProvider as isLegacyContextProvider,
popContext as popLegacyContext,
popTopLevelContextObject as popTopLevelLegacyContextObject,
} from './ReactFiberContext.new';
import {popProvider} from './ReactFiberNewContext.new';
import {popCacheProvider} from './ReactFiberCacheComponent.new';
import {transferActualDuration} from './ReactProfilerTimer.new';
import {popTreeContext} from './ReactFiberTreeContext.new';
import {popRootTransition, popTransition} from './ReactFiberTransition.new';
import {
popMarkerInstance,
popRootMarkerInstance,
} from './ReactFiberTracingMarkerComponent.new';
function unwindWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
// Note: This intentionally doesn't check if we're hydrating because comparing
// to the current tree provider fiber is just as fast and less error-prone.
// Ideally we would have a special version of the work loop only
// for hydration.
popTreeContext(workInProgress);
switch (workInProgress.tag) {
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
const flags = workInProgress.flags;
if (flags & ShouldCapture) {
workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
if (
enableProfilerTimer &&
(workInProgress.mode & ProfileMode) !== NoMode
) {
transferActualDuration(workInProgress);
}
return workInProgress;
}
return null;
}
case HostRoot: {
const root: FiberRoot = workInProgress.stateNode;
if (enableCache) {
const cache: Cache = workInProgress.memoizedState.cache;
popCacheProvider(workInProgress, cache);
}
if (enableTransitionTracing) {
popRootMarkerInstance(workInProgress);
}
popRootTransition(workInProgress, root, renderLanes);
popHostContainer(workInProgress);
popTopLevelLegacyContextObject(workInProgress);
resetMutableSourceWorkInProgressVersions();
const flags = workInProgress.flags;
if (
(flags & ShouldCapture) !== NoFlags &&
(flags & DidCapture) === NoFlags
) {
// There was an error during render that wasn't captured by a suspense
// boundary. Do a second pass on the root to unmount the children.
workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
return workInProgress;
}
// We unwound to the root without completing it. Exit.
return null;
}
case HostResource:
case HostSingleton:
case HostComponent: {
// TODO: popHydrationState
popHostContext(workInProgress);
return null;
}
case SuspenseComponent: {
popSuspenseHandler(workInProgress);
const suspenseState: null | SuspenseState = workInProgress.memoizedState;
if (suspenseState !== null && suspenseState.dehydrated !== null) {
if (workInProgress.alternate === null) {
throw new Error(
'Threw in newly mounted dehydrated component. This is likely a bug in ' +
'React. Please file an issue.',
);
}
resetHydrationState();
}
const flags = workInProgress.flags;
if (flags & ShouldCapture) {
workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
// Captured a suspense effect. Re-render the boundary.
if (
enableProfilerTimer &&
(workInProgress.mode & ProfileMode) !== NoMode
) {
transferActualDuration(workInProgress);
}
return workInProgress;
}
return null;
}
case SuspenseListComponent: {
popSuspenseListContext(workInProgress);
// SuspenseList doesn't actually catch anything. It should've been
// caught by a nested boundary. If not, it should bubble through.
return null;
}
case HostPortal:
popHostContainer(workInProgress);
return null;
case ContextProvider:
const context: ReactContext<any> = workInProgress.type._context;
popProvider(context, workInProgress);
return null;
case OffscreenComponent:
case LegacyHiddenComponent: {
popSuspenseHandler(workInProgress);
popHiddenContext(workInProgress);
popTransition(workInProgress, current);
const flags = workInProgress.flags;
if (flags & ShouldCapture) {
workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
// Captured a suspense effect. Re-render the boundary.
if (
enableProfilerTimer &&
(workInProgress.mode & ProfileMode) !== NoMode
) {
transferActualDuration(workInProgress);
}
return workInProgress;
}
return null;
}
case CacheComponent:
if (enableCache) {
const cache: Cache = workInProgress.memoizedState.cache;
popCacheProvider(workInProgress, cache);
}
return null;
case TracingMarkerComponent:
if (enableTransitionTracing) {
if (workInProgress.stateNode !== null) {
popMarkerInstance(workInProgress);
}
}
return null;
default:
return null;
}
}
function unwindInterruptedWork(
current: Fiber | null,
interruptedWork: Fiber,
renderLanes: Lanes,
) {
// Note: This intentionally doesn't check if we're hydrating because comparing
// to the current tree provider fiber is just as fast and less error-prone.
// Ideally we would have a special version of the work loop only
// for hydration.
popTreeContext(interruptedWork);
switch (interruptedWork.tag) {
case ClassComponent: {
const childContextTypes = interruptedWork.type.childContextTypes;
if (childContextTypes !== null && childContextTypes !== undefined) {
popLegacyContext(interruptedWork);
}
break;
}
case HostRoot: {
const root: FiberRoot = interruptedWork.stateNode;
if (enableCache) {
const cache: Cache = interruptedWork.memoizedState.cache;
popCacheProvider(interruptedWork, cache);
}
if (enableTransitionTracing) {
popRootMarkerInstance(interruptedWork);
}
popRootTransition(interruptedWork, root, renderLanes);
popHostContainer(interruptedWork);
popTopLevelLegacyContextObject(interruptedWork);
resetMutableSourceWorkInProgressVersions();
break;
}
case HostResource:
case HostSingleton:
case HostComponent: {
popHostContext(interruptedWork);
break;
}
case HostPortal:
popHostContainer(interruptedWork);
break;
case SuspenseComponent:
popSuspenseHandler(interruptedWork);
break;
case SuspenseListComponent:
popSuspenseListContext(interruptedWork);
break;
case ContextProvider:
const context: ReactContext<any> = interruptedWork.type._context;
popProvider(context, interruptedWork);
break;
case OffscreenComponent:
case LegacyHiddenComponent:
popSuspenseHandler(interruptedWork);
popHiddenContext(interruptedWork);
popTransition(interruptedWork, current);
break;
case CacheComponent:
if (enableCache) {
const cache: Cache = interruptedWork.memoizedState.cache;
popCacheProvider(interruptedWork, cache);
}
break;
case TracingMarkerComponent:
if (enableTransitionTracing) {
const instance: TracingMarkerInstance | null =
interruptedWork.stateNode;
if (instance !== null) {
popMarkerInstance(interruptedWork);
}
}
break;
default:
break;
}
}
export {unwindWork, unwindInterruptedWork};

File diff suppressed because it is too large Load Diff

View File

@ -176,7 +176,10 @@ import {
lowerEventPriority,
lanesToEventPriority,
} from './ReactEventPriorities.old';
import {requestCurrentTransition, NoTransition} from './ReactFiberTransition';
import {
requestCurrentTransition,
NoTransition,
} from './ReactFiberTransition.old';
import {
SelectiveHydrationException,
beginWork as originalBeginWork,

View File

@ -31,13 +31,11 @@ import type {
SuspenseInstance,
} from './ReactFiberHostConfig';
import type {Cache} from './ReactFiberCacheComponent.old';
// Doing this because there's a merge conflict because of the way sync-reconciler-fork
// is implemented
import type {
TracingMarkerInstance,
Transition,
} from './ReactFiberTracingMarkerComponent.new';
import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates.new';
} from './ReactFiberTracingMarkerComponent.old';
import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates.old';
// Unwind Circular: moved from ReactFiberHooks.old
export type HookType =
@ -416,8 +414,6 @@ export type Dispatcher = {
useId(): string,
useCacheRefresh?: () => <T>(?() => T, ?T) => void,
useMemoCache?: (size: number) => Array<any>,
unstable_isNewReconciler?: boolean,
};
export type CacheDispatcher = {

View File

@ -1,108 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {MutableSource, MutableSourceVersion} from 'shared/ReactTypes';
import type {FiberRoot} from './ReactInternalTypes';
import {isPrimaryRenderer} from './ReactFiberHostConfig';
// Work in progress version numbers only apply to a single render,
// and should be reset before starting a new render.
// This tracks which mutable sources need to be reset after a render.
const workInProgressSources: Array<MutableSource<any>> = [];
let rendererSigil;
if (__DEV__) {
// Used to detect multiple renderers using the same mutable source.
rendererSigil = {};
}
export function markSourceAsDirty(mutableSource: MutableSource<any>): void {
workInProgressSources.push(mutableSource);
}
export function resetWorkInProgressVersions(): void {
for (let i = 0; i < workInProgressSources.length; i++) {
const mutableSource = workInProgressSources[i];
if (isPrimaryRenderer) {
mutableSource._workInProgressVersionPrimary = null;
} else {
mutableSource._workInProgressVersionSecondary = null;
}
}
workInProgressSources.length = 0;
}
export function getWorkInProgressVersion(
mutableSource: MutableSource<any>,
): null | MutableSourceVersion {
if (isPrimaryRenderer) {
return mutableSource._workInProgressVersionPrimary;
} else {
return mutableSource._workInProgressVersionSecondary;
}
}
export function setWorkInProgressVersion(
mutableSource: MutableSource<any>,
version: MutableSourceVersion,
): void {
if (isPrimaryRenderer) {
mutableSource._workInProgressVersionPrimary = version;
} else {
mutableSource._workInProgressVersionSecondary = version;
}
workInProgressSources.push(mutableSource);
}
export function warnAboutMultipleRenderersDEV(
mutableSource: MutableSource<any>,
): void {
if (__DEV__) {
if (isPrimaryRenderer) {
if (mutableSource._currentPrimaryRenderer == null) {
mutableSource._currentPrimaryRenderer = rendererSigil;
} else if (mutableSource._currentPrimaryRenderer !== rendererSigil) {
console.error(
'Detected multiple renderers concurrently rendering the ' +
'same mutable source. This is currently unsupported.',
);
}
} else {
if (mutableSource._currentSecondaryRenderer == null) {
mutableSource._currentSecondaryRenderer = rendererSigil;
} else if (mutableSource._currentSecondaryRenderer !== rendererSigil) {
console.error(
'Detected multiple renderers concurrently rendering the ' +
'same mutable source. This is currently unsupported.',
);
}
}
}
}
// Eager reads the version of a mutable source and stores it on the root.
// This ensures that the version used for server rendering matches the one
// that is eventually read during hydration.
// If they don't match there's a potential tear and a full deopt render is required.
export function registerMutableSourceForHydration(
root: FiberRoot,
mutableSource: MutableSource<any>,
): void {
const getVersion = mutableSource._getVersion;
const version = getVersion(mutableSource._source);
// TODO Clear this data once all pending hydration work is finished.
// Retaining it forever may interfere with GC.
if (root.mutableSourceEagerHydrationData == null) {
root.mutableSourceEagerHydrationData = [mutableSource, version];
} else {
root.mutableSourceEagerHydrationData.push(mutableSource, version);
}
}

View File

@ -1,240 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber} from './ReactInternalTypes';
import {
enableProfilerCommitHooks,
enableProfilerNestedUpdatePhase,
enableProfilerTimer,
} from 'shared/ReactFeatureFlags';
import {HostRoot, Profiler} from './ReactWorkTags';
// Intentionally not named imports because Rollup would use dynamic dispatch for
// CommonJS interop named imports.
import * as Scheduler from 'scheduler';
const {unstable_now: now} = Scheduler;
export type ProfilerTimer = {
getCommitTime(): number,
isCurrentUpdateNested(): boolean,
markNestedUpdateScheduled(): void,
recordCommitTime(): void,
startProfilerTimer(fiber: Fiber): void,
stopProfilerTimerIfRunning(fiber: Fiber): void,
stopProfilerTimerIfRunningAndRecordDelta(fiber: Fiber): void,
syncNestedUpdateFlag(): void,
...
};
let commitTime: number = 0;
let layoutEffectStartTime: number = -1;
let profilerStartTime: number = -1;
let passiveEffectStartTime: number = -1;
/**
* Tracks whether the current update was a nested/cascading update (scheduled from a layout effect).
*
* The overall sequence is:
* 1. render
* 2. commit (and call `onRender`, `onCommit`)
* 3. check for nested updates
* 4. flush passive effects (and call `onPostCommit`)
*
* Nested updates are identified in step 3 above,
* but step 4 still applies to the work that was just committed.
* We use two flags to track nested updates then:
* one tracks whether the upcoming update is a nested update,
* and the other tracks whether the current update was a nested update.
* The first value gets synced to the second at the start of the render phase.
*/
let currentUpdateIsNested: boolean = false;
let nestedUpdateScheduled: boolean = false;
function isCurrentUpdateNested(): boolean {
return currentUpdateIsNested;
}
function markNestedUpdateScheduled(): void {
if (enableProfilerNestedUpdatePhase) {
nestedUpdateScheduled = true;
}
}
function resetNestedUpdateFlag(): void {
if (enableProfilerNestedUpdatePhase) {
currentUpdateIsNested = false;
nestedUpdateScheduled = false;
}
}
function syncNestedUpdateFlag(): void {
if (enableProfilerNestedUpdatePhase) {
currentUpdateIsNested = nestedUpdateScheduled;
nestedUpdateScheduled = false;
}
}
function getCommitTime(): number {
return commitTime;
}
function recordCommitTime(): void {
if (!enableProfilerTimer) {
return;
}
commitTime = now();
}
function startProfilerTimer(fiber: Fiber): void {
if (!enableProfilerTimer) {
return;
}
profilerStartTime = now();
if (((fiber.actualStartTime: any): number) < 0) {
fiber.actualStartTime = now();
}
}
function stopProfilerTimerIfRunning(fiber: Fiber): void {
if (!enableProfilerTimer) {
return;
}
profilerStartTime = -1;
}
function stopProfilerTimerIfRunningAndRecordDelta(
fiber: Fiber,
overrideBaseTime: boolean,
): void {
if (!enableProfilerTimer) {
return;
}
if (profilerStartTime >= 0) {
const elapsedTime = now() - profilerStartTime;
// $FlowFixMe[unsafe-addition] addition with possible null/undefined value
fiber.actualDuration += elapsedTime;
if (overrideBaseTime) {
fiber.selfBaseDuration = elapsedTime;
}
profilerStartTime = -1;
}
}
function recordLayoutEffectDuration(fiber: Fiber): void {
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
return;
}
if (layoutEffectStartTime >= 0) {
const elapsedTime = now() - layoutEffectStartTime;
layoutEffectStartTime = -1;
// Store duration on the next nearest Profiler ancestor
// Or the root (for the DevTools Profiler to read)
let parentFiber = fiber.return;
while (parentFiber !== null) {
switch (parentFiber.tag) {
case HostRoot:
const root = parentFiber.stateNode;
root.effectDuration += elapsedTime;
return;
case Profiler:
const parentStateNode = parentFiber.stateNode;
parentStateNode.effectDuration += elapsedTime;
return;
}
parentFiber = parentFiber.return;
}
}
}
function recordPassiveEffectDuration(fiber: Fiber): void {
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
return;
}
if (passiveEffectStartTime >= 0) {
const elapsedTime = now() - passiveEffectStartTime;
passiveEffectStartTime = -1;
// Store duration on the next nearest Profiler ancestor
// Or the root (for the DevTools Profiler to read)
let parentFiber = fiber.return;
while (parentFiber !== null) {
switch (parentFiber.tag) {
case HostRoot:
const root = parentFiber.stateNode;
if (root !== null) {
root.passiveEffectDuration += elapsedTime;
}
return;
case Profiler:
const parentStateNode = parentFiber.stateNode;
if (parentStateNode !== null) {
// Detached fibers have their state node cleared out.
// In this case, the return pointer is also cleared out,
// so we won't be able to report the time spent in this Profiler's subtree.
parentStateNode.passiveEffectDuration += elapsedTime;
}
return;
}
parentFiber = parentFiber.return;
}
}
}
function startLayoutEffectTimer(): void {
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
return;
}
layoutEffectStartTime = now();
}
function startPassiveEffectTimer(): void {
if (!enableProfilerTimer || !enableProfilerCommitHooks) {
return;
}
passiveEffectStartTime = now();
}
function transferActualDuration(fiber: Fiber): void {
// Transfer time spent rendering these children so we don't lose it
// after we rerender. This is used as a helper in special cases
// where we should count the work of multiple passes.
let child = fiber.child;
while (child) {
// $FlowFixMe[unsafe-addition] addition with possible null/undefined value
fiber.actualDuration += child.actualDuration;
child = child.sibling;
}
}
export {
getCommitTime,
isCurrentUpdateNested,
markNestedUpdateScheduled,
recordCommitTime,
recordLayoutEffectDuration,
recordPassiveEffectDuration,
resetNestedUpdateFlag,
startLayoutEffectTimer,
startPassiveEffectTimer,
startProfilerTimer,
stopProfilerTimerIfRunning,
stopProfilerTimerIfRunningAndRecordDelta,
syncNestedUpdateFlag,
transferActualDuration,
};

View File

@ -15,5 +15,5 @@ export {
ContinuousEventPriority,
DefaultEventPriority,
IdleEventPriority,
} from './ReactEventPriorities';
} from './ReactEventPriorities.old';
export {ConcurrentRoot, LegacyRoot} from './ReactRootTags';

View File

@ -1,370 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber} from './ReactInternalTypes';
import {
resetCurrentFiber as resetCurrentDebugFiberInDEV,
setCurrentFiber as setCurrentDebugFiberInDEV,
} from './ReactCurrentFiber';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import {StrictLegacyMode} from './ReactTypeOfMode';
type FiberArray = Array<Fiber>;
type FiberToFiberComponentsMap = Map<Fiber, FiberArray>;
const ReactStrictModeWarnings = {
recordUnsafeLifecycleWarnings: (fiber: Fiber, instance: any): void => {},
flushPendingUnsafeLifecycleWarnings: (): void => {},
recordLegacyContextWarning: (fiber: Fiber, instance: any): void => {},
flushLegacyContextWarning: (): void => {},
discardPendingWarnings: (): void => {},
};
if (__DEV__) {
const findStrictRoot = (fiber: Fiber): Fiber | null => {
let maybeStrictRoot = null;
let node: null | Fiber = fiber;
while (node !== null) {
if (node.mode & StrictLegacyMode) {
maybeStrictRoot = node;
}
node = node.return;
}
return maybeStrictRoot;
};
const setToSortedString = set => {
const array = [];
set.forEach(value => {
array.push(value);
});
return array.sort().join(', ');
};
let pendingComponentWillMountWarnings: Array<Fiber> = [];
let pendingUNSAFE_ComponentWillMountWarnings: Array<Fiber> = [];
let pendingComponentWillReceivePropsWarnings: Array<Fiber> = [];
let pendingUNSAFE_ComponentWillReceivePropsWarnings: Array<Fiber> = [];
let pendingComponentWillUpdateWarnings: Array<Fiber> = [];
let pendingUNSAFE_ComponentWillUpdateWarnings: Array<Fiber> = [];
// Tracks components we have already warned about.
const didWarnAboutUnsafeLifecycles = new Set();
ReactStrictModeWarnings.recordUnsafeLifecycleWarnings = (
fiber: Fiber,
instance: any,
) => {
// Dedupe strategy: Warn once per component.
if (didWarnAboutUnsafeLifecycles.has(fiber.type)) {
return;
}
if (
typeof instance.componentWillMount === 'function' &&
// Don't warn about react-lifecycles-compat polyfilled components.
instance.componentWillMount.__suppressDeprecationWarning !== true
) {
pendingComponentWillMountWarnings.push(fiber);
}
if (
fiber.mode & StrictLegacyMode &&
typeof instance.UNSAFE_componentWillMount === 'function'
) {
pendingUNSAFE_ComponentWillMountWarnings.push(fiber);
}
if (
typeof instance.componentWillReceiveProps === 'function' &&
instance.componentWillReceiveProps.__suppressDeprecationWarning !== true
) {
pendingComponentWillReceivePropsWarnings.push(fiber);
}
if (
fiber.mode & StrictLegacyMode &&
typeof instance.UNSAFE_componentWillReceiveProps === 'function'
) {
pendingUNSAFE_ComponentWillReceivePropsWarnings.push(fiber);
}
if (
typeof instance.componentWillUpdate === 'function' &&
instance.componentWillUpdate.__suppressDeprecationWarning !== true
) {
pendingComponentWillUpdateWarnings.push(fiber);
}
if (
fiber.mode & StrictLegacyMode &&
typeof instance.UNSAFE_componentWillUpdate === 'function'
) {
pendingUNSAFE_ComponentWillUpdateWarnings.push(fiber);
}
};
ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings = () => {
// We do an initial pass to gather component names
const componentWillMountUniqueNames = new Set();
if (pendingComponentWillMountWarnings.length > 0) {
pendingComponentWillMountWarnings.forEach(fiber => {
componentWillMountUniqueNames.add(
getComponentNameFromFiber(fiber) || 'Component',
);
didWarnAboutUnsafeLifecycles.add(fiber.type);
});
pendingComponentWillMountWarnings = [];
}
const UNSAFE_componentWillMountUniqueNames = new Set();
if (pendingUNSAFE_ComponentWillMountWarnings.length > 0) {
pendingUNSAFE_ComponentWillMountWarnings.forEach(fiber => {
UNSAFE_componentWillMountUniqueNames.add(
getComponentNameFromFiber(fiber) || 'Component',
);
didWarnAboutUnsafeLifecycles.add(fiber.type);
});
pendingUNSAFE_ComponentWillMountWarnings = [];
}
const componentWillReceivePropsUniqueNames = new Set();
if (pendingComponentWillReceivePropsWarnings.length > 0) {
pendingComponentWillReceivePropsWarnings.forEach(fiber => {
componentWillReceivePropsUniqueNames.add(
getComponentNameFromFiber(fiber) || 'Component',
);
didWarnAboutUnsafeLifecycles.add(fiber.type);
});
pendingComponentWillReceivePropsWarnings = [];
}
const UNSAFE_componentWillReceivePropsUniqueNames = new Set();
if (pendingUNSAFE_ComponentWillReceivePropsWarnings.length > 0) {
pendingUNSAFE_ComponentWillReceivePropsWarnings.forEach(fiber => {
UNSAFE_componentWillReceivePropsUniqueNames.add(
getComponentNameFromFiber(fiber) || 'Component',
);
didWarnAboutUnsafeLifecycles.add(fiber.type);
});
pendingUNSAFE_ComponentWillReceivePropsWarnings = [];
}
const componentWillUpdateUniqueNames = new Set();
if (pendingComponentWillUpdateWarnings.length > 0) {
pendingComponentWillUpdateWarnings.forEach(fiber => {
componentWillUpdateUniqueNames.add(
getComponentNameFromFiber(fiber) || 'Component',
);
didWarnAboutUnsafeLifecycles.add(fiber.type);
});
pendingComponentWillUpdateWarnings = [];
}
const UNSAFE_componentWillUpdateUniqueNames = new Set();
if (pendingUNSAFE_ComponentWillUpdateWarnings.length > 0) {
pendingUNSAFE_ComponentWillUpdateWarnings.forEach(fiber => {
UNSAFE_componentWillUpdateUniqueNames.add(
getComponentNameFromFiber(fiber) || 'Component',
);
didWarnAboutUnsafeLifecycles.add(fiber.type);
});
pendingUNSAFE_ComponentWillUpdateWarnings = [];
}
// Finally, we flush all the warnings
// UNSAFE_ ones before the deprecated ones, since they'll be 'louder'
if (UNSAFE_componentWillMountUniqueNames.size > 0) {
const sortedNames = setToSortedString(
UNSAFE_componentWillMountUniqueNames,
);
console.error(
'Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. ' +
'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' +
'* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
'\nPlease update the following components: %s',
sortedNames,
);
}
if (UNSAFE_componentWillReceivePropsUniqueNames.size > 0) {
const sortedNames = setToSortedString(
UNSAFE_componentWillReceivePropsUniqueNames,
);
console.error(
'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended ' +
'and may indicate bugs in your code. ' +
'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' +
'* Move data fetching code or side effects to componentDidUpdate.\n' +
"* If you're updating state whenever props change, " +
'refactor your code to use memoization techniques or move it to ' +
'static getDerivedStateFromProps. Learn more at: https://reactjs.org/link/derived-state\n' +
'\nPlease update the following components: %s',
sortedNames,
);
}
if (UNSAFE_componentWillUpdateUniqueNames.size > 0) {
const sortedNames = setToSortedString(
UNSAFE_componentWillUpdateUniqueNames,
);
console.error(
'Using UNSAFE_componentWillUpdate in strict mode is not recommended ' +
'and may indicate bugs in your code. ' +
'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' +
'* Move data fetching code or side effects to componentDidUpdate.\n' +
'\nPlease update the following components: %s',
sortedNames,
);
}
if (componentWillMountUniqueNames.size > 0) {
const sortedNames = setToSortedString(componentWillMountUniqueNames);
console.warn(
'componentWillMount has been renamed, and is not recommended for use. ' +
'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' +
'* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
'* Rename componentWillMount to UNSAFE_componentWillMount to suppress ' +
'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
'To rename all deprecated lifecycles to their new names, you can run ' +
'`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
'\nPlease update the following components: %s',
sortedNames,
);
}
if (componentWillReceivePropsUniqueNames.size > 0) {
const sortedNames = setToSortedString(
componentWillReceivePropsUniqueNames,
);
console.warn(
'componentWillReceiveProps has been renamed, and is not recommended for use. ' +
'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' +
'* Move data fetching code or side effects to componentDidUpdate.\n' +
"* If you're updating state whenever props change, refactor your " +
'code to use memoization techniques or move it to ' +
'static getDerivedStateFromProps. Learn more at: https://reactjs.org/link/derived-state\n' +
'* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress ' +
'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
'To rename all deprecated lifecycles to their new names, you can run ' +
'`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
'\nPlease update the following components: %s',
sortedNames,
);
}
if (componentWillUpdateUniqueNames.size > 0) {
const sortedNames = setToSortedString(componentWillUpdateUniqueNames);
console.warn(
'componentWillUpdate has been renamed, and is not recommended for use. ' +
'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' +
'* Move data fetching code or side effects to componentDidUpdate.\n' +
'* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress ' +
'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' +
'To rename all deprecated lifecycles to their new names, you can run ' +
'`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
'\nPlease update the following components: %s',
sortedNames,
);
}
};
let pendingLegacyContextWarning: FiberToFiberComponentsMap = new Map();
// Tracks components we have already warned about.
const didWarnAboutLegacyContext = new Set();
ReactStrictModeWarnings.recordLegacyContextWarning = (
fiber: Fiber,
instance: any,
) => {
const strictRoot = findStrictRoot(fiber);
if (strictRoot === null) {
console.error(
'Expected to find a StrictMode component in a strict mode tree. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
return;
}
// Dedup strategy: Warn once per component.
if (didWarnAboutLegacyContext.has(fiber.type)) {
return;
}
let warningsForRoot = pendingLegacyContextWarning.get(strictRoot);
if (
fiber.type.contextTypes != null ||
fiber.type.childContextTypes != null ||
(instance !== null && typeof instance.getChildContext === 'function')
) {
if (warningsForRoot === undefined) {
warningsForRoot = [];
pendingLegacyContextWarning.set(strictRoot, warningsForRoot);
}
warningsForRoot.push(fiber);
}
};
ReactStrictModeWarnings.flushLegacyContextWarning = () => {
((pendingLegacyContextWarning: any): FiberToFiberComponentsMap).forEach(
(fiberArray: FiberArray, strictRoot) => {
if (fiberArray.length === 0) {
return;
}
const firstFiber = fiberArray[0];
const uniqueNames = new Set();
fiberArray.forEach(fiber => {
uniqueNames.add(getComponentNameFromFiber(fiber) || 'Component');
didWarnAboutLegacyContext.add(fiber.type);
});
const sortedNames = setToSortedString(uniqueNames);
try {
setCurrentDebugFiberInDEV(firstFiber);
console.error(
'Legacy context API has been detected within a strict-mode tree.' +
'\n\nThe old API will be supported in all 16.x releases, but applications ' +
'using it should migrate to the new version.' +
'\n\nPlease update the following components: %s' +
'\n\nLearn more about this warning here: https://reactjs.org/link/legacy-context',
sortedNames,
);
} finally {
resetCurrentDebugFiberInDEV();
}
},
);
};
ReactStrictModeWarnings.discardPendingWarnings = () => {
pendingComponentWillMountWarnings = [];
pendingUNSAFE_ComponentWillMountWarnings = [];
pendingComponentWillReceivePropsWarnings = [];
pendingUNSAFE_ComponentWillReceivePropsWarnings = [];
pendingComponentWillUpdateWarnings = [];
pendingUNSAFE_ComponentWillUpdateWarnings = [];
pendingLegacyContextWarning = new Map();
};
}
export default ReactStrictModeWarnings;

View File

@ -24,7 +24,7 @@ describe('ReactFiberHostContext', () => {
ReactFiberReconciler = require('react-reconciler');
ConcurrentRoot = require('react-reconciler/src/ReactRootTags')
.ConcurrentRoot;
DefaultEventPriority = require('react-reconciler/src/ReactEventPriorities')
DefaultEventPriority = require('react-reconciler/src/ReactEventPriorities.old')
.DefaultEventPriority;
});

View File

@ -21,10 +21,7 @@ describe('ReactIncrementalErrorReplay-test', () => {
// This is the method we're going to test.
// If this is no longer used, you can delete this test file.;
const assignFiberPropertiesInDEV = gate(flags => flags.new)
? require('../ReactFiber.new').assignFiberPropertiesInDEV
: require('../ReactFiber.old').assignFiberPropertiesInDEV;
const {assignFiberPropertiesInDEV} = require('../ReactFiber.old');
// Get a real fiber.
const realFiber = ReactTestRenderer.create(<div />).root._currentFiber();

View File

@ -84,10 +84,6 @@ describe('updaters', () => {
'react-reconciler/src/ReactFiberDevToolsHook.old',
() => mockDevToolsHook,
);
jest.mock(
'react-reconciler/src/ReactFiberDevToolsHook.new',
() => mockDevToolsHook,
);
React = require('react');
ReactDOM = require('react-dom');

View File

@ -16,7 +16,7 @@ import type {
ScheduleRoot,
FindHostInstancesForRefresh,
SetRefreshHandler,
} from 'react-reconciler/src/ReactFiberHotReloading';
} from 'react-reconciler/src/ReactFiberHotReloading.old';
import type {ReactNodeList} from 'shared/ReactTypes';
import {REACT_MEMO_TYPE, REACT_FORWARD_REF_TYPE} from 'shared/ReactSymbols';

View File

@ -8,7 +8,7 @@
*/
import isArray from 'shared/isArray';
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities.old';
export type Type = string;
export type Props = Object;

View File

@ -23,7 +23,7 @@ import {
flushSync,
injectIntoDevTools,
batchedUpdates,
} from 'react-reconciler/src/ReactFiberReconciler';
} from 'react-reconciler/src/ReactFiberReconciler.old';
import {findCurrentFiberUsingSlowPath} from 'react-reconciler/src/ReactFiberTreeReflection';
import {
Fragment,

View File

@ -7,7 +7,7 @@
* @flow
*/
import type {BatchConfigTransition} from 'react-reconciler/src/ReactFiberTracingMarkerComponent.new';
import type {BatchConfigTransition} from 'react-reconciler/src/ReactFiberTracingMarkerComponent.old';
type BatchConfig = {
transition: BatchConfigTransition | null,

View File

@ -68,10 +68,6 @@ export const enableScopeAPI = false;
// Experimental Create Event Handle API.
export const enableCreateEventHandleAPI = false;
// This controls whether you get the `.old` modules or the `.new` modules in
// the react-reconciler package.
export const enableNewReconciler = false;
// Support legacy Primer support on internal FB www
export const enableLegacyFBSupport = false;

View File

@ -64,7 +64,6 @@ export const disableNativeComponentFrames = false;
export const skipUnmountedBoundaries = false;
export const deletedTreeCleanUpLevel = 3;
export const enableGetInspectorDataForInstanceInProduction = true;
export const enableNewReconciler = false;
export const deferRenderPhaseUpdateToNextBatch = false;
export const createRootStrictEffectsByDefault = false;

View File

@ -54,7 +54,6 @@ export const disableNativeComponentFrames = false;
export const skipUnmountedBoundaries = false;
export const deletedTreeCleanUpLevel = 3;
export const enableGetInspectorDataForInstanceInProduction = false;
export const enableNewReconciler = false;
export const deferRenderPhaseUpdateToNextBatch = false;
export const createRootStrictEffectsByDefault = false;

View File

@ -54,7 +54,6 @@ export const disableNativeComponentFrames = false;
export const skipUnmountedBoundaries = false;
export const deletedTreeCleanUpLevel = 3;
export const enableGetInspectorDataForInstanceInProduction = false;
export const enableNewReconciler = false;
export const deferRenderPhaseUpdateToNextBatch = false;
export const createRootStrictEffectsByDefault = false;

View File

@ -46,7 +46,6 @@ export const disableNativeComponentFrames = false;
export const skipUnmountedBoundaries = false;
export const deletedTreeCleanUpLevel = 3;
export const enableGetInspectorDataForInstanceInProduction = false;
export const enableNewReconciler = false;
export const deferRenderPhaseUpdateToNextBatch = false;
export const enableSuspenseAvoidThisFallback = false;
export const enableSuspenseAvoidThisFallbackFizz = false;

View File

@ -54,7 +54,6 @@ export const disableNativeComponentFrames = false;
export const skipUnmountedBoundaries = false;
export const deletedTreeCleanUpLevel = 3;
export const enableGetInspectorDataForInstanceInProduction = false;
export const enableNewReconciler = false;
export const deferRenderPhaseUpdateToNextBatch = false;
export const createRootStrictEffectsByDefault = false;

View File

@ -54,7 +54,6 @@ export const disableNativeComponentFrames = false;
export const skipUnmountedBoundaries = false;
export const deletedTreeCleanUpLevel = 3;
export const enableGetInspectorDataForInstanceInProduction = false;
export const enableNewReconciler = false;
export const deferRenderPhaseUpdateToNextBatch = false;
export const createRootStrictEffectsByDefault = false;

View File

@ -54,7 +54,6 @@ export const disableNativeComponentFrames = false;
export const skipUnmountedBoundaries = true;
export const deletedTreeCleanUpLevel = 3;
export const enableGetInspectorDataForInstanceInProduction = false;
export const enableNewReconciler = false;
export const deferRenderPhaseUpdateToNextBatch = false;
export const createRootStrictEffectsByDefault = false;

View File

@ -95,11 +95,6 @@ export const enableComponentStackLocations = true;
export const disableTextareaChildren = __EXPERIMENTAL__;
// Enable forked reconciler. Piggy-backing on the "variant" global so that we
// don't have to add another test dimension. The build system will compile this
// to the correct value.
export const enableNewReconciler = __VARIANT__;
export const allowConcurrentByDefault = true;
export const deletedTreeCleanUpLevel = 3;

View File

@ -1,122 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
const rule = require('../no-cross-fork-imports');
const {RuleTester} = require('eslint');
const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 8,
sourceType: 'module',
},
});
ruleTester.run('eslint-rules/no-cross-fork-imports', rule, {
valid: [
{
code: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop';",
filename: 'ReactFiberWorkLoop.js',
},
{
code: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.new';",
filename: 'ReactFiberWorkLoop.new.js',
},
{
code:
"import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.new.js';",
filename: 'ReactFiberWorkLoop.new.js',
},
{
code: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.old';",
filename: 'ReactFiberWorkLoop.old.js',
},
{
code:
"import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.old.js';",
filename: 'ReactFiberWorkLoop.old.js',
},
],
invalid: [
{
code: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.new';",
filename: 'ReactFiberWorkLoop.old.js',
errors: [
{
message:
'A module that belongs to the old fork cannot import a module ' +
'from the new fork.',
},
],
output: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.old';",
},
{
code:
"import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.new.js';",
filename: 'ReactFiberWorkLoop.old.js',
errors: [
{
message:
'A module that belongs to the old fork cannot import a module ' +
'from the new fork.',
},
],
output: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.old';",
},
{
code: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.old';",
filename: 'ReactFiberWorkLoop.new.js',
errors: [
{
message:
'A module that belongs to the new fork cannot import a module ' +
'from the old fork.',
},
],
output: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.new';",
},
{
code:
"import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.old.js';",
filename: 'ReactFiberWorkLoop.new.js',
errors: [
{
message:
'A module that belongs to the new fork cannot import a module ' +
'from the old fork.',
},
],
output: "import {scheduleUpdateOnFiber} from './ReactFiberWorkLoop.new';",
},
{
code: "export {DiscreteEventPriority} from './ReactFiberLane.old.js';",
filename: 'ReactFiberReconciler.new.js',
errors: [
{
message:
'A module that belongs to the new fork cannot import a module ' +
'from the old fork.',
},
],
output: "export {DiscreteEventPriority} from './ReactFiberLane.new';",
},
{
code: "export {DiscreteEventPriority} from './ReactFiberLane.new.js';",
filename: 'ReactFiberReconciler.old.js',
errors: [
{
message:
'A module that belongs to the old fork cannot import a module ' +
'from the new fork.',
},
],
output: "export {DiscreteEventPriority} from './ReactFiberLane.old';",
},
],
});

View File

@ -1,89 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
const rule = require('../no-cross-fork-types');
const {RuleTester} = require('eslint');
const ruleTester = new RuleTester({
parserOptions: {
ecmaVersion: 8,
sourceType: 'module',
},
});
const newAccessWarning =
'Field cannot be accessed inside the old reconciler fork, only the ' +
'new fork.';
const oldAccessWarning =
'Field cannot be accessed inside the new reconciler fork, only the ' +
'old fork.';
ruleTester.run('eslint-rules/no-cross-fork-types', rule, {
valid: [
{
code: `
const a = obj.key_old;
const b = obj.key_new;
const {key_old, key_new} = obj;
`,
filename: 'ReactFiberWorkLoop.js',
},
{
code: `
const a = obj.key_old;
const {key_old} = obj;
`,
filename: 'ReactFiberWorkLoop.old.js',
},
{
code: `
const a = obj.key_new;
const {key_new} = obj;
`,
filename: 'ReactFiberWorkLoop.new.js',
},
],
invalid: [
{
code: 'const a = obj.key_new;',
filename: 'ReactFiberWorkLoop.old.js',
errors: [{message: newAccessWarning}],
},
{
code: 'const a = obj.key_old;',
filename: 'ReactFiberWorkLoop.new.js',
errors: [{message: oldAccessWarning}],
},
{
code: 'const {key_new} = obj;',
filename: 'ReactFiberWorkLoop.old.js',
errors: [{message: newAccessWarning}],
},
{
code: 'const {key_old} = obj;',
filename: 'ReactFiberWorkLoop.new.js',
errors: [{message: oldAccessWarning}],
},
{
code: 'const subtreeFlags = obj.subtreeFlags;',
filename: 'ReactFiberWorkLoop.old.js',
options: [{new: ['subtreeFlags']}],
errors: [{message: newAccessWarning}],
},
{
code: 'const firstEffect = obj.firstEffect;',
filename: 'ReactFiberWorkLoop.new.js',
options: [{old: ['firstEffect']}],
errors: [{message: oldAccessWarning}],
},
],
});

View File

@ -7,8 +7,6 @@ module.exports = {
'warning-args': require('./warning-args'),
'prod-error-codes': require('./prod-error-codes'),
'no-production-logging': require('./no-production-logging'),
'no-cross-fork-imports': require('./no-cross-fork-imports'),
'no-cross-fork-types': require('./no-cross-fork-types'),
'safe-string-coercion': require('./safe-string-coercion'),
},
};

View File

@ -1,87 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
function isOldFork(filename) {
return filename.endsWith('.old.js') || filename.endsWith('.old');
}
function isNewFork(filename) {
return filename.endsWith('.new.js') || filename.endsWith('.new');
}
module.exports = {
meta: {
type: 'problem',
fixable: 'code',
},
create(context) {
const sourceFilename = context.getFilename();
if (isOldFork(sourceFilename)) {
const visitor = node => {
const sourceNode = node.source;
if (sourceNode === null) {
return;
}
const filename = sourceNode.value;
if (isNewFork(filename)) {
context.report({
node: sourceNode,
message:
'A module that belongs to the old fork cannot import a module ' +
'from the new fork.',
fix(fixer) {
return fixer.replaceText(
sourceNode,
`'${filename.replace(/\.new(\.js)?$/, '.old')}'`
);
},
});
}
};
return {
ImportDeclaration: visitor,
ExportNamedDeclaration: visitor,
};
}
if (isNewFork(sourceFilename)) {
const visitor = node => {
const sourceNode = node.source;
if (sourceNode === null) {
return;
}
const filename = sourceNode.value;
if (isOldFork(filename)) {
context.report({
node: sourceNode,
message:
'A module that belongs to the new fork cannot import a module ' +
'from the old fork.',
fix(fixer) {
return fixer.replaceText(
sourceNode,
`'${filename.replace(/\.old(\.js)?$/, '.new')}'`
);
},
});
}
};
return {
ImportDeclaration: visitor,
ExportNamedDeclaration: visitor,
};
}
return {};
},
};

Some files were not shown because too many files have changed in this diff Show More