Skip to content

[WIP][Not for merge] Support Inspector in Fiber #12421

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

Closed
wants to merge 2 commits into from
Closed
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
9 changes: 9 additions & 0 deletions Libraries/Inspector/ElementProperties.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ class ElementProperties extends React.Component {
}

function getInstanceName(instance) {
if (typeof instance.tag === 'number') {
if (typeof instance.type === 'string') {
return instance.type;
}
if (typeof instance.type === 'function') {
return instance.type.displayName || instance.type.name || 'Unknown';
}
return 'Unknown';
}
if (instance.getName) {
return instance.getName();
}
Expand Down
157 changes: 128 additions & 29 deletions Libraries/Inspector/Inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,61 @@
const Dimensions = require('Dimensions');
const InspectorOverlay = require('InspectorOverlay');
const InspectorPanel = require('InspectorPanel');
const InspectorUtils = require('InspectorUtils');
const Platform = require('Platform');
const React = require('React');
const ReactNative = require('ReactNative');
const StyleSheet = require('StyleSheet');
const Touchable = require('Touchable');
const UIManager = require('UIManager');
const View = require('View');
const invariant = require('invariant');

if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
// required for devtools to be able to edit react native styles
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.resolveRNStyle = require('flattenStyle');
export type ReactRenderer = {
// Fiber
findHostInstanceByFiber: (fiber: any) => ?number,
findFiberByHostInstance: (hostInstance: number) => any,

// Stack
ComponentTree: {
getNodeFromInstance: (instance: any) => ?number,
getClosestInstanceFromNode: (tag: number) => any,
},
};

function findRenderer(): ReactRenderer {
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we centralize the place we read this to a single place since it is kind of a hack? Even if it is a function call. Also, can we avoid window? It's lame that we have to add a global window object in RN.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

A single place for reading the global object, is that what you mean? Like getHook()?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yea

const renderers = hook._renderers;
const keys = Object.keys(renderers);
invariant(keys.length === 1, 'Expected to find exactly one React Native renderer on DevTools hook.');
return renderers[keys[0]];
}

function traverseOwnerTreeUp(hierarchy, instance) {
if (instance) {
hierarchy.unshift(instance);
const owner = typeof instance.tag === 'number' ?
// Fiber
instance._debugOwner :
// Stack
instance._currentElement._owner;
traverseOwnerTreeUp(hierarchy, owner);
}
}

function getOwnerHierarchy(instance) {
var hierarchy = [];
traverseOwnerTreeUp(hierarchy, instance);
return hierarchy;
}

function lastNotNativeInstance(hierarchy) {
for (let i = hierarchy.length - 1; i > 1; i--) {
const instance = hierarchy[i];
if (!instance.viewConfig) {
return instance;
}
}
return hierarchy[0];
}

class Inspector extends React.Component {
Expand All @@ -49,6 +93,7 @@ class Inspector extends React.Component {
};

_subs: ?Array<() => void>;
renderer: ReactRenderer;

constructor(props: Object) {
super(props);
Expand All @@ -64,26 +109,23 @@ class Inspector extends React.Component {
inspectedViewTag: this.props.inspectedViewTag,
networking: false,
};

this.renderer = findRenderer();
}

componentDidMount() {
if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
(this : any).attachToDevtools = this.attachToDevtools.bind(this);
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('react-devtools', this.attachToDevtools);
// if devtools is already started
if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__.reactDevtoolsAgent) {
this.attachToDevtools(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.reactDevtoolsAgent);
}
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('react-devtools', this.attachToDevtools);
// if devtools is already started
if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__.reactDevtoolsAgent) {
this.attachToDevtools(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.reactDevtoolsAgent);
}
}

componentWillUnmount() {
if (this._subs) {
this._subs.map(fn => fn());
}
if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.off('react-devtools', this.attachToDevtools);
}
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.off('react-devtools', this.attachToDevtools);
}

componentWillReceiveProps(newProps: Object) {
Expand All @@ -94,6 +136,12 @@ class Inspector extends React.Component {
let _hideWait = null;
const hlSub = agent.sub('highlight', ({node, name, props}) => {
clearTimeout(_hideWait);

if (typeof node !== 'number') {
// Fiber
node = ReactNative.findNodeHandle(node);
}

UIManager.measure(node, (x, y, width, height, left, top) => {
this.setState({
hierarchy: [],
Expand Down Expand Up @@ -128,38 +176,89 @@ class Inspector extends React.Component {

setSelection(i: number) {
const instance = this.state.hierarchy[i];
// if we inspect a stateless component we can't use the getPublicInstance method
// therefore we use the internal _instance property directly.
const publicInstance = instance['_instance'] || {};
const source = instance['_currentElement'] && instance['_currentElement']['_source'];
UIManager.measure(instance.getHostNode(), (x, y, width, height, left, top) => {
const props = typeof instance.tag === 'number' ?
// Fiber
instance.memoizedProps || {} :
// Stack
(instance['_instance'] || {}).props || {};
const source = typeof instance.tag === 'number' ?
// Fiber
instance._debugSource :
// Stack
instance['_currentElement'] && instance['_currentElement']['_source'];

let hostNode;
if (typeof instance.tag === 'number') {
let fiber = instance;
// Stateless components make this complicated.
// Look for children first.
while (fiber) {
hostNode = ReactNative.findNodeHandle(fiber.stateNode);
if (hostNode) {
break;
}
fiber = fiber.child;
}
// Look for parents second.
fiber = instance.return;
while (fiber) {
hostNode = ReactNative.findNodeHandle(fiber.stateNode);
if (hostNode) {
break;
}
fiber = fiber.return;
}
if (!hostNode) {
// Not found--hard to imagine.
return;
}
} else {
hostNode = instance.getHostNode();
}

UIManager.measure(hostNode, (x, y, width, height, left, top) => {
this.setState({
inspected: {
frame: {left, top, width, height},
style: publicInstance.props ? publicInstance.props.style : {},
style: props ? props.style : {},
source,
},
selection: i,
});
});
}

onTouchInstance(touched: Object, frame: Object, pointerY: number) {
onTouchViewTag(touchedViewTag: number, frame: Object, pointerY: number) {
const touched = typeof this.renderer.findFiberByHostInstance === 'function' ?
// Fiber
this.renderer.findFiberByHostInstance(touchedViewTag) :
// Stack
this.renderer.ComponentTree.getClosestInstanceFromNode(touchedViewTag);
if (!touched) {
return;
}

// Most likely the touched instance is a native wrapper (like RCTView)
// which is not very interesting. Most likely user wants a composite
// instance that contains it (like View)
const hierarchy = InspectorUtils.getOwnerHierarchy(touched);
const instance = InspectorUtils.lastNotNativeInstance(hierarchy);
const hierarchy = getOwnerHierarchy(touched);
const instance = lastNotNativeInstance(hierarchy);

if (this.state.devtoolsAgent) {
this.state.devtoolsAgent.selectFromReactInstance(instance, true);
}

// if we inspect a stateless component we can't use the getPublicInstance method
// therefore we use the internal _instance property directly.
const publicInstance = instance['_instance'] || {};
const props = publicInstance.props || {};
const source = instance['_currentElement'] && instance['_currentElement']['_source'];
const props = typeof instance.tag === 'number' ?
// Fiber
instance.memoizedProps || {} :
// Stack
(instance['_instance'] || {}).props || {};
const source = typeof instance.tag === 'number' ?
// Fiber
instance._debugSource :
// Stack
instance['_currentElement'] && instance['_currentElement']['_source'];

this.setState({
panelPos: pointerY > Dimensions.get('window').height / 2 ? 'top' : 'bottom',
selection: hierarchy.indexOf(instance),
Expand Down Expand Up @@ -214,7 +313,7 @@ class Inspector extends React.Component {
<InspectorOverlay
inspected={this.state.inspected}
inspectedViewTag={this.state.inspectedViewTag}
onTouchInstance={this.onTouchInstance.bind(this)}
onTouchViewTag={this.onTouchViewTag.bind(this)}
/>}
<View style={[styles.panelContainer, panelContainerStyle]}>
<InspectorPanel
Expand Down
11 changes: 3 additions & 8 deletions Libraries/Inspector/InspectorOverlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
'use strict';

var Dimensions = require('Dimensions');
var InspectorUtils = require('InspectorUtils');
var React = require('React');
var StyleSheet = require('StyleSheet');
var UIManager = require('UIManager');
Expand All @@ -32,7 +31,7 @@ class InspectorOverlay extends React.Component {
style?: any,
},
inspectedViewTag?: number,
onTouchInstance: Function,
onTouchViewTag: (tag: number) => void,
};

Choose a reason for hiding this comment

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

undefined (too few arguments, expected default/rest parameters) This type is incompatible with number

Choose a reason for hiding this comment

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

undefined (too few arguments, expected default/rest parameters) This type is incompatible with object type


static propTypes = {
Expand All @@ -41,7 +40,7 @@ class InspectorOverlay extends React.Component {
style: PropTypes.any,
}),
inspectedViewTag: PropTypes.number,
onTouchInstance: PropTypes.func.isRequired,
onTouchViewTag: PropTypes.func.isRequired,
};

findViewForTouchEvent = (e: EventLike) => {
Expand All @@ -50,11 +49,7 @@ class InspectorOverlay extends React.Component {
this.props.inspectedViewTag,
[locationX, locationY],
(nativeViewTag, left, top, width, height) => {
var instance = InspectorUtils.findInstanceByNativeTag(nativeViewTag);
if (!instance) {
return;
}
this.props.onTouchInstance(instance, {left, top, width, height}, locationY);
this.props.onTouchViewTag(nativeViewTag, {left, top, width, height}, locationY);
}
);
};
Expand Down
47 changes: 0 additions & 47 deletions Libraries/Inspector/InspectorUtils.js

This file was deleted.