/* * Copyright (C) 2011 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @constructor * @extends {WebInspector.Object} */ WebInspector.NetworkManager = function() { WebInspector.Object.call(this); this._dispatcher = new WebInspector.NetworkDispatcher(this); if (WebInspector.settings.cacheDisabled.get()) NetworkAgent.setCacheDisabled(true); NetworkAgent.enable(); WebInspector.settings.cacheDisabled.addChangeListener(this._cacheDisabledSettingChanged, this); } WebInspector.NetworkManager.EventTypes = { RequestStarted: "RequestStarted", RequestUpdated: "RequestUpdated", RequestFinished: "RequestFinished", RequestUpdateDropped: "RequestUpdateDropped" } WebInspector.NetworkManager._MIMETypes = { "text/html": {"document": true}, "text/xml": {"document": true}, "text/plain": {"document": true}, "application/xhtml+xml": {"document": true}, "text/css": {"stylesheet": true}, "text/xsl": {"stylesheet": true}, "image/jpg": {"image": true}, "image/jpeg": {"image": true}, "image/pjpeg": {"image": true}, "image/png": {"image": true}, "image/gif": {"image": true}, "image/bmp": {"image": true}, "image/svg+xml": {"image": true, "font": true, "document": true}, "image/vnd.microsoft.icon": {"image": true}, "image/webp": {"image": true}, "image/x-icon": {"image": true}, "image/x-xbitmap": {"image": true}, "font/ttf": {"font": true}, "font/otf": {"font": true}, "font/woff": {"font": true}, "font/woff2": {"font": true}, "font/truetype": {"font": true}, "font/opentype": {"font": true}, "application/octet-stream": {"font": true, "image": true}, "application/font-woff": {"font": true}, "application/x-font-woff": {"font": true}, "application/x-font-type1": {"font": true}, "application/x-font-ttf": {"font": true}, "application/x-truetype-font": {"font": true}, "text/javascript": {"script": true}, "text/ecmascript": {"script": true}, "application/javascript": {"script": true}, "application/ecmascript": {"script": true}, "application/x-javascript": {"script": true}, "application/json": {"script": true}, "text/javascript1.1": {"script": true}, "text/javascript1.2": {"script": true}, "text/javascript1.3": {"script": true}, "text/jscript": {"script": true}, "text/livescript": {"script": true}, } WebInspector.NetworkManager.prototype = { /** * @param {string} url * @return {WebInspector.NetworkRequest} */ inflightRequestForURL: function(url) { return this._dispatcher._inflightRequestsByURL[url]; }, /** * @param {WebInspector.Event} event */ _cacheDisabledSettingChanged: function(event) { var enabled = /** @type {boolean} */ (event.data); NetworkAgent.setCacheDisabled(enabled); }, __proto__: WebInspector.Object.prototype } /** * @constructor * @implements {NetworkAgent.Dispatcher} */ WebInspector.NetworkDispatcher = function(manager) { this._manager = manager; this._inflightRequestsById = {}; this._inflightRequestsByURL = {}; InspectorBackend.registerNetworkDispatcher(this); } WebInspector.NetworkDispatcher.prototype = { /** * @param {NetworkAgent.Headers} headersMap * @return {!Array.} */ _headersMapToHeadersArray: function(headersMap) { var result = []; for (var name in headersMap) { var values = headersMap[name].split("\n"); for (var i = 0; i < values.length; ++i) result.push({name: name, value: values[i]}); } return result; }, /** * @param {WebInspector.NetworkRequest} networkRequest * @param {NetworkAgent.Request} request */ _updateNetworkRequestWithRequest: function(networkRequest, request) { networkRequest.requestMethod = request.method; networkRequest.requestHeaders = this._headersMapToHeadersArray(request.headers); networkRequest.requestFormData = request.postData; }, /** * @param {WebInspector.NetworkRequest} networkRequest * @param {NetworkAgent.Response=} response */ _updateNetworkRequestWithResponse: function(networkRequest, response) { if (!response) return; if (response.url && networkRequest.url !== response.url) networkRequest.url = response.url; networkRequest.mimeType = response.mimeType; networkRequest.statusCode = response.status; networkRequest.statusText = response.statusText; networkRequest.responseHeaders = this._headersMapToHeadersArray(response.headers); if (response.headersText) networkRequest.responseHeadersText = response.headersText; if (response.requestHeaders) networkRequest.requestHeaders = this._headersMapToHeadersArray(response.requestHeaders); if (response.requestHeadersText) networkRequest.requestHeadersText = response.requestHeadersText; networkRequest.connectionReused = response.connectionReused; networkRequest.connectionId = response.connectionId; if (response.fromDiskCache) networkRequest.cached = true; else networkRequest.timing = response.timing; if (!this._mimeTypeIsConsistentWithType(networkRequest)) { WebInspector.console.addMessage(WebInspector.ConsoleMessage.create(WebInspector.ConsoleMessage.MessageSource.Network, WebInspector.ConsoleMessage.MessageLevel.Log, WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s: \"%s\".", networkRequest.type.title(), networkRequest.mimeType, networkRequest.url), WebInspector.ConsoleMessage.MessageType.Log, "", 0, 0, 1, [], null, networkRequest.requestId)); } }, /** * @param {WebInspector.NetworkRequest} networkRequest * @return {boolean} */ _mimeTypeIsConsistentWithType: function(networkRequest) { // If status is an error, content is likely to be of an inconsistent type, // as it's going to be an error message. We do not want to emit a warning // for this, though, as this will already be reported as resource loading failure. // Also, if a URL like http://localhost/wiki/load.php?debug=true&lang=en produces text/css and gets reloaded, // it is 304 Not Modified and its guessed mime-type is text/php, which is wrong. // Don't check for mime-types in 304-resources. if (networkRequest.hasErrorStatusCode() || networkRequest.statusCode === 304 || networkRequest.statusCode === 204) return true; if (typeof networkRequest.type === "undefined" || networkRequest.type === WebInspector.resourceTypes.Other || networkRequest.type === WebInspector.resourceTypes.XHR || networkRequest.type === WebInspector.resourceTypes.WebSocket) return true; if (!networkRequest.mimeType) return true; // Might be not known for cached resources with null responses. if (networkRequest.mimeType in WebInspector.NetworkManager._MIMETypes) return networkRequest.type.name() in WebInspector.NetworkManager._MIMETypes[networkRequest.mimeType]; return false; }, /** * @param {NetworkAgent.Response} response * @return {boolean} */ _isNull: function(response) { if (!response) return true; return !response.status && !response.mimeType && (!response.headers || !Object.keys(response.headers).length); }, /** * @param {NetworkAgent.RequestId} requestId * @param {PageAgent.FrameId} frameId * @param {NetworkAgent.LoaderId} loaderId * @param {string} documentURL * @param {NetworkAgent.Request} request * @param {NetworkAgent.Timestamp} time * @param {NetworkAgent.Initiator} initiator * @param {NetworkAgent.Response=} redirectResponse */ requestWillBeSent: function(requestId, frameId, loaderId, documentURL, request, time, initiator, redirectResponse) { var networkRequest = this._inflightRequestsById[requestId]; if (networkRequest) { // FIXME: move this check to the backend. if (!redirectResponse) return; this.responseReceived(requestId, frameId, loaderId, time, PageAgent.ResourceType.Other, redirectResponse); networkRequest = this._appendRedirect(requestId, time, request.url); } else networkRequest = this._createNetworkRequest(requestId, frameId, loaderId, request.url, documentURL, initiator); networkRequest.hasNetworkData = true; this._updateNetworkRequestWithRequest(networkRequest, request); networkRequest.startTime = time; this._startNetworkRequest(networkRequest); }, /** * @param {NetworkAgent.RequestId} requestId */ requestServedFromCache: function(requestId) { var networkRequest = this._inflightRequestsById[requestId]; if (!networkRequest) return; networkRequest.cached = true; }, /** * @param {NetworkAgent.RequestId} requestId * @param {PageAgent.FrameId} frameId * @param {NetworkAgent.LoaderId} loaderId * @param {NetworkAgent.Timestamp} time * @param {PageAgent.ResourceType} resourceType * @param {NetworkAgent.Response} response */ responseReceived: function(requestId, frameId, loaderId, time, resourceType, response) { // FIXME: move this check to the backend. if (this._isNull(response)) return; var networkRequest = this._inflightRequestsById[requestId]; if (!networkRequest) { // We missed the requestWillBeSent. var eventData = {}; eventData.url = response.url; eventData.frameId = frameId; eventData.loaderId = loaderId; eventData.resourceType = resourceType; eventData.mimeType = response.mimeType; this._manager.dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.RequestUpdateDropped, eventData); return; } networkRequest.responseReceivedTime = time; networkRequest.type = WebInspector.resourceTypes[resourceType]; this._updateNetworkRequestWithResponse(networkRequest, response); this._updateNetworkRequest(networkRequest); }, /** * @param {NetworkAgent.RequestId} requestId * @param {NetworkAgent.Timestamp} time * @param {number} dataLength * @param {number} encodedDataLength */ dataReceived: function(requestId, time, dataLength, encodedDataLength) { var networkRequest = this._inflightRequestsById[requestId]; if (!networkRequest) return; networkRequest.resourceSize += dataLength; if (encodedDataLength != -1) networkRequest.increaseTransferSize(encodedDataLength); networkRequest.endTime = time; this._updateNetworkRequest(networkRequest); }, /** * @param {NetworkAgent.RequestId} requestId * @param {NetworkAgent.Timestamp} finishTime */ loadingFinished: function(requestId, finishTime) { var networkRequest = this._inflightRequestsById[requestId]; if (!networkRequest) return; this._finishNetworkRequest(networkRequest, finishTime); }, /** * @param {NetworkAgent.RequestId} requestId * @param {NetworkAgent.Timestamp} time * @param {string} localizedDescription * @param {boolean=} canceled */ loadingFailed: function(requestId, time, localizedDescription, canceled) { var networkRequest = this._inflightRequestsById[requestId]; if (!networkRequest) return; networkRequest.failed = true; networkRequest.canceled = canceled; networkRequest.localizedFailDescription = localizedDescription; this._finishNetworkRequest(networkRequest, time); }, /** * @param {NetworkAgent.RequestId} requestId * @param {string} requestURL */ webSocketCreated: function(requestId, requestURL) { var networkRequest = new WebInspector.NetworkRequest(requestId, requestURL, "", "", ""); networkRequest.type = WebInspector.resourceTypes.WebSocket; this._startNetworkRequest(networkRequest); }, /** * @param {NetworkAgent.RequestId} requestId * @param {NetworkAgent.Timestamp} time * @param {NetworkAgent.WebSocketRequest} request */ webSocketWillSendHandshakeRequest: function(requestId, time, request) { var networkRequest = this._inflightRequestsById[requestId]; if (!networkRequest) return; networkRequest.requestMethod = "GET"; networkRequest.requestHeaders = this._headersMapToHeadersArray(request.headers); networkRequest.startTime = time; this._updateNetworkRequest(networkRequest); }, /** * @param {NetworkAgent.RequestId} requestId * @param {NetworkAgent.Timestamp} time * @param {NetworkAgent.WebSocketResponse} response */ webSocketHandshakeResponseReceived: function(requestId, time, response) { var networkRequest = this._inflightRequestsById[requestId]; if (!networkRequest) return; networkRequest.statusCode = response.status; networkRequest.statusText = response.statusText; networkRequest.responseHeaders = this._headersMapToHeadersArray(response.headers); networkRequest.responseReceivedTime = time; this._updateNetworkRequest(networkRequest); }, /** * @param {NetworkAgent.RequestId} requestId * @param {NetworkAgent.Timestamp} time * @param {NetworkAgent.WebSocketFrame} response */ webSocketFrameReceived: function(requestId, time, response) { var networkRequest = this._inflightRequestsById[requestId]; if (!networkRequest) return; networkRequest.addFrame(response, time); networkRequest.responseReceivedTime = time; this._updateNetworkRequest(networkRequest); }, /** * @param {NetworkAgent.RequestId} requestId * @param {NetworkAgent.Timestamp} time * @param {NetworkAgent.WebSocketFrame} response */ webSocketFrameSent: function(requestId, time, response) { var networkRequest = this._inflightRequestsById[requestId]; if (!networkRequest) return; networkRequest.addFrame(response, time, true); networkRequest.responseReceivedTime = time; this._updateNetworkRequest(networkRequest); }, /** * @param {NetworkAgent.RequestId} requestId * @param {NetworkAgent.Timestamp} time * @param {string} errorMessage */ webSocketFrameError: function(requestId, time, errorMessage) { var networkRequest = this._inflightRequestsById[requestId]; if (!networkRequest) return; networkRequest.addFrameError(errorMessage, time); networkRequest.responseReceivedTime = time; this._updateNetworkRequest(networkRequest); }, /** * @param {NetworkAgent.RequestId} requestId * @param {NetworkAgent.Timestamp} time */ webSocketClosed: function(requestId, time) { var networkRequest = this._inflightRequestsById[requestId]; if (!networkRequest) return; this._finishNetworkRequest(networkRequest, time); }, /** * @param {NetworkAgent.RequestId} requestId * @param {NetworkAgent.Timestamp} time * @param {string} redirectURL * @return {WebInspector.NetworkRequest} */ _appendRedirect: function(requestId, time, redirectURL) { var originalNetworkRequest = this._inflightRequestsById[requestId]; var previousRedirects = originalNetworkRequest.redirects || []; originalNetworkRequest.requestId = "redirected:" + requestId + "." + previousRedirects.length; delete originalNetworkRequest.redirects; if (previousRedirects.length > 0) originalNetworkRequest.redirectSource = previousRedirects[previousRedirects.length - 1]; this._finishNetworkRequest(originalNetworkRequest, time); var newNetworkRequest = this._createNetworkRequest(requestId, originalNetworkRequest.frameId, originalNetworkRequest.loaderId, redirectURL, originalNetworkRequest.documentURL, originalNetworkRequest.initiator); newNetworkRequest.redirects = previousRedirects.concat(originalNetworkRequest); return newNetworkRequest; }, /** * @param {WebInspector.NetworkRequest} networkRequest */ _startNetworkRequest: function(networkRequest) { this._inflightRequestsById[networkRequest.requestId] = networkRequest; this._inflightRequestsByURL[networkRequest.url] = networkRequest; this._dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.RequestStarted, networkRequest); }, /** * @param {WebInspector.NetworkRequest} networkRequest */ _updateNetworkRequest: function(networkRequest) { this._dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.RequestUpdated, networkRequest); }, /** * @param {WebInspector.NetworkRequest} networkRequest * @param {NetworkAgent.Timestamp} finishTime */ _finishNetworkRequest: function(networkRequest, finishTime) { networkRequest.endTime = finishTime; networkRequest.finished = true; this._dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.RequestFinished, networkRequest); delete this._inflightRequestsById[networkRequest.requestId]; delete this._inflightRequestsByURL[networkRequest.url]; }, /** * @param {string} eventType * @param {WebInspector.NetworkRequest} networkRequest */ _dispatchEventToListeners: function(eventType, networkRequest) { this._manager.dispatchEventToListeners(eventType, networkRequest); }, /** * @param {NetworkAgent.RequestId} requestId * @param {string} frameId * @param {NetworkAgent.LoaderId} loaderId * @param {string} url * @param {string} documentURL * @param {NetworkAgent.Initiator} initiator */ _createNetworkRequest: function(requestId, frameId, loaderId, url, documentURL, initiator) { var networkRequest = new WebInspector.NetworkRequest(requestId, url, documentURL, frameId, loaderId); networkRequest.initiator = initiator; return networkRequest; } } /** * @type {?WebInspector.NetworkManager} */ WebInspector.networkManager = null;