diff --git a/Libraries/Inspector/NetworkOverlay.js b/Libraries/Inspector/NetworkOverlay.js index abeda34abcdeca..549c523ca829fb 100644 --- a/Libraries/Inspector/NetworkOverlay.js +++ b/Libraries/Inspector/NetworkOverlay.js @@ -10,7 +10,7 @@ 'use strict'; -const ListView = require('ListView'); +const FlatList = require('FlatList'); const React = require('React'); const ScrollView = require('ScrollView'); const StyleSheet = require('StyleSheet'); @@ -21,12 +21,12 @@ const WebSocketInterceptor = require('WebSocketInterceptor'); const XHRInterceptor = require('XHRInterceptor'); const LISTVIEW_CELL_HEIGHT = 15; -const SEPARATOR_THICKNESS = 2; // Global id for the intercepted XMLHttpRequest objects. let nextXHRId = 0; type NetworkRequestInfo = { + id: number, type?: string, url?: string, method?: string, @@ -52,54 +52,38 @@ type NetworkRequestInfo = { class NetworkOverlay extends React.Component< Object, { - dataSource: ListView.DataSource, - newDetailInfo: boolean, - detailRowID: ?number, + detailRowId: ?number, + requests: Array, }, > { - _requests: Array; - _listViewDataSource: ListView.DataSource; - _listView: ?ListView; - _listViewHighlighted: boolean; - _listViewHeight: number; - _scrollView: ?ScrollView; - _detailViewItems: Array>>; - _listViewOnLayout: (event: Event) => void; - _captureRequestListView: (listRef: ?ListView) => void; + _requestsListView: ?FlatList; + _detailScrollView: ?ScrollView; + _captureRequestsListView: (listRef: ?FlatList) => void; _captureDetailScrollView: (scrollRef: ?ScrollView) => void; - _renderRow: ( - rowData: NetworkRequestInfo, - sectionID: number, - rowID: number, - highlightRow: (sectionID: number, rowID: number) => void, - ) => React.Element; + _renderItem: ({ + item: NetworkRequestInfo, + index: number, + }) => ?React.Element; + _renderItemDetail: (index: number) => React.Element; _closeButtonClicked: () => void; - // Map of `socketId` -> `index in `_requests``. + // Map of `socketId` -> `index in `this.state.requests`. _socketIdMap: Object; - // Map of `xhr._index` -> `index in `_requests``. + // Map of `xhr._index` -> `index in `this.state.requests`. _xhrIdMap: {[key: number]: number}; constructor(props: Object) { super(props); - this._requests = []; - this._detailViewItems = []; - this._listViewDataSource = new ListView.DataSource({ - rowHasChanged: (r1, r2) => r1 !== r2, - }); - this.state = { - dataSource: this._listViewDataSource.cloneWithRows([]), - newDetailInfo: false, - detailRowID: null, - }; - this._listViewHighlighted = false; - this._listViewHeight = 0; - this._captureRequestListView = this._captureRequestListView.bind(this); + this._captureRequestsListView = this._captureRequestsListView.bind(this); this._captureDetailScrollView = this._captureDetailScrollView.bind(this); - this._listViewOnLayout = this._listViewOnLayout.bind(this); - this._renderRow = this._renderRow.bind(this); + this._renderItem = this._renderItem.bind(this); + this._closeButtonClicked = this._closeButtonClicked.bind(this); this._socketIdMap = {}; this._xhrIdMap = {}; + this.state = { + detailRowId: null, + requests: [], + }; } _enableXHRInterception(): void { @@ -112,20 +96,20 @@ class NetworkOverlay extends React.Component< // to the xhr object as a private `_index` property to identify it, // so that we can distinguish different xhr objects in callbacks. xhr._index = nextXHRId++; - const xhrIndex = this._requests.length; + const xhrIndex = this.state.requests.length; this._xhrIdMap[xhr._index] = xhrIndex; const _xhr: NetworkRequestInfo = { + id: xhrIndex, type: 'XMLHttpRequest', method: method, url: url, }; - this._requests.push(_xhr); - this._detailViewItems.push([]); - this._genDetailViewItem(xhrIndex); this.setState( - {dataSource: this._listViewDataSource.cloneWithRows(this._requests)}, - this._scrollToBottom(), + { + requests: this.state.requests.concat(_xhr), + }, + this._scrollRequestsToBottom, ); }); @@ -134,12 +118,15 @@ class NetworkOverlay extends React.Component< if (xhrIndex === -1) { return; } - const networkInfo = this._requests[xhrIndex]; - if (!networkInfo.requestHeaders) { - networkInfo.requestHeaders = {}; - } - networkInfo.requestHeaders[header] = value; - this._genDetailViewItem(xhrIndex); + + this.setState(({requests}) => { + const networkRequestInfo = requests[xhrIndex]; + if (!networkRequestInfo.requestHeaders) { + networkRequestInfo.requestHeaders = {}; + } + networkRequestInfo.requestHeaders[header] = value; + return {requests}; + }); }); XHRInterceptor.setSendCallback((data, xhr) => { @@ -147,8 +134,12 @@ class NetworkOverlay extends React.Component< if (xhrIndex === -1) { return; } - this._requests[xhrIndex].dataSent = data; - this._genDetailViewItem(xhrIndex); + + this.setState(({requests}) => { + const networkRequestInfo = requests[xhrIndex]; + networkRequestInfo.dataSent = data; + return {requests}; + }); }); XHRInterceptor.setHeaderReceivedCallback( @@ -157,11 +148,14 @@ class NetworkOverlay extends React.Component< if (xhrIndex === -1) { return; } - const networkInfo = this._requests[xhrIndex]; - networkInfo.responseContentType = type; - networkInfo.responseSize = size; - networkInfo.responseHeaders = responseHeaders; - this._genDetailViewItem(xhrIndex); + + this.setState(({requests}) => { + const networkRequestInfo = requests[xhrIndex]; + networkRequestInfo.responseContentType = type; + networkRequestInfo.responseSize = size; + networkRequestInfo.responseHeaders = responseHeaders; + return {requests}; + }); }, ); @@ -171,13 +165,17 @@ class NetworkOverlay extends React.Component< if (xhrIndex === -1) { return; } - const networkInfo = this._requests[xhrIndex]; - networkInfo.status = status; - networkInfo.timeout = timeout; - networkInfo.response = response; - networkInfo.responseURL = responseURL; - networkInfo.responseType = responseType; - this._genDetailViewItem(xhrIndex); + + this.setState(({requests}) => { + const networkRequestInfo = requests[xhrIndex]; + networkRequestInfo.status = status; + networkRequestInfo.timeout = timeout; + networkRequestInfo.response = response; + networkRequestInfo.responseURL = responseURL; + networkRequestInfo.responseType = responseType; + + return {requests}; + }); }, ); @@ -192,19 +190,19 @@ class NetworkOverlay extends React.Component< // Show the WebSocket request item in listView when 'connect' is called. WebSocketInterceptor.setConnectCallback( (url, protocols, options, socketId) => { - const socketIndex = this._requests.length; + const socketIndex = this.state.requests.length; this._socketIdMap[socketId] = socketIndex; const _webSocket: NetworkRequestInfo = { + id: socketIndex, type: 'WebSocket', url: url, protocols: protocols, }; - this._requests.push(_webSocket); - this._detailViewItems.push([]); - this._genDetailViewItem(socketIndex); this.setState( - {dataSource: this._listViewDataSource.cloneWithRows(this._requests)}, - this._scrollToBottom(), + { + requests: this.state.requests.concat(_webSocket), + }, + this._scrollRequestsToBottom, ); }, ); @@ -216,10 +214,13 @@ class NetworkOverlay extends React.Component< return; } if (statusCode !== null && closeReason !== null) { - this._requests[socketIndex].status = statusCode; - this._requests[socketIndex].closeReason = closeReason; + this.setState(({requests}) => { + const networkRequestInfo = requests[socketIndex]; + networkRequestInfo.status = statusCode; + networkRequestInfo.closeReason = closeReason; + return {requests}; + }); } - this._genDetailViewItem(socketIndex); }, ); @@ -228,12 +229,17 @@ class NetworkOverlay extends React.Component< if (socketIndex === undefined) { return; } - if (!this._requests[socketIndex].messages) { - this._requests[socketIndex].messages = ''; - } - this._requests[socketIndex].messages += - 'Sent: ' + JSON.stringify(data) + '\n'; - this._genDetailViewItem(socketIndex); + + this.setState(({requests}) => { + const networkRequestInfo = requests[socketIndex]; + + if (!networkRequestInfo.messages) { + networkRequestInfo.messages = ''; + } + networkRequestInfo.messages += 'Sent: ' + JSON.stringify(data) + '\n'; + + return {requests}; + }); }); WebSocketInterceptor.setOnMessageCallback((socketId, message) => { @@ -241,12 +247,18 @@ class NetworkOverlay extends React.Component< if (socketIndex === undefined) { return; } - if (!this._requests[socketIndex].messages) { - this._requests[socketIndex].messages = ''; - } - this._requests[socketIndex].messages += - 'Received: ' + JSON.stringify(message) + '\n'; - this._genDetailViewItem(socketIndex); + + this.setState(({requests}) => { + const networkRequestInfo = requests[socketIndex]; + + if (!networkRequestInfo.messages) { + networkRequestInfo.messages = ''; + } + networkRequestInfo.messages += + 'Received: ' + JSON.stringify(message) + '\n'; + + return {requests}; + }); }); WebSocketInterceptor.setOnCloseCallback((socketId, message) => { @@ -254,8 +266,13 @@ class NetworkOverlay extends React.Component< if (socketIndex === undefined) { return; } - this._requests[socketIndex].serverClose = message; - this._genDetailViewItem(socketIndex); + + this.setState(({requests}) => { + const networkRequestInfo = requests[socketIndex]; + networkRequestInfo.serverClose = message; + + return {requests}; + }); }); WebSocketInterceptor.setOnErrorCallback((socketId, message) => { @@ -263,8 +280,13 @@ class NetworkOverlay extends React.Component< if (socketIndex === undefined) { return; } - this._requests[socketIndex].serverError = message; - this._genDetailViewItem(socketIndex); + + this.setState(({requests}) => { + const networkRequestInfo = requests[socketIndex]; + networkRequestInfo.serverError = message; + + return {requests}; + }); }); // Fire above callbacks. @@ -281,34 +303,30 @@ class NetworkOverlay extends React.Component< WebSocketInterceptor.disableInterception(); } - _renderRow( - rowData: NetworkRequestInfo, - sectionID: number, - rowID: number, - highlightRow: (sectionID: number, rowID: number) => void, - ): React.Element { - let urlCellViewStyle = styles.urlEvenCellView; - let methodCellViewStyle = styles.methodEvenCellView; - if (rowID % 2 === 1) { - urlCellViewStyle = styles.urlOddCellView; - methodCellViewStyle = styles.methodOddCellView; - } + _renderItem({item, index}): ?React.Element { + const tableRowViewStyle = [ + styles.tableRow, + index % 2 === 1 ? styles.tableRowOdd : styles.tableRowEven, + index === this.state.detailRowId && styles.tableRowPressed, + ]; + const urlCellViewStyle = styles.urlCellView; + const methodCellViewStyle = styles.methodCellView; + return ( { - this._pressRow(rowID); - highlightRow(sectionID, rowID); + this._pressRow(index); }}> - + - {rowData.url} + {item.url} - {this._getTypeShortName(rowData.type)} + {this._getTypeShortName(item.type)} @@ -317,62 +335,63 @@ class NetworkOverlay extends React.Component< ); } - _renderSeperator( - sectionID: number, - rowID: number, - adjacentRowHighlighted: boolean, - ): React.Element { + _renderItemDetail(id) { + const requestItem = this.state.requests[id]; + const details = Object.keys(requestItem).map(key => { + if (key === 'id') { + return; + } + return ( + + + {key} + + + {this._getStringByValue(requestItem[key])} + + + ); + }); + return ( - + + + + v + + + + {details} + + ); } - _scrollToBottom(): void { - if (this._listView) { - 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, - }); - } + _scrollRequestsToBottom(): void { + if (this._requestsListView) { + this._requestsListView.scrollToEnd(); } } - _captureRequestListView(listRef: ?ListView): void { - this._listView = listRef; - } - - _listViewOnLayout(event: any): void { - const {height} = event.nativeEvent.layout; - this._listViewHeight = height; + _captureRequestsListView(listRef: ?FlatList): void { + this._requestsListView = listRef; } /** * Popup a scrollView to dynamically show detailed information of * the request, when pressing a row in the network flow listView. */ - _pressRow(rowID: number): void { - this._listViewHighlighted = true; - this.setState({detailRowID: rowID}, this._scrollToTop()); + _pressRow(rowId: number): void { + this.setState({detailRowId: rowId}, this._scrollDetailToTop()); } - _scrollToTop(): void { - if (this._scrollView) { - this._scrollView.scrollTo({ + _scrollDetailToTop(): void { + if (this._detailScrollView) { + this._detailScrollView.scrollTo({ y: 0, animated: false, }); @@ -380,11 +399,11 @@ class NetworkOverlay extends React.Component< } _captureDetailScrollView(scrollRef: ?ScrollView): void { - this._scrollView = scrollRef; + this._detailScrollView = scrollRef; } _closeButtonClicked() { - this.setState({detailRowID: null}); + this.setState({detailRowId: null}); } _getStringByValue(value: any): string { @@ -424,58 +443,18 @@ class NetworkOverlay extends React.Component< return ''; } - /** - * Generate a list of views containing network request information for - * a XHR object, to be shown in the detail scrollview. This function - * should be called every time there is a new update of the XHR object, - * in order to show network request/response information in real time. - */ - _genDetailViewItem(index: number): void { - this._detailViewItems[index] = []; - const detailViewItem = this._detailViewItems[index]; - const requestItem = this._requests[index]; - for (let key in requestItem) { - detailViewItem.push( - - - {key} - - - {this._getStringByValue(requestItem[key])} - - , - ); - } - // Re-render if this network request is showing in the detail view. - if ( - this.state.detailRowID != null && - Number(this.state.detailRowID) === index - ) { - this.setState({newDetailInfo: true}); - } + _keyExtractor(request: NetworkRequestInfo): string { + return String(request.id); } render() { + const {requests, detailRowId} = this.state; + return ( - {this.state.detailRowID != null && ( - - - v - - - )} - {this.state.detailRowID != null && ( - - {this._detailViewItems[this.state.detailRowID]} - - )} + {detailRowId != null && this._renderItemDetail(detailRowId)} - {this._requests.length > 0 && ( + {requests.length > 0 && ( @@ -490,15 +469,14 @@ class NetworkOverlay extends React.Component< )} - ); @@ -522,6 +500,16 @@ const styles = StyleSheet.create({ tableRow: { flexDirection: 'row', flex: 1, + height: LISTVIEW_CELL_HEIGHT, + }, + tableRowEven: { + backgroundColor: '#555', + }, + tableRowOdd: { + backgroundColor: '#000', + }, + tableRowPressed: { + backgroundColor: '#3B5998', }, cellText: { color: 'white', @@ -550,41 +538,20 @@ const styles = StyleSheet.create({ flex: 5, paddingLeft: 3, }, - methodOddCellView: { + methodCellView: { height: 15, borderColor: '#DCD7CD', borderRightWidth: 1, alignItems: 'center', justifyContent: 'center', - backgroundColor: '#000', flex: 1, }, - urlOddCellView: { + urlCellView: { 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, }, @@ -608,7 +575,7 @@ const styles = StyleSheet.create({ color: 'white', fontSize: 11, }, - clostButtonText: { + closeButtonText: { color: 'white', fontSize: 10, },