Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extracted definition and access to public instances to a separate module in Fabric #26321

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/react-native-renderer/src/ReactFabric.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
findNodeHandle,
dispatchCommand,
sendAccessibilityEvent,
getNodeFromInternalInstanceHandle,
} from './ReactNativePublicCompat';

// $FlowFixMe[missing-local-annot]
Expand Down Expand Up @@ -119,6 +120,10 @@ export {
// This export is typically undefined in production builds.
// See the "enableGetInspectorDataForInstanceInProduction" flag.
getInspectorDataForInstance,
// The public instance has a reference to the internal instance handle.
// This method allows it to acess the most recent shadow node for
// the instance (it's only accessible through it).
getNodeFromInternalInstanceHandle,
};

injectIntoDevTools({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ import {getPublicInstance} from './ReactFabricHostConfig';
// This is ok in DOM because they types are interchangeable, but in React Native
// they aren't.
function getInstanceFromNode(node: Instance | TextInstance): Fiber | null {
const instance: Instance = (node: $FlowFixMe); // In React Native, node is never a text instance

if (instance.internalInstanceHandle != null) {
return instance.internalInstanceHandle;
}

// $FlowFixMe[incompatible-return] DevTools incorrectly passes a fiber in React Native.
return node;
}
Expand All @@ -35,7 +41,7 @@ function getNodeFromInstance(fiber: Fiber): PublicInstance {
}

function getFiberCurrentPropsFromNode(instance: Instance): Props {
return instance.canonical.currentProps;
return instance.currentProps;
}

export {
Expand Down
202 changes: 38 additions & 164 deletions packages/react-native-renderer/src/ReactFabricHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,13 @@
* @flow
*/

import type {ElementRef} from 'react';
import type {
HostComponent,
MeasureInWindowOnSuccessCallback,
MeasureLayoutOnSuccessCallback,
MeasureOnSuccessCallback,
INativeMethods,
ViewConfig,
TouchedViewDataAtPoint,
} from './ReactNativeTypes';

import {warnForStyleProps} from './NativeMethodsMixinUtils';
import type {TouchedViewDataAtPoint, ViewConfig} from './ReactNativeTypes';
import {
createPublicInstance,
type ReactFabricHostComponent,
} from './ReactFabricPublicInstance';
import {create, diff} from './ReactNativeAttributePayload';

import {dispatchEvent} from './ReactFabricEventEmitter';

import {
DefaultEventPriority,
DiscreteEventPriority,
Expand All @@ -31,7 +22,6 @@ import {
// Modules provided by RN:
import {
ReactNativeViewConfigRegistry,
TextInputState,
deepFreezeAndThrowOnMutationInDev,
} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';

Expand All @@ -46,14 +36,9 @@ const {
appendChildToSet: appendChildNodeToSet,
completeRoot,
registerEventHandler,
measure: fabricMeasure,
measureInWindow: fabricMeasureInWindow,
measureLayout: fabricMeasureLayout,
unstable_DefaultEventPriority: FabricDefaultPriority,
unstable_DiscreteEventPriority: FabricDiscretePriority,
unstable_getCurrentEventPriority: fabricGetCurrentEventPriority,
setNativeProps,
getBoundingClientRect: fabricGetBoundingClientRect,
} = nativeFabricUIManager;

const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
Expand All @@ -68,9 +53,15 @@ type Node = Object;
export type Type = string;
export type Props = Object;
export type Instance = {
// Reference to the shadow node.
node: Node,
canonical: ReactFabricHostComponent,
...
nativeTag: number,
viewConfig: ViewConfig,
currentProps: Props,
// Reference to the React handle (the fiber)
internalInstanceHandle: Object,
// Exposed through refs.
publicInstance: ReactFabricHostComponent,
Comment on lines +56 to +64
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we structure this as the following instead, to create a single shared object between instances?

  // Reference to the shadow node.
  node: Node,
  canonical: {
    nativeTag: number,
    viewConfig: ViewConfig,
    currentProps: Props,
    // Exposed through refs.
    publicInstance: ReactFabricHostComponent,
},

Copy link
Contributor Author

@rubennorte rubennorte Mar 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just implementing that when I saw that comment. Making sure I'm updating all the references using this PR as well, hehe. Thanks!

};
export type TextInstance = {node: Node, ...};
export type HydratableInstance = Instance | TextInstance;
Expand Down Expand Up @@ -104,137 +95,6 @@ if (registerEventHandler) {
registerEventHandler(dispatchEvent);
}

const noop = () => {};

/**
* This is used for refs on host components.
*/
class ReactFabricHostComponent implements INativeMethods {
_nativeTag: number;
viewConfig: ViewConfig;
currentProps: Props;
_internalInstanceHandle: Object;

constructor(
tag: number,
viewConfig: ViewConfig,
props: Props,
internalInstanceHandle: Object,
) {
this._nativeTag = tag;
this.viewConfig = viewConfig;
this.currentProps = props;
this._internalInstanceHandle = internalInstanceHandle;
}

blur() {
TextInputState.blurTextInput(this);
}

focus() {
TextInputState.focusTextInput(this);
}

measure(callback: MeasureOnSuccessCallback) {
const node = getShadowNodeFromInternalInstanceHandle(
this._internalInstanceHandle,
);
if (node != null) {
fabricMeasure(node, callback);
}
}

measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
const node = getShadowNodeFromInternalInstanceHandle(
this._internalInstanceHandle,
);
if (node != null) {
fabricMeasureInWindow(node, callback);
}
}

measureLayout(
relativeToNativeNode: number | ElementRef<HostComponent<mixed>>,
onSuccess: MeasureLayoutOnSuccessCallback,
onFail?: () => void /* currently unused */,
) {
if (
typeof relativeToNativeNode === 'number' ||
!(relativeToNativeNode instanceof ReactFabricHostComponent)
) {
if (__DEV__) {
console.error(
'Warning: ref.measureLayout must be called with a ref to a native component.',
);
}

return;
}

const toStateNode = getShadowNodeFromInternalInstanceHandle(
this._internalInstanceHandle,
);
const fromStateNode = getShadowNodeFromInternalInstanceHandle(
relativeToNativeNode._internalInstanceHandle,
);

if (toStateNode != null && fromStateNode != null) {
fabricMeasureLayout(
toStateNode,
fromStateNode,
onFail != null ? onFail : noop,
onSuccess != null ? onSuccess : noop,
);
}
}

unstable_getBoundingClientRect(): DOMRect {
const node = getShadowNodeFromInternalInstanceHandle(
this._internalInstanceHandle,
);
if (node != null) {
const rect = fabricGetBoundingClientRect(node);

if (rect) {
return new DOMRect(rect[0], rect[1], rect[2], rect[3]);
}
}

// Empty rect if any of the above failed
return new DOMRect(0, 0, 0, 0);
}

setNativeProps(nativeProps: Object) {
if (__DEV__) {
warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
}
const updatePayload = create(nativeProps, this.viewConfig.validAttributes);

const node = getShadowNodeFromInternalInstanceHandle(
this._internalInstanceHandle,
);
if (node != null && updatePayload != null) {
setNativeProps(node, updatePayload);
}
}
}

type ParamOf<Fn> = $Call<<T>((arg: T) => mixed) => T, Fn>;
type ShadowNode = ParamOf<(typeof nativeFabricUIManager)['measure']>;

export function getShadowNodeFromInternalInstanceHandle(
internalInstanceHandle: mixed,
): ?ShadowNode {
return (
// $FlowExpectedError[incompatible-return] internalInstanceHandle is opaque but we need to make an exception here.
internalInstanceHandle &&
// $FlowExpectedError[incompatible-return]
internalInstanceHandle.stateNode &&
// $FlowExpectedError[incompatible-use]
internalInstanceHandle.stateNode.node
);
}

export * from 'react-reconciler/src/ReactFiberHostConfigWithNoMutation';
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoHydration';
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoScopes';
Expand Down Expand Up @@ -280,16 +140,19 @@ export function createInstance(
internalInstanceHandle, // internalInstanceHandle
);

const component = new ReactFabricHostComponent(
const component = createPublicInstance(
tag,
viewConfig,
props,
internalInstanceHandle,
);

return {
node: node,
canonical: component,
nativeTag: tag,
viewConfig,
currentProps: props,
internalInstanceHandle,
publicInstance: component,
Comment on lines +151 to +155
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is internalInstanceHandle actually used?

};
}

Expand Down Expand Up @@ -359,12 +222,15 @@ export function getChildHostContext(
}

export function getPublicInstance(instance: Instance): null | PublicInstance {
if (instance.canonical) {
return instance.canonical;
if (instance.publicInstance != null) {
return instance.publicInstance;
}

// For compatibility with Paper
// For compatibility with the legacy renderer, in case it's used with Fabric
// in the same app.
// $FlowExpectedError[prop-missing]
if (instance._nativeTag != null) {
// $FlowExpectedError[incompatible-return]
return instance;
}

Expand All @@ -383,12 +249,12 @@ export function prepareUpdate(
newProps: Props,
hostContext: HostContext,
): null | Object {
const viewConfig = instance.canonical.viewConfig;
const viewConfig = instance.viewConfig;
const 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;
instance.currentProps = newProps;
return updatePayload;
}

Expand Down Expand Up @@ -467,7 +333,11 @@ export function cloneInstance(
}
return {
node: clone,
canonical: instance.canonical,
nativeTag: instance.nativeTag,
viewConfig: instance.viewConfig,
currentProps: instance.currentProps,
internalInstanceHandle: instance.internalInstanceHandle,
publicInstance: instance.publicInstance,
};
}

Expand All @@ -477,15 +347,19 @@ export function cloneHiddenInstance(
props: Props,
internalInstanceHandle: Object,
): Instance {
const viewConfig = instance.canonical.viewConfig;
const viewConfig = instance.viewConfig;
const node = instance.node;
const updatePayload = create(
{style: {display: 'none'}},
viewConfig.validAttributes,
);
return {
node: cloneNodeWithNewProps(node, updatePayload),
canonical: instance.canonical,
nativeTag: instance.nativeTag,
viewConfig: instance.viewConfig,
currentProps: instance.currentProps,
internalInstanceHandle: instance.internalInstanceHandle,
publicInstance: instance.publicInstance,
};
}

Expand Down
Loading