2018-05-15 08:12:28 +08:00
|
|
|
/**
|
2022-10-18 23:19:24 +08:00
|
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
2018-05-15 08:12:28 +08:00
|
|
|
*
|
|
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
|
|
|
|
*
|
|
|
|
|
* @flow
|
|
|
|
|
*/
|
|
|
|
|
|
2023-03-13 21:25:42 +08:00
|
|
|
import type {TouchedViewDataAtPoint, ViewConfig} from './ReactNativeTypes';
|
2018-11-20 07:32:54 +08:00
|
|
|
import {create, diff} from './ReactNativeAttributePayload';
|
2018-05-18 03:38:50 +08:00
|
|
|
import {dispatchEvent} from './ReactFabricEventEmitter';
|
2021-06-09 00:26:21 +08:00
|
|
|
import {
|
|
|
|
|
DefaultEventPriority,
|
|
|
|
|
DiscreteEventPriority,
|
2022-12-02 12:19:13 +08:00
|
|
|
} from 'react-reconciler/src/ReactEventPriorities';
|
2023-04-04 21:43:35 +08:00
|
|
|
import {HostText} from 'react-reconciler/src/ReactWorkTags';
|
2021-02-10 02:32:20 +08:00
|
|
|
|
2018-05-15 08:12:28 +08:00
|
|
|
// Modules provided by RN:
|
2018-05-16 05:35:13 +08:00
|
|
|
import {
|
2019-05-23 15:23:54 +08:00
|
|
|
ReactNativeViewConfigRegistry,
|
|
|
|
|
deepFreezeAndThrowOnMutationInDev,
|
2023-03-23 01:54:36 +08:00
|
|
|
createPublicInstance,
|
2023-04-04 21:43:35 +08:00
|
|
|
createPublicTextInstance,
|
2023-03-23 01:54:36 +08:00
|
|
|
type PublicInstance as ReactNativePublicInstance,
|
2023-04-04 21:43:35 +08:00
|
|
|
type PublicTextInstance,
|
2019-05-23 15:23:54 +08:00
|
|
|
} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
|
|
|
|
|
|
|
|
|
|
const {
|
2018-05-16 05:35:13 +08:00
|
|
|
createNode,
|
|
|
|
|
cloneNode,
|
|
|
|
|
cloneNodeWithNewChildren,
|
|
|
|
|
cloneNodeWithNewChildrenAndProps,
|
|
|
|
|
cloneNodeWithNewProps,
|
2019-05-23 15:23:54 +08:00
|
|
|
createChildSet: createChildNodeSet,
|
|
|
|
|
appendChild: appendChildNode,
|
|
|
|
|
appendChildToSet: appendChildNodeToSet,
|
2018-05-16 05:35:13 +08:00
|
|
|
completeRoot,
|
2018-05-18 03:38:50 +08:00
|
|
|
registerEventHandler,
|
2021-06-09 00:26:21 +08:00
|
|
|
unstable_DefaultEventPriority: FabricDefaultPriority,
|
|
|
|
|
unstable_DiscreteEventPriority: FabricDiscretePriority,
|
|
|
|
|
unstable_getCurrentEventPriority: fabricGetCurrentEventPriority,
|
2019-05-23 15:23:54 +08:00
|
|
|
} = nativeFabricUIManager;
|
|
|
|
|
|
2023-04-11 07:09:28 +08:00
|
|
|
import {diffInCommitPhase} from 'shared/ReactFeatureFlags';
|
|
|
|
|
|
2019-05-23 15:23:54 +08:00
|
|
|
const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
|
2018-05-15 08:12:28 +08:00
|
|
|
|
|
|
|
|
// Counter for uniquely identifying views.
|
|
|
|
|
// % 10 === 1 means it is a rootTag.
|
|
|
|
|
// % 2 === 0 means it is a Fabric tag.
|
|
|
|
|
// This means that they never overlap.
|
|
|
|
|
let nextReactTag = 2;
|
|
|
|
|
|
2023-04-04 21:43:35 +08:00
|
|
|
type InternalInstanceHandle = Object;
|
2018-05-19 18:29:11 +08:00
|
|
|
type Node = Object;
|
|
|
|
|
export type Type = string;
|
|
|
|
|
export type Props = Object;
|
|
|
|
|
export type Instance = {
|
2023-03-13 21:25:42 +08:00
|
|
|
// Reference to the shadow node.
|
2018-05-19 18:29:11 +08:00
|
|
|
node: Node,
|
2023-04-04 21:43:35 +08:00
|
|
|
// This object is shared by all the clones of the instance.
|
|
|
|
|
// We use it to access their shared public instance (exposed through refs)
|
|
|
|
|
// and to access its committed state for events, etc.
|
2023-03-17 19:27:49 +08:00
|
|
|
canonical: {
|
|
|
|
|
nativeTag: number,
|
|
|
|
|
viewConfig: ViewConfig,
|
|
|
|
|
currentProps: Props,
|
|
|
|
|
// Reference to the React handle (the fiber)
|
2023-04-04 21:43:35 +08:00
|
|
|
internalInstanceHandle: InternalInstanceHandle,
|
2023-03-17 19:27:49 +08:00
|
|
|
// Exposed through refs.
|
2023-03-23 01:54:36 +08:00
|
|
|
publicInstance: PublicInstance,
|
2023-03-17 19:27:49 +08:00
|
|
|
},
|
2018-05-19 18:29:11 +08:00
|
|
|
};
|
2023-04-04 21:43:35 +08:00
|
|
|
export type TextInstance = {
|
|
|
|
|
// Reference to the shadow node.
|
|
|
|
|
node: Node,
|
|
|
|
|
// Text instances are never cloned, so we don't need to keep a "canonical"
|
|
|
|
|
// reference to make sure all clones of the instance point to the same values.
|
|
|
|
|
publicInstance?: PublicTextInstance,
|
|
|
|
|
};
|
2018-05-19 18:29:11 +08:00
|
|
|
export type HydratableInstance = Instance | TextInstance;
|
2023-03-23 01:54:36 +08:00
|
|
|
export type PublicInstance = ReactNativePublicInstance;
|
2018-05-19 18:29:11 +08:00
|
|
|
export type Container = number;
|
|
|
|
|
export type ChildSet = Object;
|
2022-09-10 04:03:48 +08:00
|
|
|
export type HostContext = $ReadOnly<{
|
2018-05-15 08:12:28 +08:00
|
|
|
isInAParentText: boolean,
|
2022-09-10 04:03:48 +08:00
|
|
|
}>;
|
2018-05-19 18:29:11 +08:00
|
|
|
export type UpdatePayload = Object;
|
2018-05-15 08:12:28 +08:00
|
|
|
|
2018-07-04 10:22:41 +08:00
|
|
|
export type TimeoutHandle = TimeoutID;
|
|
|
|
|
export type NoTimeout = -1;
|
2023-04-27 06:19:58 +08:00
|
|
|
export type TransitionStatus = mixed;
|
2018-07-04 10:22:41 +08:00
|
|
|
|
2022-09-10 04:03:48 +08:00
|
|
|
export type RendererInspectionConfig = $ReadOnly<{
|
2020-03-31 03:42:41 +08:00
|
|
|
// Deprecated. Replaced with getInspectorDataForViewAtPoint.
|
|
|
|
|
getInspectorDataForViewTag?: (tag: number) => Object,
|
|
|
|
|
getInspectorDataForViewAtPoint?: (
|
|
|
|
|
inspectedView: Object,
|
|
|
|
|
locationX: number,
|
|
|
|
|
locationY: number,
|
|
|
|
|
callback: (viewData: TouchedViewDataAtPoint) => mixed,
|
|
|
|
|
) => void,
|
2022-09-10 04:03:48 +08:00
|
|
|
}>;
|
2020-03-31 03:42:41 +08:00
|
|
|
|
2018-05-18 03:38:50 +08:00
|
|
|
// TODO: Remove this conditional once all changes have propagated.
|
|
|
|
|
if (registerEventHandler) {
|
|
|
|
|
/**
|
|
|
|
|
* Register the event emitter with the native bridge
|
|
|
|
|
*/
|
|
|
|
|
registerEventHandler(dispatchEvent);
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-11 05:58:44 +08:00
|
|
|
export * from 'react-reconciler/src/ReactFiberConfigWithNoMutation';
|
|
|
|
|
export * from 'react-reconciler/src/ReactFiberConfigWithNoHydration';
|
|
|
|
|
export * from 'react-reconciler/src/ReactFiberConfigWithNoScopes';
|
|
|
|
|
export * from 'react-reconciler/src/ReactFiberConfigWithNoTestSelectors';
|
|
|
|
|
export * from 'react-reconciler/src/ReactFiberConfigWithNoMicrotasks';
|
|
|
|
|
export * from 'react-reconciler/src/ReactFiberConfigWithNoResources';
|
|
|
|
|
export * from 'react-reconciler/src/ReactFiberConfigWithNoSingletons';
|
2018-05-15 08:12:28 +08:00
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
export function appendInitialChild(
|
|
|
|
|
parentInstance: Instance,
|
|
|
|
|
child: Instance | TextInstance,
|
|
|
|
|
): void {
|
|
|
|
|
appendChildNode(parentInstance.node, child.node);
|
|
|
|
|
}
|
2018-05-15 08:12:28 +08:00
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
export function createInstance(
|
|
|
|
|
type: string,
|
|
|
|
|
props: Props,
|
|
|
|
|
rootContainerInstance: Container,
|
|
|
|
|
hostContext: HostContext,
|
2023-04-04 21:43:35 +08:00
|
|
|
internalInstanceHandle: InternalInstanceHandle,
|
2018-05-19 18:29:11 +08:00
|
|
|
): Instance {
|
|
|
|
|
const tag = nextReactTag;
|
|
|
|
|
nextReactTag += 2;
|
|
|
|
|
|
2018-11-20 07:32:54 +08:00
|
|
|
const viewConfig = getViewConfigForType(type);
|
2018-05-19 18:29:11 +08:00
|
|
|
|
|
|
|
|
if (__DEV__) {
|
|
|
|
|
for (const key in viewConfig.validAttributes) {
|
|
|
|
|
if (props.hasOwnProperty(key)) {
|
|
|
|
|
deepFreezeAndThrowOnMutationInDev(props[key]);
|
2018-05-15 08:12:28 +08:00
|
|
|
}
|
|
|
|
|
}
|
2018-05-19 18:29:11 +08:00
|
|
|
}
|
2018-05-15 08:12:28 +08:00
|
|
|
|
2018-11-20 07:32:54 +08:00
|
|
|
const updatePayload = create(props, viewConfig.validAttributes);
|
2018-05-19 18:29:11 +08:00
|
|
|
|
|
|
|
|
const node = createNode(
|
|
|
|
|
tag, // reactTag
|
|
|
|
|
viewConfig.uiViewClassName, // viewName
|
|
|
|
|
rootContainerInstance, // rootTag
|
|
|
|
|
updatePayload, // props
|
|
|
|
|
internalInstanceHandle, // internalInstanceHandle
|
|
|
|
|
);
|
|
|
|
|
|
2023-03-13 21:25:42 +08:00
|
|
|
const component = createPublicInstance(
|
2019-04-10 06:10:15 +08:00
|
|
|
tag,
|
|
|
|
|
viewConfig,
|
|
|
|
|
internalInstanceHandle,
|
|
|
|
|
);
|
2018-05-19 18:29:11 +08:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
node: node,
|
2023-03-17 19:27:49 +08:00
|
|
|
canonical: {
|
|
|
|
|
nativeTag: tag,
|
|
|
|
|
viewConfig,
|
|
|
|
|
currentProps: props,
|
|
|
|
|
internalInstanceHandle,
|
|
|
|
|
publicInstance: component,
|
|
|
|
|
},
|
2018-05-19 18:29:11 +08:00
|
|
|
};
|
|
|
|
|
}
|
2018-05-15 08:12:28 +08:00
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
export function createTextInstance(
|
|
|
|
|
text: string,
|
|
|
|
|
rootContainerInstance: Container,
|
|
|
|
|
hostContext: HostContext,
|
2023-04-04 21:43:35 +08:00
|
|
|
internalInstanceHandle: InternalInstanceHandle,
|
2018-05-19 18:29:11 +08:00
|
|
|
): TextInstance {
|
2021-08-17 09:43:24 +08:00
|
|
|
if (__DEV__) {
|
|
|
|
|
if (!hostContext.isInAParentText) {
|
|
|
|
|
console.error('Text strings must be rendered within a <Text> component.');
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-05-19 18:29:11 +08:00
|
|
|
|
|
|
|
|
const tag = nextReactTag;
|
|
|
|
|
nextReactTag += 2;
|
|
|
|
|
|
|
|
|
|
const node = createNode(
|
|
|
|
|
tag, // reactTag
|
|
|
|
|
'RCTRawText', // viewName
|
|
|
|
|
rootContainerInstance, // rootTag
|
|
|
|
|
{text: text}, // props
|
|
|
|
|
internalInstanceHandle, // instance handle
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
node: node,
|
|
|
|
|
};
|
|
|
|
|
}
|
2018-05-15 08:12:28 +08:00
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
export function finalizeInitialChildren(
|
|
|
|
|
parentInstance: Instance,
|
|
|
|
|
type: string,
|
|
|
|
|
props: Props,
|
|
|
|
|
hostContext: HostContext,
|
|
|
|
|
): boolean {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2018-05-15 08:12:28 +08:00
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
export function getRootHostContext(
|
|
|
|
|
rootContainerInstance: Container,
|
|
|
|
|
): HostContext {
|
|
|
|
|
return {isInAParentText: false};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getChildHostContext(
|
|
|
|
|
parentHostContext: HostContext,
|
|
|
|
|
type: string,
|
|
|
|
|
): HostContext {
|
|
|
|
|
const prevIsInAParentText = parentHostContext.isInAParentText;
|
|
|
|
|
const isInAParentText =
|
|
|
|
|
type === 'AndroidTextInput' || // Android
|
|
|
|
|
type === 'RCTMultilineTextInputView' || // iOS
|
|
|
|
|
type === 'RCTSinglelineTextInputView' || // iOS
|
|
|
|
|
type === 'RCTText' ||
|
|
|
|
|
type === 'RCTVirtualText';
|
|
|
|
|
|
[Fabric] Use container node to toggle the visibility of Offscreen and Suspense trees (#21960)
* Fix type of Offscreen props argument
Fixes an oversight from a previous refactor. The fiber that wraps
a Suspense component's children used to be a Fragment but now it's on
Offscreen fiber, so its props type has changed. There's a special
hydration path where I forgot to update this. This isn't observable
because we don't ever end up rendering this particular fiber (because
the Suspense boundary is in its fallback state) but we should fix it
anyway to avoid a potential regression in the future.
* Extract createOffscreenFromFiber logic
...into a new method called `createWorkInProgressOffscreenFiber`. Just
for symmetry with `updateWorkInProgressOffscreenFiber`. Doesn't change
any behavior.
* [Fabric] Use container node to hide/show tree
This changes how we hide and show the contents of Offscreen boundaries
in the React Fabric renderer (persistent mode), and also Suspense
boundaries which use the same feature.=
The way it used to work was that when a boundary is hidden, in the
complete phase, instead of calling the normal `cloneInstance` method
inside `appendAllChildren`, we would call a forked method called
`cloneHiddenInstance` for each of the nearest host nodes within the
subtree. This design was largely based on how it works in React DOM
(mutation mode), where instead of cloning the nearest host nodes, we
mutate their `style.display` property.
The motivation for doing it this way in React DOM was because there's no
built-in browser API for hiding a collection of DOM nodes without
affecting their layout.
In Fabric, however, there is no such limitation, so we can instead wrap
in an extra host node and apply a hidden style.
The immediate motivation for this change is that Fabric on Android has a
view pooling mechanism for instances that relies on the assumption that
a current Fiber that is cloned and replaced by a new Fiber will never
appear in a future commit. When this assumption is broken, it may cause
crashes. In the current implementation, that can indeed happen when a
node that was previously hidden is toggled back to visible. Although
this change sidesteps the issue, we may introduce in other features in
the future that would benefit from being able to revert back to an older
node without cloning it again, such as animations.
The way I've implemented this is to insert an additional HostComponent
fiber as the child of each OffscreenComponent. The extra fiber is not
ideal — the way I'd prefer to do it is to attach the host instance to
the OffscreenComponent. However, the native Fabric implementation
currently expects a 1:1 correspondence between HostComponents and host
instances, so I've deferred that optimization to a future PR to derisk
fixing the Fabric pooling crash. I left a TODO in the host config with a
description of the remaining steps, but this alone should be sufficient
to unblock.
2021-07-27 04:17:08 +08:00
|
|
|
// TODO: If this is an offscreen host container, we should reuse the
|
|
|
|
|
// parent context.
|
|
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
if (prevIsInAParentText !== isInAParentText) {
|
|
|
|
|
return {isInAParentText};
|
|
|
|
|
} else {
|
|
|
|
|
return parentHostContext;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-05-15 08:12:28 +08:00
|
|
|
|
2023-03-03 17:38:08 +08:00
|
|
|
export function getPublicInstance(instance: Instance): null | PublicInstance {
|
2023-03-17 19:27:49 +08:00
|
|
|
if (instance.canonical != null && instance.canonical.publicInstance != null) {
|
|
|
|
|
return instance.canonical.publicInstance;
|
2023-03-03 17:38:08 +08:00
|
|
|
}
|
|
|
|
|
|
2023-03-13 21:25:42 +08:00
|
|
|
// For compatibility with the legacy renderer, in case it's used with Fabric
|
|
|
|
|
// in the same app.
|
|
|
|
|
// $FlowExpectedError[prop-missing]
|
2023-03-03 17:38:08 +08:00
|
|
|
if (instance._nativeTag != null) {
|
2023-03-13 21:25:42 +08:00
|
|
|
// $FlowExpectedError[incompatible-return]
|
2023-03-03 17:38:08 +08:00
|
|
|
return instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
2018-05-19 18:29:11 +08:00
|
|
|
}
|
2018-05-15 08:12:28 +08:00
|
|
|
|
2023-04-04 21:43:35 +08:00
|
|
|
function getPublicTextInstance(
|
|
|
|
|
textInstance: TextInstance,
|
|
|
|
|
internalInstanceHandle: InternalInstanceHandle,
|
|
|
|
|
): PublicTextInstance {
|
|
|
|
|
if (textInstance.publicInstance == null) {
|
|
|
|
|
textInstance.publicInstance = createPublicTextInstance(
|
|
|
|
|
internalInstanceHandle,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return textInstance.publicInstance;
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-20 21:35:18 +08:00
|
|
|
export function getPublicInstanceFromInternalInstanceHandle(
|
2023-04-04 21:43:35 +08:00
|
|
|
internalInstanceHandle: InternalInstanceHandle,
|
|
|
|
|
): null | PublicInstance | PublicTextInstance {
|
|
|
|
|
if (internalInstanceHandle.tag === HostText) {
|
|
|
|
|
const textInstance: TextInstance = internalInstanceHandle.stateNode;
|
|
|
|
|
return getPublicTextInstance(textInstance, internalInstanceHandle);
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-20 21:35:18 +08:00
|
|
|
const instance: Instance = internalInstanceHandle.stateNode;
|
|
|
|
|
return getPublicInstance(instance);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 02:32:22 +08:00
|
|
|
export function prepareForCommit(containerInfo: Container): null | Object {
|
2018-05-19 18:29:11 +08:00
|
|
|
// Noop
|
2020-04-21 02:32:22 +08:00
|
|
|
return null;
|
2018-05-19 18:29:11 +08:00
|
|
|
}
|
2018-05-15 08:12:28 +08:00
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
export function prepareUpdate(
|
|
|
|
|
instance: Instance,
|
|
|
|
|
type: string,
|
|
|
|
|
oldProps: Props,
|
|
|
|
|
newProps: Props,
|
|
|
|
|
hostContext: HostContext,
|
|
|
|
|
): null | Object {
|
2023-04-11 07:09:28 +08:00
|
|
|
if (diffInCommitPhase) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2023-03-17 19:27:49 +08:00
|
|
|
const viewConfig = instance.canonical.viewConfig;
|
2018-11-20 07:32:54 +08:00
|
|
|
const updatePayload = diff(oldProps, newProps, viewConfig.validAttributes);
|
2018-05-19 18:29:11 +08:00
|
|
|
// TODO: If the event handlers have changed, we need to update the current props
|
|
|
|
|
// in the commit phase but there is no host config hook to do it yet.
|
2018-06-14 07:20:48 +08:00
|
|
|
// So instead we hack it by updating it in the render phase.
|
2023-03-17 19:27:49 +08:00
|
|
|
instance.canonical.currentProps = newProps;
|
2018-05-19 18:29:11 +08:00
|
|
|
return updatePayload;
|
|
|
|
|
}
|
2018-05-15 08:12:28 +08:00
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
export function resetAfterCommit(containerInfo: Container): void {
|
|
|
|
|
// Noop
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function shouldSetTextContent(type: string, props: Props): boolean {
|
|
|
|
|
// TODO (bvaughn) Revisit this decision.
|
|
|
|
|
// Always returning false simplifies the createInstance() implementation,
|
|
|
|
|
// But creates an additional child Fiber for raw text children.
|
|
|
|
|
// No additional native views are created though.
|
|
|
|
|
// It's not clear to me which is better so I'm deferring for now.
|
|
|
|
|
// More context @ github.com/facebook/react/pull/8560#discussion_r92111303
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-10 02:32:20 +08:00
|
|
|
export function getCurrentEventPriority(): * {
|
2021-06-09 00:26:21 +08:00
|
|
|
const currentEventPriority = fabricGetCurrentEventPriority
|
|
|
|
|
? fabricGetCurrentEventPriority()
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
if (currentEventPriority != null) {
|
|
|
|
|
switch (currentEventPriority) {
|
|
|
|
|
case FabricDiscretePriority:
|
|
|
|
|
return DiscreteEventPriority;
|
|
|
|
|
case FabricDefaultPriority:
|
|
|
|
|
default:
|
|
|
|
|
return DefaultEventPriority;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-24 04:57:28 +08:00
|
|
|
return DefaultEventPriority;
|
2021-02-10 02:32:20 +08:00
|
|
|
}
|
|
|
|
|
|
2023-04-14 03:21:19 +08:00
|
|
|
export function shouldAttemptEagerTransition(): boolean {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
// The Fabric renderer is secondary to the existing React Native renderer.
|
|
|
|
|
export const isPrimaryRenderer = false;
|
|
|
|
|
|
2019-06-25 02:18:26 +08:00
|
|
|
// The Fabric renderer shouldn't trigger missing act() warnings
|
2019-07-03 05:20:17 +08:00
|
|
|
export const warnsIfNotActing = false;
|
2019-06-25 02:18:26 +08:00
|
|
|
|
2018-07-04 10:22:41 +08:00
|
|
|
export const scheduleTimeout = setTimeout;
|
|
|
|
|
export const cancelTimeout = clearTimeout;
|
|
|
|
|
export const noTimeout = -1;
|
|
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
// -------------------
|
|
|
|
|
// Persistence
|
|
|
|
|
// -------------------
|
|
|
|
|
|
|
|
|
|
export const supportsPersistence = true;
|
|
|
|
|
|
|
|
|
|
export function cloneInstance(
|
|
|
|
|
instance: Instance,
|
|
|
|
|
updatePayload: null | Object,
|
|
|
|
|
type: string,
|
|
|
|
|
oldProps: Props,
|
|
|
|
|
newProps: Props,
|
2023-04-04 21:43:35 +08:00
|
|
|
internalInstanceHandle: InternalInstanceHandle,
|
2018-05-19 18:29:11 +08:00
|
|
|
keepChildren: boolean,
|
|
|
|
|
recyclableInstance: null | Instance,
|
|
|
|
|
): Instance {
|
2023-04-11 07:09:28 +08:00
|
|
|
if (diffInCommitPhase) {
|
|
|
|
|
const viewConfig = instance.canonical.viewConfig;
|
|
|
|
|
updatePayload = diff(oldProps, newProps, viewConfig.validAttributes);
|
|
|
|
|
// TODO: If the event handlers have changed, we need to update the current props
|
|
|
|
|
// in the commit phase but there is no host config hook to do it yet.
|
|
|
|
|
// So instead we hack it by updating it in the render phase.
|
|
|
|
|
instance.canonical.currentProps = newProps;
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
const node = instance.node;
|
|
|
|
|
let clone;
|
|
|
|
|
if (keepChildren) {
|
|
|
|
|
if (updatePayload !== null) {
|
2018-08-17 00:54:12 +08:00
|
|
|
clone = cloneNodeWithNewProps(node, updatePayload);
|
2018-05-15 08:12:28 +08:00
|
|
|
} else {
|
2023-04-11 07:09:28 +08:00
|
|
|
if (diffInCommitPhase) {
|
|
|
|
|
// No changes
|
|
|
|
|
return instance;
|
|
|
|
|
} else {
|
|
|
|
|
clone = cloneNode(node);
|
|
|
|
|
}
|
2018-05-15 08:12:28 +08:00
|
|
|
}
|
2018-05-19 18:29:11 +08:00
|
|
|
} else {
|
|
|
|
|
if (updatePayload !== null) {
|
2018-08-17 00:54:12 +08:00
|
|
|
clone = cloneNodeWithNewChildrenAndProps(node, updatePayload);
|
2018-05-19 18:29:11 +08:00
|
|
|
} else {
|
2018-08-17 00:54:12 +08:00
|
|
|
clone = cloneNodeWithNewChildren(node);
|
2018-05-19 18:29:11 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
node: clone,
|
2023-03-17 19:27:49 +08:00
|
|
|
canonical: instance.canonical,
|
2018-05-19 18:29:11 +08:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-04 10:30:20 +08:00
|
|
|
export function cloneHiddenInstance(
|
|
|
|
|
instance: Instance,
|
|
|
|
|
type: string,
|
|
|
|
|
props: Props,
|
2023-04-04 21:43:35 +08:00
|
|
|
internalInstanceHandle: InternalInstanceHandle,
|
2021-08-04 10:30:20 +08:00
|
|
|
): Instance {
|
2023-03-17 19:27:49 +08:00
|
|
|
const viewConfig = instance.canonical.viewConfig;
|
2021-08-04 10:30:20 +08:00
|
|
|
const node = instance.node;
|
|
|
|
|
const updatePayload = create(
|
|
|
|
|
{style: {display: 'none'}},
|
|
|
|
|
viewConfig.validAttributes,
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
node: cloneNodeWithNewProps(node, updatePayload),
|
2023-03-17 19:27:49 +08:00
|
|
|
canonical: instance.canonical,
|
2021-08-04 10:30:20 +08:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function cloneHiddenTextInstance(
|
|
|
|
|
instance: Instance,
|
|
|
|
|
text: string,
|
2023-04-04 21:43:35 +08:00
|
|
|
internalInstanceHandle: InternalInstanceHandle,
|
2021-08-04 10:30:20 +08:00
|
|
|
): TextInstance {
|
|
|
|
|
throw new Error('Not yet implemented.');
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
export function createContainerChildSet(container: Container): ChildSet {
|
|
|
|
|
return createChildNodeSet(container);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function appendChildToContainerChildSet(
|
|
|
|
|
childSet: ChildSet,
|
|
|
|
|
child: Instance | TextInstance,
|
|
|
|
|
): void {
|
|
|
|
|
appendChildNodeToSet(childSet, child.node);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function finalizeContainerChildren(
|
|
|
|
|
container: Container,
|
|
|
|
|
newChildren: ChildSet,
|
|
|
|
|
): void {
|
|
|
|
|
completeRoot(container, newChildren);
|
|
|
|
|
}
|
2018-05-15 08:12:28 +08:00
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
export function replaceContainerChildren(
|
|
|
|
|
container: Container,
|
|
|
|
|
newChildren: ChildSet,
|
|
|
|
|
): void {}
|
2019-03-15 01:02:42 +08:00
|
|
|
|
2022-10-04 09:59:33 +08:00
|
|
|
export function getInstanceFromNode(node: any): empty {
|
2019-10-09 01:32:53 +08:00
|
|
|
throw new Error('Not yet implemented.');
|
|
|
|
|
}
|
2019-11-14 04:46:00 +08:00
|
|
|
|
2023-04-04 21:43:35 +08:00
|
|
|
export function beforeActiveInstanceBlur(
|
|
|
|
|
internalInstanceHandle: InternalInstanceHandle,
|
|
|
|
|
) {
|
2020-04-21 02:32:22 +08:00
|
|
|
// noop
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function afterActiveInstanceBlur() {
|
|
|
|
|
// noop
|
|
|
|
|
}
|
2020-04-24 03:57:52 +08:00
|
|
|
|
|
|
|
|
export function preparePortalMount(portalInstance: Instance): void {
|
|
|
|
|
// noop
|
|
|
|
|
}
|
2021-03-23 12:54:53 +08:00
|
|
|
|
|
|
|
|
export function detachDeletedInstance(node: Instance): void {
|
|
|
|
|
// noop
|
|
|
|
|
}
|
2022-09-14 01:55:56 +08:00
|
|
|
|
|
|
|
|
export function requestPostPaintCallback(callback: (time: number) => void) {
|
|
|
|
|
// noop
|
|
|
|
|
}
|
2022-10-01 07:14:04 +08:00
|
|
|
|
Check if suspensey instance resolves in immediate task (#26427)
When rendering a suspensey resource that we haven't seen before, it may
have loaded in the background while we were rendering. We should yield
to the main thread to see if the load event fires in an immediate task.
For example, if the resource for a link element has already loaded, its
load event will fire in a task right after React yields to the main
thread. Because the continuation task is not scheduled until right
before React yields, the load event will ping React before it resumes.
If this happens, we can resume rendering without showing a fallback.
I don't think this matters much for images, because the `completed`
property tells us whether the image has loaded, and during a non-urgent
render, we never block the main thread for more than 5ms at a time (for
now — we might increase this in the future). It matters more for
stylesheets because the only way to check if it has loaded is by
listening for the load event.
This is essentially the same trick that `use` does for userspace
promises, but a bit simpler because we don't need to replay the host
component's begin phase; the work-in-progress fiber already completed,
so we can just continue onto the next sibling without any additional
work.
As part of this change, I split the `shouldSuspendCommit` host config
method into separate `maySuspendCommit` and `preloadInstance` methods.
Previously `shouldSuspendCommit` was used for both.
This raised a question of whether we should preload resources during a
synchronous render. My initial instinct was that we shouldn't, because
we're going to synchronously block the main thread until the resource is
inserted into the DOM, anyway. But I wonder if the browser is able to
initiate the preload even while the main thread is blocked. It's
probably a micro-optimization either way because most resources will be
loaded during transitions, not urgent renders.
2023-03-21 00:35:10 +08:00
|
|
|
export function maySuspendCommit(type: Type, props: Props): boolean {
|
Feature: Suspend commit without blocking render (#26398)
This adds a new capability for renderers (React DOM, React Native):
prevent a tree from being displayed until it is ready, showing a
fallback if necessary, but without blocking the React components from
being evaluated in the meantime.
A concrete example is CSS loading: React DOM can block a commit from
being applied until the stylesheet has loaded. This allows us to load
the CSS asynchronously, while also preventing a flash of unstyled
content. Images and fonts are some of the other use cases.
You can think of this as "Suspense for the commit phase". Traditional
Suspense, i.e. with `use`, blocking during the render phase: React
cannot proceed with rendering until the data is available. But in the
case of things like stylesheets, you don't need the CSS in order to
evaluate the component. It just needs to be loaded before the tree is
committed. Because React buffers its side effects and mutations, it can
do work in parallel while the stylesheets load in the background.
Like regular Suspense, a "suspensey" stylesheet or image will trigger
the nearest Suspense fallback if it hasn't loaded yet. For now, though,
we only do this for non-urgent updates, like with startTransition. If
you render a suspensey resource during an urgent update, it will revert
to today's behavior. (We may or may not add a way to suspend the commit
during an urgent update in the future.)
In this PR, I have implemented this capability in the reconciler via new
methods added to the host config. I've used our internal React "no-op"
renderer to write tests that demonstrate the feature. I have not yet
implemented Suspensey CSS, images, etc in React DOM. @gnoff and I will
work on that in subsequent PRs.
2023-03-18 06:05:11 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
Check if suspensey instance resolves in immediate task (#26427)
When rendering a suspensey resource that we haven't seen before, it may
have loaded in the background while we were rendering. We should yield
to the main thread to see if the load event fires in an immediate task.
For example, if the resource for a link element has already loaded, its
load event will fire in a task right after React yields to the main
thread. Because the continuation task is not scheduled until right
before React yields, the load event will ping React before it resumes.
If this happens, we can resume rendering without showing a fallback.
I don't think this matters much for images, because the `completed`
property tells us whether the image has loaded, and during a non-urgent
render, we never block the main thread for more than 5ms at a time (for
now — we might increase this in the future). It matters more for
stylesheets because the only way to check if it has loaded is by
listening for the load event.
This is essentially the same trick that `use` does for userspace
promises, but a bit simpler because we don't need to replay the host
component's begin phase; the work-in-progress fiber already completed,
so we can just continue onto the next sibling without any additional
work.
As part of this change, I split the `shouldSuspendCommit` host config
method into separate `maySuspendCommit` and `preloadInstance` methods.
Previously `shouldSuspendCommit` was used for both.
This raised a question of whether we should preload resources during a
synchronous render. My initial instinct was that we shouldn't, because
we're going to synchronously block the main thread until the resource is
inserted into the DOM, anyway. But I wonder if the browser is able to
initiate the preload even while the main thread is blocked. It's
probably a micro-optimization either way because most resources will be
loaded during transitions, not urgent renders.
2023-03-21 00:35:10 +08:00
|
|
|
export function preloadInstance(type: Type, props: Props): boolean {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
Feature: Suspend commit without blocking render (#26398)
This adds a new capability for renderers (React DOM, React Native):
prevent a tree from being displayed until it is ready, showing a
fallback if necessary, but without blocking the React components from
being evaluated in the meantime.
A concrete example is CSS loading: React DOM can block a commit from
being applied until the stylesheet has loaded. This allows us to load
the CSS asynchronously, while also preventing a flash of unstyled
content. Images and fonts are some of the other use cases.
You can think of this as "Suspense for the commit phase". Traditional
Suspense, i.e. with `use`, blocking during the render phase: React
cannot proceed with rendering until the data is available. But in the
case of things like stylesheets, you don't need the CSS in order to
evaluate the component. It just needs to be loaded before the tree is
committed. Because React buffers its side effects and mutations, it can
do work in parallel while the stylesheets load in the background.
Like regular Suspense, a "suspensey" stylesheet or image will trigger
the nearest Suspense fallback if it hasn't loaded yet. For now, though,
we only do this for non-urgent updates, like with startTransition. If
you render a suspensey resource during an urgent update, it will revert
to today's behavior. (We may or may not add a way to suspend the commit
during an urgent update in the future.)
In this PR, I have implemented this capability in the reconciler via new
methods added to the host config. I've used our internal React "no-op"
renderer to write tests that demonstrate the feature. I have not yet
implemented Suspensey CSS, images, etc in React DOM. @gnoff and I will
work on that in subsequent PRs.
2023-03-18 06:05:11 +08:00
|
|
|
export function startSuspendingCommit(): void {}
|
|
|
|
|
|
|
|
|
|
export function suspendInstance(type: Type, props: Props): void {}
|
|
|
|
|
|
|
|
|
|
export function waitForCommitToBeReady(): null {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2023-04-27 06:19:58 +08:00
|
|
|
|
|
|
|
|
export const NotPendingTransition: TransitionStatus = null;
|