Permalink
Browse files

Refactor of Fiber integration with React Fiber + Stack

Summary:
This PR aims to update the Inspector tool in React Native to use the new inspection APIs that have been added to the ReactNative renderer:

facebook/react#9691

This PR also cleans up the code in `Inspector.js` so there's no usage of React's internals.
Closes #14160

Reviewed By: bvaughn

Differential Revision: D5129280

Pulled By: trueadm

fbshipit-source-id: b1b077c04f46b0f52cdea0e19b4154441558f77a
  • Loading branch information...
trueadm authored and facebook-github-bot committed May 30, 2017
1 parent da50811 commit 59e41b4485f9ee3ab10ac8c479089715ce02944b
@@ -12,8 +12,8 @@
'use strict';
const BoxInspector = require('BoxInspector');
const React = require('React');
const PropTypes = require('prop-types');
const React = require('React');
const StyleInspector = require('StyleInspector');
const StyleSheet = require('StyleSheet');
const Text = require('Text');
@@ -76,14 +76,14 @@ class ElementProperties extends React.Component {
<View style={styles.breadcrumb}>
{mapWithSeparator(
this.props.hierarchy,
(item, i) => (
(hierarchyItem, i) => (
<TouchableHighlight
key={'item-' + i}
style={[styles.breadItem, i === selection && styles.selected]}
// $FlowFixMe found when converting React.createClass to ES6
onPress={() => this.props.setSelection(i)}>
<Text style={styles.breadItemText}>
{getInstanceName(item)}
{hierarchyItem.name}
</Text>
</TouchableHighlight>
),
@@ -109,16 +109,6 @@ class ElementProperties extends React.Component {
}
}
function getInstanceName(instance) {
if (instance.getName) {
return instance.getName();
}
if (instance.constructor && instance.constructor.displayName) {
return instance.constructor.displayName;
}
return 'Unknown';
}
const styles = StyleSheet.create({
breadSep: {
fontSize: 8,
@@ -17,17 +17,31 @@
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');
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');
const emptyObject = require('fbjs/lib/emptyObject');
const invariant = require('invariant');
export type ReactRenderer = {
getInspectorDataForViewTag: (viewTag: number) => Object,
};
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
const renderer: ReactRenderer = findRenderer();
// required for devtools to be able to edit react native styles
hook.resolveRNStyle = require('flattenStyle');
function findRenderer(): ReactRenderer {
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]];
}
class Inspector extends React.Component {
@@ -67,23 +81,18 @@ class Inspector extends React.Component {
}
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);
}
hook.on('react-devtools', this.attachToDevtools);
// if devtools is already started
if (hook.reactDevtoolsAgent) {
this.attachToDevtools(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);
}
hook.off('react-devtools', this.attachToDevtools);
}
componentWillReceiveProps(newProps: Object) {
@@ -94,12 +103,18 @@ 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: [],
inspected: {
frame: {left, top, width, height},
style: props ? props.style : {},
style: props ? props.style : emptyObject,
},
});
});
@@ -126,46 +141,50 @@ 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 hierarchyItem = this.state.hierarchy[i];
// we pass in ReactNative.findNodeHandle as the method is injected
const {
measure,
props,
source,
} = hierarchyItem.getInspectorData(ReactNative.findNodeHandle);
measure((x, y, width, height, left, top) => {
this.setState({
inspected: {
frame: {left, top, width, height},
style: publicInstance.props ? publicInstance.props.style : {},
style: props.style,
source,
},
selection: i,
});
});
}
onTouchInstance(touched: Object, frame: Object, pointerY: number) {
onTouchViewTag(touchedViewTag: number, frame: Object, pointerY: number) {
// 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,
instance,
props,
selection,
source,
} = renderer.getInspectorDataForViewTag(touchedViewTag);
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'];
this.setState({
panelPos: pointerY > Dimensions.get('window').height / 2 ? 'top' : 'bottom',
selection: hierarchy.indexOf(instance),
selection,
hierarchy,
inspected: {
style: props.style || {},
style: props.style,
frame,
source,
},
@@ -214,7 +233,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
@@ -12,13 +12,12 @@
'use strict';
var Dimensions = require('Dimensions');
var InspectorUtils = require('InspectorUtils');
var React = require('React');
var ElementBox = require('ElementBox');
var PropTypes = require('prop-types');
var React = require('React');
var StyleSheet = require('StyleSheet');
var UIManager = require('UIManager');
var View = require('View');
var ElementBox = require('ElementBox');
type EventLike = {
nativeEvent: Object,
@@ -31,7 +30,7 @@ class InspectorOverlay extends React.Component {
style?: any,
},
inspectedViewTag?: number,
onTouchInstance: Function,
onTouchViewTag: (tag: number, frame: Object, pointerY: number) => void,
};
static propTypes = {
@@ -40,7 +39,7 @@ class InspectorOverlay extends React.Component {
style: PropTypes.any,
}),
inspectedViewTag: PropTypes.number,
onTouchInstance: PropTypes.func.isRequired,
onTouchViewTag: PropTypes.func.isRequired,
};
findViewForTouchEvent = (e: EventLike) => {
@@ -49,11 +48,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);
}
);
};
@@ -1,47 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule InspectorUtils
*/
'use strict';
var ReactNativeComponentTree = require('ReactNativeComponentTree');
function traverseOwnerTreeUp(hierarchy, instance) {
if (instance) {
hierarchy.unshift(instance);
traverseOwnerTreeUp(hierarchy, instance._currentElement._owner);
}
}
function findInstanceByNativeTag(nativeTag) {
var instance = ReactNativeComponentTree.getInstanceFromNode(nativeTag);
if (!instance || typeof instance.tag === 'number') {
// TODO(sema): We've disabled the inspector when using Fiber. Fix #15953531
return null;
}
return instance;
}
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];
}
module.exports = {findInstanceByNativeTag, getOwnerHierarchy, lastNotNativeInstance};
Oops, something went wrong.

0 comments on commit 59e41b4

Please sign in to comment.