From 1e8b83d2e663b7934cff5fe8f00ad9870229be56 Mon Sep 17 00:00:00 2001 From: Siqi Liu Date: Tue, 2 Aug 2016 08:23:53 -0700 Subject: [PATCH] Add unique ids to the intercepted XHR objects to make the tracking correctly across inspector restarts. Summary: In previous `XHRInterceptor`, it sometimes crashes when restarting the network inspector because the id of the XHR objects are not unique all time. Fix this in diff by adding a global id "generator" for all intercepted xhr objects in order to make it safe across inspector restarts. Reviewed By: davidaurelio Differential Revision: D3641624 fbshipit-source-id: f9a1589f278023243aa182d3da93ce69c985587c --- Libraries/Inspector/NetworkOverlay.js | 66 +++++++++++++++++++-------- Libraries/Utilities/XHRInterceptor.js | 47 ++++++++++++------- 2 files changed, 78 insertions(+), 35 deletions(-) diff --git a/Libraries/Inspector/NetworkOverlay.js b/Libraries/Inspector/NetworkOverlay.js index 4cd154333..ca3c781af 100644 --- a/Libraries/Inspector/NetworkOverlay.js +++ b/Libraries/Inspector/NetworkOverlay.js @@ -24,6 +24,9 @@ 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 = { url?: string, method?: string, @@ -61,6 +64,8 @@ class NetworkOverlay extends React.Component { ) => ReactElement; _renderScrollComponent: (props: Object) => ReactElement; _closeButtonClicked: () => void; + // Map of `xhr._index` -> `index in `_requests``. + _xhrIdMap: {[key: number]: number}; state: { dataSource: ListView.DataSource, @@ -87,6 +92,7 @@ class NetworkOverlay extends React.Component { this._renderRow = this._renderRow.bind(this); this._renderScrollComponent = this._renderScrollComponent.bind(this); this._closeButtonClicked = this._closeButtonClicked.bind(this); + this._xhrIdMap = {}; } _enableInterception(): void { @@ -95,13 +101,20 @@ class NetworkOverlay extends React.Component { } // Show the network request item in listView as soon as it was opened. XHRInterceptor.setOpenCallback(function(method, url, xhr) { - // Add one private `_index` property to identify the xhr object, + // Generate a global id for each intercepted xhr object, add this id + // to the xhr object as a private `_index` property to identify it, // so that we can distinguish different xhr objects in callbacks. - xhr._index = this._requests.length; - const _xhr: NetworkRequestInfo = {'method': method, 'url': url}; + xhr._index = nextXHRId++; + const xhrIndex = this._requests.length; + this._xhrIdMap[xhr._index] = xhrIndex; + + const _xhr: NetworkRequestInfo = { + 'method': method, + 'url': url + }; this._requests.push(_xhr); this._detailViewItems.push([]); - this._genDetailViewItem(xhr._index); + this._genDetailViewItem(xhrIndex); this.setState( {dataSource: this._listViewDataSource.cloneWithRows(this._requests)}, this._scrollToBottom(), @@ -109,35 +122,38 @@ class NetworkOverlay extends React.Component { }.bind(this)); XHRInterceptor.setRequestHeaderCallback(function(header, value, xhr) { - if (xhr._index === undefined) { + const xhrIndex = this._getRequestIndexByXHRID(xhr._index); + if (xhrIndex === -1) { return; } - const networkInfo = this._requests[xhr._index]; + const networkInfo = this._requests[xhrIndex]; if (!networkInfo.requestHeaders) { networkInfo.requestHeaders = {}; } networkInfo.requestHeaders[header] = value; - this._genDetailViewItem(xhr._index); + this._genDetailViewItem(xhrIndex); }.bind(this)); XHRInterceptor.setSendCallback(function(data, xhr) { - if (xhr._index === undefined) { + const xhrIndex = this._getRequestIndexByXHRID(xhr._index); + if (xhrIndex === -1) { return; } - this._requests[xhr._index].dataSent = data; - this._genDetailViewItem(xhr._index); + this._requests[xhrIndex].dataSent = data; + this._genDetailViewItem(xhrIndex); }.bind(this)); XHRInterceptor.setHeaderReceivedCallback( function(type, size, responseHeaders, xhr) { - if (xhr._index === undefined) { + const xhrIndex = this._getRequestIndexByXHRID(xhr._index); + if (xhrIndex === -1) { return; } - const networkInfo = this._requests[xhr._index]; + const networkInfo = this._requests[xhrIndex]; networkInfo.responseContentType = type; networkInfo.responseSize = size; networkInfo.responseHeaders = responseHeaders; - this._genDetailViewItem(xhr._index); + this._genDetailViewItem(xhrIndex); }.bind(this) ); @@ -150,16 +166,17 @@ class NetworkOverlay extends React.Component { responseType, xhr, ) { - if (xhr._index === undefined) { + const xhrIndex = this._getRequestIndexByXHRID(xhr._index); + if (xhrIndex === -1) { return; } - const networkInfo = this._requests[xhr._index]; + const networkInfo = this._requests[xhrIndex]; networkInfo.status = status; networkInfo.timeout = timeout; networkInfo.response = response; networkInfo.responseURL = responseURL; networkInfo.responseType = responseType; - this._genDetailViewItem(xhr._index); + this._genDetailViewItem(xhrIndex); }.bind(this) ); @@ -296,11 +313,24 @@ class NetworkOverlay extends React.Component { return JSON.stringify(value); } if (typeof value === 'string' && value.length > 500) { - return String(value).substr(0, 500).concat('\n***TRUNCATED TO 500 CHARACTERS***'); + return String(value).substr(0, 500).concat( + '\n***TRUNCATED TO 500 CHARACTERS***'); } return value; } + _getRequestIndexByXHRID(index: number): number { + if (index === undefined) { + return -1; + } + const xhrIndex = this._xhrIdMap[index]; + if (xhrIndex === undefined) { + return -1; + } else { + return xhrIndex; + } + } + /** * Generate a list of views containing network request information for * a XHR object, to be shown in the detail scrollview. This function @@ -324,7 +354,7 @@ class NetworkOverlay extends React.Component { ); } // Re-render if this network request is showing in the detail view. - if (this.state.detailRowID != null && this.state.detailRowID == index) { + if (this.state.detailRowID != null && Number(this.state.detailRowID) === index) { this.setState({newDetailInfo: true}); } } diff --git a/Libraries/Utilities/XHRInterceptor.js b/Libraries/Utilities/XHRInterceptor.js index 9aa7d402b..0c037c8e0 100644 --- a/Libraries/Utilities/XHRInterceptor.js +++ b/Libraries/Utilities/XHRInterceptor.js @@ -77,23 +77,32 @@ const XHRInterceptor = { // Override `open` method for all XHR requests to intercept the request // method and url, then pass them through the `openCallback`. XMLHttpRequest.prototype.open = function(method, url) { - openCallback(method, url, this); + if (openCallback) { + openCallback(method, url, this); + } originalXHROpen.apply(this, arguments); }; // Override `setRequestHeader` method for all XHR requests to intercept // the request headers, then pass them through the `requestHeaderCallback`. XMLHttpRequest.prototype.setRequestHeader = function(header, value) { - requestHeaderCallback(header, value, this); + if (requestHeaderCallback) { + requestHeaderCallback(header, value, this); + } originalXHRSetRequestHeader.apply(this, arguments); }; // Override `send` method of all XHR requests to intercept the data sent, // register listeners to intercept the response, and invoke the callbacks. XMLHttpRequest.prototype.send = function(data) { - sendCallback(data, this); + if (sendCallback) { + sendCallback(data, this); + } if (this.addEventListener) { this.addEventListener('readystatechange', () => { + if (!isInterceptorEnabled) { + return; + } if (this.readyState === this.HEADERS_RECEIVED) { const contentTypeString = this.getResponseHeader('Content-Type'); const contentLengthString = @@ -105,22 +114,26 @@ const XHRInterceptor = { if (contentLengthString) { responseSize = parseInt(contentLengthString, 10); } - headerReceivedCallback( - responseContentType, - responseSize, - this.getAllResponseHeaders(), - this, - ); + if (headerReceivedCallback) { + headerReceivedCallback( + responseContentType, + responseSize, + this.getAllResponseHeaders(), + this, + ); + } } if (this.readyState === this.DONE) { - responseCallback( - this.status, - this.timeout, - this.response, - this.responseURL, - this.responseType, - this, - ); + if (responseCallback) { + responseCallback( + this.status, + this.timeout, + this.response, + this.responseURL, + this.responseType, + this, + ); + } } }, false); }