Skip to content

Commit

Permalink
Add getInspectorDataForViewAtPoint (take two) (#18388)
Browse files Browse the repository at this point in the history
* Add getInspectorDataForViewAtPoint (take two)

* Updates from review

* Add DEV to dev-only variable

* Missed this rename
  • Loading branch information
rickhanlonii committed Mar 30, 2020
1 parent d7382b6 commit dd7e5e4
Show file tree
Hide file tree
Showing 13 changed files with 260 additions and 24 deletions.
1 change: 1 addition & 0 deletions packages/react-dom/src/client/ReactDOMHostConfig.js
Expand Up @@ -145,6 +145,7 @@ export type UpdatePayload = Array<mixed>;
export type ChildSet = void; // Unused
export type TimeoutHandle = TimeoutID;
export type NoTimeout = -1;
export type RendererInspectionConfig = $ReadOnly<{||}>;

type SelectionInformation = {|
activeElementDetached: null | HTMLElement,
Expand Down
14 changes: 11 additions & 3 deletions packages/react-native-renderer/src/ReactFabric.js
Expand Up @@ -34,8 +34,10 @@ import ReactVersion from 'shared/ReactVersion';
import {UIManager} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';

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

import {
getInspectorDataForViewTag,
getInspectorDataForViewAtPoint,
} from './ReactNativeFiberInspector';
import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import getComponentName from 'shared/getComponentName';
Expand Down Expand Up @@ -232,8 +234,14 @@ export {

injectIntoDevTools({
findFiberByHostInstance: getClosestInstanceFromNode,
getInspectorDataForViewTag: getInspectorDataForViewTag,
bundleType: __DEV__ ? 1 : 0,
version: ReactVersion,
rendererPackageName: 'react-native-renderer',
rendererConfig: {
getInspectorDataForViewTag: getInspectorDataForViewTag,
getInspectorDataForViewAtPoint: getInspectorDataForViewAtPoint.bind(
null,
findNodeHandle,
),
},
});
12 changes: 12 additions & 0 deletions packages/react-native-renderer/src/ReactFabricHostConfig.js
Expand Up @@ -15,6 +15,7 @@ import type {
MeasureOnSuccessCallback,
NativeMethods,
ReactNativeBaseComponentViewConfig,
TouchedViewDataAtPoint,
} from './ReactNativeTypes';

import {mountSafeCallback_NOT_REALLY_SAFE} from './NativeMethodsMixinUtils';
Expand Down Expand Up @@ -80,6 +81,17 @@ export type ReactListenerEvent = Object;
export type ReactListenerMap = Object;
export type ReactListener = Object;

export type RendererInspectionConfig = $ReadOnly<{|
// Deprecated. Replaced with getInspectorDataForViewAtPoint.
getInspectorDataForViewTag?: (tag: number) => Object,
getInspectorDataForViewAtPoint?: (
inspectedView: Object,
locationX: number,
locationY: number,
callback: (viewData: TouchedViewDataAtPoint) => mixed,
) => void,
|}>;

// TODO: Remove this conditional once all changes have propagated.
if (registerEventHandler) {
/**
Expand Down
Expand Up @@ -33,12 +33,20 @@ import {
class ReactNativeFiberHostComponent {
_children: Array<Instance | number>;
_nativeTag: number;
_internalFiberInstanceHandleDEV: Object;
viewConfig: ReactNativeBaseComponentViewConfig<>;

constructor(tag: number, viewConfig: ReactNativeBaseComponentViewConfig<>) {
constructor(
tag: number,
viewConfig: ReactNativeBaseComponentViewConfig<>,
internalInstanceHandleDEV: Object,
) {
this._nativeTag = tag;
this._children = [];
this.viewConfig = viewConfig;
if (__DEV__) {
this._internalFiberInstanceHandleDEV = internalInstanceHandleDEV;
}
}

blur() {
Expand Down
143 changes: 133 additions & 10 deletions packages/react-native-renderer/src/ReactNativeFiberInspector.js
Expand Up @@ -8,6 +8,7 @@
*/

import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {TouchedViewDataAtPoint, InspectorData} 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 @@ -80,15 +82,59 @@ if (__DEV__) {
const createHierarchy = function(fiberHierarchy) {
return fiberHierarchy.map(fiber => ({
name: getComponentName(fiber.type),
getInspectorData: findNodeHandle => ({
measure: callback =>
UIManager.measure(getHostNode(fiber, findNodeHandle), callback),
props: getHostProps(fiber),
source: fiber._debugSource,
}),
getInspectorData: findNodeHandle => {
return {
props: getHostProps(fiber),
source: fiber._debugSource,
measure: callback => {
// If this is Fabric, we'll find a ShadowNode and use that to measure.
const hostFiber = findCurrentHostFiber(fiber);
const shadowNode =
hostFiber != null &&
hostFiber.stateNode !== null &&
hostFiber.stateNode.node;

if (shadowNode) {
nativeFabricUIManager.measure(shadowNode, callback);
} else {
return UIManager.measure(
getHostNode(fiber, findNodeHandle),
callback,
);
}
},
};
},
}));
};

const getInspectorDataForInstance = function(closestInstance): InspectorData {
// Handle case where user clicks outside of ReactNative
if (!closestInstance) {
return {
hierarchy: [],
props: emptyObject,
selectedIndex: 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 selectedIndex = fiberHierarchy.indexOf(instance);

return {
hierarchy,
props,
selectedIndex,
source,
};
};

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

Expand All @@ -97,7 +143,7 @@ if (__DEV__) {
return {
hierarchy: [],
props: emptyObject,
selection: null,
selectedIndex: null,
source: null,
};
}
Expand All @@ -108,22 +154,99 @@ if (__DEV__) {
const hierarchy = createHierarchy(fiberHierarchy);
const props = getHostProps(instance);
const source = instance._debugSource;
const selection = fiberHierarchy.indexOf(instance);
const selectedIndex = fiberHierarchy.indexOf(instance);

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

getInspectorDataForViewAtPoint = function(
findNodeHandle: (componentOrHandle: any) => ?number,
inspectedView: Object,
locationX: number,
locationY: number,
callback: (viewData: TouchedViewDataAtPoint) => mixed,
): void {
let closestInstance = null;

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,
internalInstanceHandle => {
if (internalInstanceHandle == null) {
callback({
pointerY: locationY,
frame: {left: 0, top: 0, width: 0, height: 0},
...getInspectorDataForInstance(closestInstance),
});
}

closestInstance =
internalInstanceHandle.stateNode.canonical._internalInstanceHandle;
nativeFabricUIManager.measure(
internalInstanceHandle.stateNode.node,
(x, y, width, height, pageX, pageY) => {
callback({
pointerY: locationY,
frame: {left: pageX, top: pageY, width, height},
...getInspectorDataForInstance(closestInstance),
});
},
);
},
);
} else if (inspectedView._internalFiberInstanceHandleDEV != 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),
);
callback({
...inspectorData,
pointerY: locationY,
frame: {left, top, width, height},
touchedViewTag: nativeViewTag,
});
},
);
} else {
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};
19 changes: 18 additions & 1 deletion packages/react-native-renderer/src/ReactNativeHostConfig.js
Expand Up @@ -7,6 +7,8 @@
* @flow
*/

import type {TouchedViewDataAtPoint} from './ReactNativeTypes';

import invariant from 'shared/invariant';

// Modules provided by RN:
Expand Down Expand Up @@ -46,6 +48,17 @@ export type ChildSet = void; // Unused
export type TimeoutHandle = TimeoutID;
export type NoTimeout = -1;

export type RendererInspectionConfig = $ReadOnly<{|
// Deprecated. Replaced with getInspectorDataForViewAtPoint.
getInspectorDataForViewTag?: (tag: number) => Object,
getInspectorDataForViewAtPoint?: (
inspectedView: Object,
locationX: number,
locationY: number,
callback: (viewData: TouchedViewDataAtPoint) => mixed,
) => void,
|}>;

const UPDATE_SIGNAL = {};
if (__DEV__) {
Object.freeze(UPDATE_SIGNAL);
Expand Down Expand Up @@ -112,7 +125,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
14 changes: 11 additions & 3 deletions packages/react-native-renderer/src/ReactNativeRenderer.js
Expand Up @@ -36,8 +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 'react-reconciler/src/ReactRootTags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import getComponentName from 'shared/getComponentName';
Expand Down Expand Up @@ -246,8 +248,14 @@ export {

injectIntoDevTools({
findFiberByHostInstance: getClosestInstanceFromNode,
getInspectorDataForViewTag: getInspectorDataForViewTag,
bundleType: __DEV__ ? 1 : 0,
version: ReactVersion,
rendererPackageName: 'react-native-renderer',
rendererConfig: {
getInspectorDataForViewTag: getInspectorDataForViewTag,
getInspectorDataForViewAtPoint: getInspectorDataForViewAtPoint.bind(
null,
findNodeHandle,
),
},
});
40 changes: 40 additions & 0 deletions packages/react-native-renderer/src/ReactNativeTypes.js
Expand Up @@ -100,6 +100,46 @@ type SecretInternalsType = {
...
};

type InspectorDataProps = $ReadOnly<{
[propName: string]: string,
...,
}>;

type InspectorDataSource = $ReadOnly<{|
fileName?: string,
lineNumber?: number,
|}>;

type InspectorDataGetter = (
(componentOrHandle: any) => ?number,
) => $ReadOnly<{|
measure: Function,
props: InspectorDataProps,
source: InspectorDataSource,
|}>;

export type InspectorData = $ReadOnly<{|
hierarchy: Array<{|
name: ?string,
getInspectorData: InspectorDataGetter,
|}>,
selectedIndex: ?number,
props: InspectorDataProps,
source: ?InspectorDataSource,
|}>;

export type TouchedViewDataAtPoint = $ReadOnly<{|
pointerY: number,
touchedViewTag?: number,
frame: $ReadOnly<{|
top: number,
left: number,
width: number,
height: number,
|}>,
...InspectorData,
|}>;

/**
* 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

0 comments on commit dd7e5e4

Please sign in to comment.