Permalink
Browse files

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
  • Loading branch information...
1 parent f20d5ed commit ecea0cef74f9adf8b3497484bd2811f74aad4d46 @lebronJ lebronJ committed with Facebook Github Bot 7 Jul 27, 2016
Showing with 223 additions and 0 deletions.
  1. +223 −0 Libraries/Inspector/NetworkOverlay.js
@@ -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;

1 comment on commit ecea0ce

@mikefowler
Contributor

🎆

Please sign in to comment.