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:

https://github.com/facebook/react/pull/9691

This PR also cleans up the code in `Inspector.js` so there's no usage of React's internals.
Closes https://github.com/facebook/react-native/pull/14160

Reviewed By: bvaughn

Differential Revision: D5129280

Pulled By: trueadm

fbshipit-source-id: b1b077c04f46b0f52cdea0e19b4154441558f77a
This commit is contained in:
Dominic Gannaway 2017-05-30 09:55:20 -07:00 committed by Facebook Github Bot
parent da50811609
commit 59e41b4485
8 changed files with 1098 additions and 1135 deletions

View File

@ -12,8 +12,8 @@
'use strict'; 'use strict';
const BoxInspector = require('BoxInspector'); const BoxInspector = require('BoxInspector');
const React = require('React');
const PropTypes = require('prop-types'); const PropTypes = require('prop-types');
const React = require('React');
const StyleInspector = require('StyleInspector'); const StyleInspector = require('StyleInspector');
const StyleSheet = require('StyleSheet'); const StyleSheet = require('StyleSheet');
const Text = require('Text'); const Text = require('Text');
@ -76,14 +76,14 @@ class ElementProperties extends React.Component {
<View style={styles.breadcrumb}> <View style={styles.breadcrumb}>
{mapWithSeparator( {mapWithSeparator(
this.props.hierarchy, this.props.hierarchy,
(item, i) => ( (hierarchyItem, i) => (
<TouchableHighlight <TouchableHighlight
key={'item-' + i} key={'item-' + i}
style={[styles.breadItem, i === selection && styles.selected]} style={[styles.breadItem, i === selection && styles.selected]}
// $FlowFixMe found when converting React.createClass to ES6 // $FlowFixMe found when converting React.createClass to ES6
onPress={() => this.props.setSelection(i)}> onPress={() => this.props.setSelection(i)}>
<Text style={styles.breadItemText}> <Text style={styles.breadItemText}>
{getInstanceName(item)} {hierarchyItem.name}
</Text> </Text>
</TouchableHighlight> </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({ const styles = StyleSheet.create({
breadSep: { breadSep: {
fontSize: 8, fontSize: 8,

View File

@ -17,17 +17,31 @@
const Dimensions = require('Dimensions'); const Dimensions = require('Dimensions');
const InspectorOverlay = require('InspectorOverlay'); const InspectorOverlay = require('InspectorOverlay');
const InspectorPanel = require('InspectorPanel'); const InspectorPanel = require('InspectorPanel');
const InspectorUtils = require('InspectorUtils');
const Platform = require('Platform'); const Platform = require('Platform');
const React = require('React'); const React = require('React');
const ReactNative = require('ReactNative');
const StyleSheet = require('StyleSheet'); const StyleSheet = require('StyleSheet');
const Touchable = require('Touchable'); const Touchable = require('Touchable');
const UIManager = require('UIManager'); const UIManager = require('UIManager');
const View = require('View'); const View = require('View');
if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) { const emptyObject = require('fbjs/lib/emptyObject');
// required for devtools to be able to edit react native styles const invariant = require('invariant');
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.resolveRNStyle = require('flattenStyle');
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 { class Inspector extends React.Component {
@ -67,13 +81,10 @@ class Inspector extends React.Component {
} }
componentDidMount() { componentDidMount() {
if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) { hook.on('react-devtools', this.attachToDevtools);
(this : any).attachToDevtools = this.attachToDevtools.bind(this);
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('react-devtools', this.attachToDevtools);
// if devtools is already started // if devtools is already started
if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__.reactDevtoolsAgent) { if (hook.reactDevtoolsAgent) {
this.attachToDevtools(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.reactDevtoolsAgent); this.attachToDevtools(hook.reactDevtoolsAgent);
}
} }
} }
@ -81,9 +92,7 @@ class Inspector extends React.Component {
if (this._subs) { if (this._subs) {
this._subs.map(fn => fn()); this._subs.map(fn => fn());
} }
if (window.__REACT_DEVTOOLS_GLOBAL_HOOK__) { hook.off('react-devtools', this.attachToDevtools);
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.off('react-devtools', this.attachToDevtools);
}
} }
componentWillReceiveProps(newProps: Object) { componentWillReceiveProps(newProps: Object) {
@ -94,12 +103,18 @@ class Inspector extends React.Component {
let _hideWait = null; let _hideWait = null;
const hlSub = agent.sub('highlight', ({node, name, props}) => { const hlSub = agent.sub('highlight', ({node, name, props}) => {
clearTimeout(_hideWait); clearTimeout(_hideWait);
if (typeof node !== 'number') {
// Fiber
node = ReactNative.findNodeHandle(node);
}
UIManager.measure(node, (x, y, width, height, left, top) => { UIManager.measure(node, (x, y, width, height, left, top) => {
this.setState({ this.setState({
hierarchy: [], hierarchy: [],
inspected: { inspected: {
frame: {left, top, width, height}, frame: {left, top, width, height},
style: props ? props.style : {}, style: props ? props.style : emptyObject,
}, },
}); });
}); });
@ -126,17 +141,21 @@ class Inspector extends React.Component {
}); });
}; };
setSelection(i: number) { setSelection(i: number) {
const instance = this.state.hierarchy[i]; const hierarchyItem = this.state.hierarchy[i];
// if we inspect a stateless component we can't use the getPublicInstance method // we pass in ReactNative.findNodeHandle as the method is injected
// therefore we use the internal _instance property directly. const {
const publicInstance = instance['_instance'] || {}; measure,
const source = instance['_currentElement'] && instance['_currentElement']['_source']; props,
UIManager.measure(instance.getHostNode(), (x, y, width, height, left, top) => { source,
} = hierarchyItem.getInspectorData(ReactNative.findNodeHandle);
measure((x, y, width, height, left, top) => {
this.setState({ this.setState({
inspected: { inspected: {
frame: {left, top, width, height}, frame: {left, top, width, height},
style: publicInstance.props ? publicInstance.props.style : {}, style: props.style,
source, source,
}, },
selection: i, selection: i,
@ -144,28 +163,28 @@ class Inspector extends React.Component {
}); });
} }
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) // Most likely the touched instance is a native wrapper (like RCTView)
// which is not very interesting. Most likely user wants a composite // which is not very interesting. Most likely user wants a composite
// instance that contains it (like View) // instance that contains it (like View)
const hierarchy = InspectorUtils.getOwnerHierarchy(touched); const {
const instance = InspectorUtils.lastNotNativeInstance(hierarchy); hierarchy,
instance,
props,
selection,
source,
} = renderer.getInspectorDataForViewTag(touchedViewTag);
if (this.state.devtoolsAgent) { if (this.state.devtoolsAgent) {
this.state.devtoolsAgent.selectFromReactInstance(instance, true); 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({ this.setState({
panelPos: pointerY > Dimensions.get('window').height / 2 ? 'top' : 'bottom', panelPos: pointerY > Dimensions.get('window').height / 2 ? 'top' : 'bottom',
selection: hierarchy.indexOf(instance), selection,
hierarchy, hierarchy,
inspected: { inspected: {
style: props.style || {}, style: props.style,
frame, frame,
source, source,
}, },
@ -214,7 +233,7 @@ class Inspector extends React.Component {
<InspectorOverlay <InspectorOverlay
inspected={this.state.inspected} inspected={this.state.inspected}
inspectedViewTag={this.state.inspectedViewTag} inspectedViewTag={this.state.inspectedViewTag}
onTouchInstance={this.onTouchInstance.bind(this)} onTouchViewTag={this.onTouchViewTag.bind(this)}
/>} />}
<View style={[styles.panelContainer, panelContainerStyle]}> <View style={[styles.panelContainer, panelContainerStyle]}>
<InspectorPanel <InspectorPanel

View File

@ -12,13 +12,12 @@
'use strict'; 'use strict';
var Dimensions = require('Dimensions'); var Dimensions = require('Dimensions');
var InspectorUtils = require('InspectorUtils'); var ElementBox = require('ElementBox');
var React = require('React');
var PropTypes = require('prop-types'); var PropTypes = require('prop-types');
var React = require('React');
var StyleSheet = require('StyleSheet'); var StyleSheet = require('StyleSheet');
var UIManager = require('UIManager'); var UIManager = require('UIManager');
var View = require('View'); var View = require('View');
var ElementBox = require('ElementBox');
type EventLike = { type EventLike = {
nativeEvent: Object, nativeEvent: Object,
@ -31,7 +30,7 @@ class InspectorOverlay extends React.Component {
style?: any, style?: any,
}, },
inspectedViewTag?: number, inspectedViewTag?: number,
onTouchInstance: Function, onTouchViewTag: (tag: number, frame: Object, pointerY: number) => void,
}; };
static propTypes = { static propTypes = {
@ -40,7 +39,7 @@ class InspectorOverlay extends React.Component {
style: PropTypes.any, style: PropTypes.any,
}), }),
inspectedViewTag: PropTypes.number, inspectedViewTag: PropTypes.number,
onTouchInstance: PropTypes.func.isRequired, onTouchViewTag: PropTypes.func.isRequired,
}; };
findViewForTouchEvent = (e: EventLike) => { findViewForTouchEvent = (e: EventLike) => {
@ -49,11 +48,7 @@ class InspectorOverlay extends React.Component {
this.props.inspectedViewTag, this.props.inspectedViewTag,
[locationX, locationY], [locationX, locationY],
(nativeViewTag, left, top, width, height) => { (nativeViewTag, left, top, width, height) => {
var instance = InspectorUtils.findInstanceByNativeTag(nativeViewTag); this.props.onTouchViewTag(nativeViewTag, {left, top, width, height}, locationY);
if (!instance) {
return;
}
this.props.onTouchInstance(instance, {left, top, width, height}, locationY);
} }
); );
}; };

View File

@ -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};

View File

@ -2914,6 +2914,9 @@ ReactNativeBaseComponent.Mixin = {
updatePayload && UIManager.updateView(this._rootNodeID, this.viewConfig.uiViewClassName, updatePayload), updatePayload && UIManager.updateView(this._rootNodeID, this.viewConfig.uiViewClassName, updatePayload),
this.updateChildren(nextElement.props.children, transaction, context); this.updateChildren(nextElement.props.children, transaction, context);
}, },
getName: function() {
return this.constructor.displayName || this.constructor.name || "Unknown";
},
getHostNode: function() { getHostNode: function() {
return this._rootNodeID; return this._rootNodeID;
}, },

View File

@ -2406,6 +2406,9 @@ ReactNativeBaseComponent.Mixin = {
updatePayload && UIManager.updateView(this._rootNodeID, this.viewConfig.uiViewClassName, updatePayload), updatePayload && UIManager.updateView(this._rootNodeID, this.viewConfig.uiViewClassName, updatePayload),
this.updateChildren(nextElement.props.children, transaction, context); this.updateChildren(nextElement.props.children, transaction, context);
}, },
getName: function() {
return this.constructor.displayName || this.constructor.name || "Unknown";
},
getHostNode: function() { getHostNode: function() {
return this._rootNodeID; return this._rootNodeID;
}, },