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

Add getInspectorDataForViewAtPoint (take two) #18388

Merged
merged 4 commits into from Mar 30, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/react-dom/src/client/ReactDOMHostConfig.js
Expand Up @@ -140,6 +140,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