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

[React Native] Add getInspectorDataForViewAtPoint #18233

Merged
merged 11 commits into from
Mar 11, 2020
9 changes: 8 additions & 1 deletion packages/react-native-renderer/src/ReactFabric.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ import ReactVersion from 'shared/ReactVersion';
import {UIManager} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';

import {getClosestInstanceFromNode} from './ReactFabricComponentTree';
import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
import {
getInspectorDataForViewAtPoint,
getInspectorDataForViewTag,
} from './ReactNativeFiberInspector';

import {LegacyRoot} from 'shared/ReactRootTags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
Expand Down Expand Up @@ -233,6 +236,10 @@ export {
injectIntoDevTools({
findFiberByHostInstance: getClosestInstanceFromNode,
getInspectorDataForViewTag: getInspectorDataForViewTag,
rickhanlonii marked this conversation as resolved.
Show resolved Hide resolved
getInspectorDataForViewAtPoint: getInspectorDataForViewAtPoint.bind(
null,
findNodeHandle,
),
bundleType: __DEV__ ? 1 : 0,
version: ReactVersion,
rendererPackageName: 'react-native-renderer',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,17 @@ class ReactNativeFiberHostComponent {
_children: Array<Instance | number>;
_nativeTag: number;
viewConfig: ReactNativeBaseComponentViewConfig<>;
_internalFiberInstanceHandle: Object;

constructor(tag: number, viewConfig: ReactNativeBaseComponentViewConfig<>) {
constructor(
tag: number,
viewConfig: ReactNativeBaseComponentViewConfig<>,
internalInstanceHandle: Object,
) {
this._nativeTag = tag;
this._children = [];
this.viewConfig = viewConfig;
this._internalFiberInstanceHandle = internalInstanceHandle;
}

blur() {
Expand Down
112 changes: 111 additions & 1 deletion packages/react-native-renderer/src/ReactNativeFiberInspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {TouchedViewDataAtPoint} from './ReactNativeTypes';

import {
findCurrentHostFiber,
Expand All @@ -27,6 +28,7 @@ if (__DEV__) {
}

let getInspectorDataForViewTag;
let getInspectorDataForViewAtPoint;

if (__DEV__) {
const traverseOwnerTreeUp = function(hierarchy, instance: any) {
Expand Down Expand Up @@ -89,6 +91,34 @@ if (__DEV__) {
}));
};

const getInspectorDataForInstance = function(closestInstance, frame): Object {
rickhanlonii marked this conversation as resolved.
Show resolved Hide resolved
// Handle case where user clicks outside of ReactNative
if (!closestInstance) {
return {
hierarchy: [],
props: emptyObject,
selection: null,
source: null,
};
}

const fiber = findCurrentFiberUsingSlowPath(closestInstance);
const fiberHierarchy = getOwnerHierarchy(fiber);
const instance = lastNonHostInstance(fiberHierarchy);
const hierarchy = createHierarchy(fiberHierarchy);
const props = getHostProps(instance);
const source = instance._debugSource;
const selection = fiberHierarchy.indexOf(instance);

return {
hierarchy,
frame,
props,
selection,
source,
};
};

getInspectorDataForViewTag = function(viewTag: number): Object {
const closestInstance = getClosestInstanceFromNode(viewTag);

Expand Down Expand Up @@ -117,13 +147,93 @@ if (__DEV__) {
source,
};
};

getInspectorDataForViewAtPoint = function(
findNodeHandle: (componentOrHandle: any) => ?number,
inspectedView: Object,
locationX: number,
locationY: number,
callback: (viewData: TouchedViewDataAtPoint) => mixed,
): void {
let closestInstance = null;
let frame = {
rickhanlonii marked this conversation as resolved.
Show resolved Hide resolved
left: 0,
top: 0,
width: 0,
height: 0,
};

if (inspectedView._internalInstanceHandle != null) {
// For Fabric we can look up the instance handle directly and measure it.
nativeFabricUIManager.findNodeAtPoint(
inspectedView._internalInstanceHandle.stateNode.node,
locationX,
locationY,
shadowNode => {
rickhanlonii marked this conversation as resolved.
Show resolved Hide resolved
if (shadowNode == null) {
callback(getInspectorDataForInstance(closestInstance, frame));
}

closestInstance =
shadowNode.stateNode.canonical._internalInstanceHandle;
nativeFabricUIManager.measure(
shadowNode.stateNode.node,
(x, y, width, height, pageX, pageY) => {
const inspectorData = getInspectorDataForInstance(
closestInstance,
);
callback({
...inspectorData,
frame: {left: pageX, top: pageY, width, height},
});
},
);
},
);
} else if (inspectedView._internalFiberInstanceHandle != null) {
// For Paper we fall back to the old strategy using the React tag.
UIManager.findSubviewIn(
findNodeHandle(inspectedView),
[locationX, locationY],
(nativeViewTag, left, top, width, height) => {
const inspectorData = getInspectorDataForInstance(
getClosestInstanceFromNode(nativeViewTag),
rickhanlonii marked this conversation as resolved.
Show resolved Hide resolved
);
callback({
...inspectorData,
frame: {left, top, width, height},
touchedViewTag: nativeViewTag,
});
},
);
} else if (__DEV__) {
console.error(
'getInspectorDataForViewAtPoint expects to receieve a host component',
);

return;
}
};
} else {
getInspectorDataForViewTag = () => {
invariant(
false,
'getInspectorDataForViewTag() is not available in production',
);
};

getInspectorDataForViewAtPoint = (
findNodeHandle: (componentOrHandle: any) => ?number,
inspectedView: Object,
locationX: number,
locationY: number,
callback: (viewData: TouchedViewDataAtPoint) => mixed,
): void => {
invariant(
false,
'getInspectorDataForViewAtPoint() is not available in production',
);
};
}

export {getInspectorDataForViewTag};
export {getInspectorDataForViewAtPoint, getInspectorDataForViewTag};
6 changes: 5 additions & 1 deletion packages/react-native-renderer/src/ReactNativeHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,11 @@ export function createInstance(
updatePayload, // props
);

const component = new ReactNativeFiberHostComponent(tag, viewConfig);
const component = new ReactNativeFiberHostComponent(
tag,
viewConfig,
internalInstanceHandle,
);

precacheFiberNode(internalInstanceHandle, tag);
updateFiberProps(tag, props);
Expand Down
9 changes: 8 additions & 1 deletion packages/react-native-renderer/src/ReactNativeRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ import ReactVersion from 'shared/ReactVersion';
import {UIManager} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';

import {getClosestInstanceFromNode} from './ReactNativeComponentTree';
import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
import {
getInspectorDataForViewTag,
getInspectorDataForViewAtPoint,
} from './ReactNativeFiberInspector';

import {LegacyRoot} from 'shared/ReactRootTags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
Expand Down Expand Up @@ -247,6 +250,10 @@ export {
injectIntoDevTools({
findFiberByHostInstance: getClosestInstanceFromNode,
getInspectorDataForViewTag: getInspectorDataForViewTag,
getInspectorDataForViewAtPoint: getInspectorDataForViewAtPoint.bind(
null,
findNodeHandle,
),
bundleType: __DEV__ ? 1 : 0,
version: ReactVersion,
rendererPackageName: 'react-native-renderer',
Expand Down
18 changes: 18 additions & 0 deletions packages/react-native-renderer/src/ReactNativeTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,24 @@ type SecretInternalsType = {
...
};

export type TouchedViewDataAtPoint = $ReadOnly<{
hierarchy?: ?Array<{|name: string|}>,
pointerY: number,
touchedViewTag?: ?number,
props: $ReadOnly<{[propName: string]: string, ...}>,
selection: number,
source: $ReadOnly<{|
fileName?: string,
lineNumber?: number,
|}>,
frame?: ?$ReadOnly<{|
top?: ?number,
left?: ?number,
width?: ?number,
height: ?number,
|}>,
}>;

/**
* Flat ReactNative renderer bundles are too big for Flow to parse efficiently.
* Provide minimal Flow typing for the high-level RN API and call it a day.
Expand Down
8 changes: 8 additions & 0 deletions packages/react-reconciler/src/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import type {Fiber} from './ReactFiber';
import type {FiberRoot} from './ReactFiberRoot';
import type {RootTag} from 'shared/ReactRootTags';
import type {TouchedViewDataAtPoint} from 'react-native-renderer/src/ReactNativeTypes';
Copy link
Contributor

Choose a reason for hiding this comment

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

This import is causing CI to fail.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We can't have dependencies on RN from the reconciler bundle. The folders are checked independently.

Also from a layering perspective it's probably not right.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a little confused that some of the commits seemed to pass CI

import type {
Instance,
TextInstance,
Expand Down Expand Up @@ -109,6 +110,13 @@ type DevToolsConfig = {|
// This API is unfortunately RN-specific.
// TODO: Change it to accept Fiber instead and type it properly.
getInspectorDataForViewTag?: (tag: number) => Object,
// Used by RN in-app inspector.
getInspectorDataForViewAtPoint?: (
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think either of these two should be part of this type because they're not actually used by the core DevTools protocol. It's kind of an adhoc RN specific addition.

Instead we should make this a renderer specific object.

rendererConfig: RendererInspectionConfig

Then you can define the type for RendererInspectionConfig through the HostConfig mechanism. So that each renderer gets their own type. That can then be safely referenced in the react-native folder.

So Fabric and RN host configs gets this type:

export type RendererInspectionConfig = {
  getInspectorDataForViewTag?: (tag: number) => Object,
  getInspectorDataForViewAtPoint?: (...) => ...;
};

You then import that from here using:

import type {RendererInspectionConfig} from './ReactFiberHostConfig';

Copy link
Contributor

Choose a reason for hiding this comment

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

I like this suggestion.

inspectedView: Object,
locationX: number,
locationY: number,
callback: (viewData: TouchedViewDataAtPoint) => mixed,
) => void,
|};

let didWarnAboutNestedUpdates;
Expand Down
3 changes: 2 additions & 1 deletion scripts/error-codes/codes.json
Original file line number Diff line number Diff line change
Expand Up @@ -346,5 +346,6 @@
"345": "Root did not complete. This is a bug in React.",
"346": "An event responder context was used outside of an event cycle.",
"347": "Maps are not valid as a React child (found: %s). Consider converting children to an array of keyed ReactElements instead.",
"348": "ensureListeningTo(): received a container that was not an element node. This is likely a bug in React."
"348": "ensureListeningTo(): received a container that was not an element node. This is likely a bug in React.",
"349": "getInspectorDataForViewAtPoint() is not available in production"
rickhanlonii marked this conversation as resolved.
Show resolved Hide resolved
}
18 changes: 18 additions & 0 deletions scripts/flow/react-native-host-hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
} from 'react-native-renderer/src/ReactNativeTypes';
import type {RNTopLevelEventType} from 'legacy-events/TopLevelEventTypes';
import type {CapturedError} from 'react-reconciler/src/ReactCapturedValue';
import type {Fiber} from 'react-reconciler/src/ReactFiber';

type DeepDifferOptions = {|+unsafelyIgnoreFunctions?: boolean|};

Expand Down Expand Up @@ -96,6 +97,17 @@ declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'
) => Promise<any>,
setJSResponder: (reactTag: number, blockNativeResponder: boolean) => void,
clearJSResponder: () => void,
findSubviewIn: (
reactTag: ?number,
point: Array<number>,
callback: (
nativeViewTag: number,
left: number,
top: number,
width: number,
height: number,
) => void,
) => void,
...
};
declare export var BatchedBridge: {
Expand Down Expand Up @@ -156,6 +168,12 @@ declare var nativeFabricUIManager: {
onFail: () => void,
onSuccess: MeasureLayoutOnSuccessCallback,
) => void,
findNodeAtPoint: (
node: Node,
locationX: number,
locationY: number,
callback: (Fiber) => void,
) => void,
...
};

Expand Down