Add UI to show intercepted network information in the inspector

Summary:
This diff adds a UI to display network information flows in the inspector tool (Android and iOS both supported):
- uses a ListView to show network flows, always scrolling to the latest item when a new network request occurs.
- displays the network requests as soon as they were created.
- highlights the selection row and toggles events to log the detailed intercepted information. (Next diff will draw UIs for this).

Follow-up:
- Will add one detail view to show all valuable information about a network request, after user clicks on one item in the ListView.
- Add more tabs in the detail view, ideally looking like the chrome network panel.

Reviewed By: davidaurelio

Differential Revision: D3598900

fbshipit-source-id: 5ec9ade6d13e3a9110db105fafbc7ba52adc515d
This commit is contained in:
Siqi Liu 2016-07-27 12:17:07 -07:00 committed by Facebook Github Bot 7
parent f20d5ed67f
commit ecea0cef74
1 changed files with 223 additions and 0 deletions

View File

@ -11,11 +11,18 @@
*/
'use strict';
const ListView = require('ListView');
const React = require('React');
const RecyclerViewBackedScrollView = require('RecyclerViewBackedScrollView');
const StyleSheet = require('StyleSheet');
const Text = require('Text');
const TouchableHighlight = require('TouchableHighlight');
const View = require('View');
const XHRInterceptor = require('XHRInterceptor');
const LISTVIEW_CELL_HEIGHT = 15;
const SEPARATOR_THICKNESS = 2;
type NetworkRequestInfo = {
url?: string,
method?: string,
@ -36,10 +43,38 @@ type NetworkRequestInfo = {
*/
class NetworkOverlay extends React.Component {
_requests: Array<NetworkRequestInfo>;
_listViewDataSource: ListView.DataSource;
_listView: ?ListView;
_listViewHighlighted: bool;
_listViewHeight: ?number;
_listViewOnLayout: (event: Event) => void;
_captureRequestList: (listRef: ?ListView) => void;
_renderRow: (
rowData: NetworkRequestInfo,
sectionID: number,
rowID: number,
highlightRow: (sectionID: number, rowID: number) => void,
) => ReactElement<any>;
_renderScrollComponent: (props: Object) => ReactElement<any>;
state: {
dataSource: ListView.DataSource,
};
constructor(props: Object) {
super(props);
this._requests = [];
this._listViewDataSource =
new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: this._listViewDataSource.cloneWithRows([]),
};
this._listViewHighlighted = false;
this._listViewHeight = null;
this._captureRequestList = this._captureRequestList.bind(this);
this._listViewOnLayout = this._listViewOnLayout.bind(this);
this._renderRow = this._renderRow.bind(this);
this._renderScrollComponent = this._renderScrollComponent.bind(this);
}
_enableInterception(): void {
@ -53,6 +88,10 @@ class NetworkOverlay extends React.Component {
xhr._index = this._requests.length;
const _xhr: NetworkRequestInfo = {'method': method, 'url': url};
this._requests = this._requests.concat(_xhr);
this.setState(
{dataSource: this._listViewDataSource.cloneWithRows(this._requests)},
this._scrollToBottom(),
);
}.bind(this));
XHRInterceptor.setRequestHeaderCallback(function(header, value, xhr) {
@ -103,9 +142,121 @@ class NetworkOverlay extends React.Component {
XHRInterceptor.disableInterception();
}
_renderRow(
rowData: NetworkRequestInfo,
sectionID: number,
rowID: number,
highlightRow: (sectionID: number, rowID: number) => void,
): ReactElement<any> {
let urlCellViewStyle = styles.urlEvenCellView;
let methodCellViewStyle = styles.methodEvenCellView;
if (rowID % 2 === 1) {
urlCellViewStyle = styles.urlOddCellView;
methodCellViewStyle = styles.methodOddCellView;
}
return (
<TouchableHighlight onPress={() => {
this._pressRow(rowID);
highlightRow(sectionID, rowID);
}}>
<View>
<View style={styles.tableRow}>
<View style={urlCellViewStyle}>
<Text style={styles.cellText} numberOfLines={1}>
{rowData.url}
</Text>
</View>
<View style={methodCellViewStyle}>
<Text style={styles.cellText} numberOfLines={1}>
{rowData.method}
</Text>
</View>
</View>
</View>
</TouchableHighlight>
);
}
_renderSeperator(
sectionID: number,
rowID: number,
adjacentRowHighlighted: bool): ReactElement<any> {
return (
<View
key={`${sectionID}-${rowID}`}
style={{
height: adjacentRowHighlighted ? SEPARATOR_THICKNESS : 0,
backgroundColor: adjacentRowHighlighted ? '#3B5998' : '#CCCCCC',
}}
/>
);
}
_scrollToBottom(): void {
if (!!this._listView && !!this._listViewHeight) {
const scrollResponder = this._listView.getScrollResponder();
if (scrollResponder) {
const scrollY = Math.max(
this._requests.length * LISTVIEW_CELL_HEIGHT +
(this._listViewHighlighted ? 2 * SEPARATOR_THICKNESS : 0) -
this._listViewHeight,
0,
);
scrollResponder.scrollResponderScrollTo({
x: 0,
y: scrollY,
animated: true
});
}
}
}
_captureRequestList(listRef: ?ListView): void {
this._listView = listRef;
}
_listViewOnLayout(event: any): void {
const {height} = event.nativeEvent.layout;
this._listViewHeight = height;
}
_renderScrollComponent(props: Object): ReactElement<any> {
return (
<RecyclerViewBackedScrollView {...props} />
);
}
/**
* TODO: When item is pressed, should pop up another view to show details.
*/
_pressRow(rowID: number): void {
this._listViewHighlighted = true;
}
render() {
return (
<View style={styles.container}>
{this._requests.length > 0 &&
<View>
<View style={styles.tableRow}>
<View style={styles.urlTitleCellView}>
<Text style={styles.cellText} numberOfLines={1}>URL</Text>
</View>
<View style={styles.methodTitleCellView}>
<Text style={styles.cellText} numberOfLines={1}>Method</Text>
</View>
</View>
</View>}
<ListView
style={styles.listView}
ref={this._captureRequestList}
dataSource={this.state.dataSource}
renderRow={this._renderRow}
renderScrollComponent={this._renderScrollComponent}
enableEmptySections={true}
renderSeparator={this._renderSeperator}
onLayout={this._listViewOnLayout}
/>
</View>
);
}
@ -119,6 +270,78 @@ const styles = StyleSheet.create({
paddingLeft: 5,
paddingRight: 5,
},
listView: {
flex: 1,
},
tableRow: {
flexDirection: 'row',
flex: 1,
},
cellText: {
color: 'white',
fontSize: 12,
},
methodTitleCellView: {
height: 18,
borderColor: '#DCD7CD',
borderTopWidth: 1,
borderBottomWidth: 1,
borderRightWidth: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#444',
flex: 1,
},
urlTitleCellView: {
height: 18,
borderColor: '#DCD7CD',
borderTopWidth: 1,
borderBottomWidth: 1,
borderLeftWidth: 1,
borderRightWidth: 1,
justifyContent: 'center',
backgroundColor: '#444',
flex: 5,
paddingLeft: 3,
},
methodOddCellView: {
height: 15,
borderColor: '#DCD7CD',
borderRightWidth: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#000',
flex: 1,
},
urlOddCellView: {
height: 15,
borderColor: '#DCD7CD',
borderLeftWidth: 1,
borderRightWidth: 1,
justifyContent: 'center',
backgroundColor: '#000',
flex: 5,
paddingLeft: 3,
},
methodEvenCellView: {
height: 15,
borderColor: '#DCD7CD',
borderRightWidth: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#888',
flex: 1,
},
urlEvenCellView: {
height: 15,
borderColor: '#DCD7CD',
borderLeftWidth: 1,
borderRightWidth: 1,
justifyContent: 'center',
backgroundColor: '#888',
flex: 5,
paddingLeft: 3,
},
});
module.exports = NetworkOverlay;