1/* 2 * Copyright (C) 2011 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31/** 32 * @constructor 33 * @extends {WebInspector.Object} 34 */ 35WebInspector.NetworkManager = function() 36{ 37 WebInspector.Object.call(this); 38 this._dispatcher = new WebInspector.NetworkDispatcher(this); 39 if (WebInspector.settings.cacheDisabled.get()) 40 NetworkAgent.setCacheDisabled(true); 41 42 NetworkAgent.enable(); 43 44 WebInspector.settings.cacheDisabled.addChangeListener(this._cacheDisabledSettingChanged, this); 45} 46 47WebInspector.NetworkManager.EventTypes = { 48 RequestStarted: "RequestStarted", 49 RequestUpdated: "RequestUpdated", 50 RequestFinished: "RequestFinished", 51 RequestUpdateDropped: "RequestUpdateDropped" 52} 53 54WebInspector.NetworkManager._MIMETypes = { 55 "text/html": {"document": true}, 56 "text/xml": {"document": true}, 57 "text/plain": {"document": true}, 58 "application/xhtml+xml": {"document": true}, 59 "text/css": {"stylesheet": true}, 60 "text/xsl": {"stylesheet": true}, 61 "image/jpg": {"image": true}, 62 "image/jpeg": {"image": true}, 63 "image/pjpeg": {"image": true}, 64 "image/png": {"image": true}, 65 "image/gif": {"image": true}, 66 "image/bmp": {"image": true}, 67 "image/svg+xml": {"image": true, "font": true, "document": true}, 68 "image/vnd.microsoft.icon": {"image": true}, 69 "image/webp": {"image": true}, 70 "image/x-icon": {"image": true}, 71 "image/x-xbitmap": {"image": true}, 72 "font/ttf": {"font": true}, 73 "font/otf": {"font": true}, 74 "font/woff": {"font": true}, 75 "font/woff2": {"font": true}, 76 "font/truetype": {"font": true}, 77 "font/opentype": {"font": true}, 78 "application/octet-stream": {"font": true, "image": true}, 79 "application/font-woff": {"font": true}, 80 "application/x-font-woff": {"font": true}, 81 "application/x-font-type1": {"font": true}, 82 "application/x-font-ttf": {"font": true}, 83 "application/x-truetype-font": {"font": true}, 84 "text/javascript": {"script": true}, 85 "text/ecmascript": {"script": true}, 86 "application/javascript": {"script": true}, 87 "application/ecmascript": {"script": true}, 88 "application/x-javascript": {"script": true}, 89 "application/json": {"script": true}, 90 "text/javascript1.1": {"script": true}, 91 "text/javascript1.2": {"script": true}, 92 "text/javascript1.3": {"script": true}, 93 "text/jscript": {"script": true}, 94 "text/livescript": {"script": true}, 95} 96 97WebInspector.NetworkManager.prototype = { 98 /** 99 * @param {string} url 100 * @return {!WebInspector.NetworkRequest} 101 */ 102 inflightRequestForURL: function(url) 103 { 104 return this._dispatcher._inflightRequestsByURL[url]; 105 }, 106 107 /** 108 * @param {!WebInspector.Event} event 109 */ 110 _cacheDisabledSettingChanged: function(event) 111 { 112 var enabled = /** @type {boolean} */ (event.data); 113 NetworkAgent.setCacheDisabled(enabled); 114 }, 115 116 __proto__: WebInspector.Object.prototype 117} 118 119/** 120 * @constructor 121 * @implements {NetworkAgent.Dispatcher} 122 */ 123WebInspector.NetworkDispatcher = function(manager) 124{ 125 this._manager = manager; 126 this._inflightRequestsById = {}; 127 this._inflightRequestsByURL = {}; 128 InspectorBackend.registerNetworkDispatcher(this); 129} 130 131WebInspector.NetworkDispatcher.prototype = { 132 /** 133 * @param {!NetworkAgent.Headers} headersMap 134 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>} 135 */ 136 _headersMapToHeadersArray: function(headersMap) 137 { 138 var result = []; 139 for (var name in headersMap) { 140 var values = headersMap[name].split("\n"); 141 for (var i = 0; i < values.length; ++i) 142 result.push({name: name, value: values[i]}); 143 } 144 return result; 145 }, 146 147 /** 148 * @param {!WebInspector.NetworkRequest} networkRequest 149 * @param {!NetworkAgent.Request} request 150 */ 151 _updateNetworkRequestWithRequest: function(networkRequest, request) 152 { 153 networkRequest.requestMethod = request.method; 154 networkRequest.setRequestHeaders(this._headersMapToHeadersArray(request.headers)); 155 networkRequest.requestFormData = request.postData; 156 }, 157 158 /** 159 * @param {!WebInspector.NetworkRequest} networkRequest 160 * @param {!NetworkAgent.Response=} response 161 */ 162 _updateNetworkRequestWithResponse: function(networkRequest, response) 163 { 164 if (!response) 165 return; 166 167 if (response.url && networkRequest.url !== response.url) 168 networkRequest.url = response.url; 169 networkRequest.mimeType = response.mimeType; 170 networkRequest.statusCode = response.status; 171 networkRequest.statusText = response.statusText; 172 networkRequest.responseHeaders = this._headersMapToHeadersArray(response.headers); 173 if (response.headersText) 174 networkRequest.responseHeadersText = response.headersText; 175 if (response.requestHeaders) { 176 networkRequest.setRequestHeaders(this._headersMapToHeadersArray(response.requestHeaders)); 177 networkRequest.setRequestHeadersText(response.requestHeadersText || ""); 178 } 179 180 networkRequest.connectionReused = response.connectionReused; 181 networkRequest.connectionId = response.connectionId; 182 183 if (response.fromDiskCache) 184 networkRequest.cached = true; 185 else 186 networkRequest.timing = response.timing; 187 188 if (!this._mimeTypeIsConsistentWithType(networkRequest)) { 189 WebInspector.console.addMessage(WebInspector.ConsoleMessage.create(WebInspector.ConsoleMessage.MessageSource.Network, 190 WebInspector.ConsoleMessage.MessageLevel.Log, 191 WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s: \"%s\".", networkRequest.type.title(), networkRequest.mimeType, networkRequest.url), 192 WebInspector.ConsoleMessage.MessageType.Log, 193 "", 194 0, 195 0, 196 1, 197 [], 198 undefined, 199 networkRequest.requestId)); 200 } 201 }, 202 203 /** 204 * @param {!WebInspector.NetworkRequest} networkRequest 205 * @return {boolean} 206 */ 207 _mimeTypeIsConsistentWithType: function(networkRequest) 208 { 209 // If status is an error, content is likely to be of an inconsistent type, 210 // as it's going to be an error message. We do not want to emit a warning 211 // for this, though, as this will already be reported as resource loading failure. 212 // Also, if a URL like http://localhost/wiki/load.php?debug=true&lang=en produces text/css and gets reloaded, 213 // it is 304 Not Modified and its guessed mime-type is text/php, which is wrong. 214 // Don't check for mime-types in 304-resources. 215 if (networkRequest.hasErrorStatusCode() || networkRequest.statusCode === 304 || networkRequest.statusCode === 204) 216 return true; 217 218 if (typeof networkRequest.type === "undefined" 219 || networkRequest.type === WebInspector.resourceTypes.Other 220 || networkRequest.type === WebInspector.resourceTypes.XHR 221 || networkRequest.type === WebInspector.resourceTypes.WebSocket) 222 return true; 223 224 if (!networkRequest.mimeType) 225 return true; // Might be not known for cached resources with null responses. 226 227 if (networkRequest.mimeType in WebInspector.NetworkManager._MIMETypes) 228 return networkRequest.type.name() in WebInspector.NetworkManager._MIMETypes[networkRequest.mimeType]; 229 230 return false; 231 }, 232 233 /** 234 * @param {!NetworkAgent.Response} response 235 * @return {boolean} 236 */ 237 _isNull: function(response) 238 { 239 if (!response) 240 return true; 241 return !response.status && !response.mimeType && (!response.headers || !Object.keys(response.headers).length); 242 }, 243 244 /** 245 * @param {!NetworkAgent.RequestId} requestId 246 * @param {!PageAgent.FrameId} frameId 247 * @param {!NetworkAgent.LoaderId} loaderId 248 * @param {string} documentURL 249 * @param {!NetworkAgent.Request} request 250 * @param {!NetworkAgent.Timestamp} time 251 * @param {!NetworkAgent.Initiator} initiator 252 * @param {!NetworkAgent.Response=} redirectResponse 253 */ 254 requestWillBeSent: function(requestId, frameId, loaderId, documentURL, request, time, initiator, redirectResponse) 255 { 256 var networkRequest = this._inflightRequestsById[requestId]; 257 if (networkRequest) { 258 // FIXME: move this check to the backend. 259 if (!redirectResponse) 260 return; 261 this.responseReceived(requestId, frameId, loaderId, time, PageAgent.ResourceType.Other, redirectResponse); 262 networkRequest = this._appendRedirect(requestId, time, request.url); 263 } else 264 networkRequest = this._createNetworkRequest(requestId, frameId, loaderId, request.url, documentURL, initiator); 265 networkRequest.hasNetworkData = true; 266 this._updateNetworkRequestWithRequest(networkRequest, request); 267 networkRequest.startTime = time; 268 269 this._startNetworkRequest(networkRequest); 270 }, 271 272 /** 273 * @param {!NetworkAgent.RequestId} requestId 274 */ 275 requestServedFromCache: function(requestId) 276 { 277 var networkRequest = this._inflightRequestsById[requestId]; 278 if (!networkRequest) 279 return; 280 281 networkRequest.cached = true; 282 }, 283 284 /** 285 * @param {!NetworkAgent.RequestId} requestId 286 * @param {!PageAgent.FrameId} frameId 287 * @param {!NetworkAgent.LoaderId} loaderId 288 * @param {!NetworkAgent.Timestamp} time 289 * @param {!PageAgent.ResourceType} resourceType 290 * @param {!NetworkAgent.Response} response 291 */ 292 responseReceived: function(requestId, frameId, loaderId, time, resourceType, response) 293 { 294 // FIXME: move this check to the backend. 295 if (this._isNull(response)) 296 return; 297 298 var networkRequest = this._inflightRequestsById[requestId]; 299 if (!networkRequest) { 300 // We missed the requestWillBeSent. 301 var eventData = {}; 302 eventData.url = response.url; 303 eventData.frameId = frameId; 304 eventData.loaderId = loaderId; 305 eventData.resourceType = resourceType; 306 eventData.mimeType = response.mimeType; 307 this._manager.dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.RequestUpdateDropped, eventData); 308 return; 309 } 310 311 networkRequest.responseReceivedTime = time; 312 networkRequest.type = WebInspector.resourceTypes[resourceType]; 313 314 this._updateNetworkRequestWithResponse(networkRequest, response); 315 316 this._updateNetworkRequest(networkRequest); 317 }, 318 319 /** 320 * @param {!NetworkAgent.RequestId} requestId 321 * @param {!NetworkAgent.Timestamp} time 322 * @param {number} dataLength 323 * @param {number} encodedDataLength 324 */ 325 dataReceived: function(requestId, time, dataLength, encodedDataLength) 326 { 327 var networkRequest = this._inflightRequestsById[requestId]; 328 if (!networkRequest) 329 return; 330 331 networkRequest.resourceSize += dataLength; 332 if (encodedDataLength != -1) 333 networkRequest.increaseTransferSize(encodedDataLength); 334 networkRequest.endTime = time; 335 336 this._updateNetworkRequest(networkRequest); 337 }, 338 339 /** 340 * @param {!NetworkAgent.RequestId} requestId 341 * @param {!NetworkAgent.Timestamp} finishTime 342 */ 343 loadingFinished: function(requestId, finishTime) 344 { 345 var networkRequest = this._inflightRequestsById[requestId]; 346 if (!networkRequest) 347 return; 348 this._finishNetworkRequest(networkRequest, finishTime); 349 }, 350 351 /** 352 * @param {!NetworkAgent.RequestId} requestId 353 * @param {!NetworkAgent.Timestamp} time 354 * @param {string} localizedDescription 355 * @param {boolean=} canceled 356 */ 357 loadingFailed: function(requestId, time, localizedDescription, canceled) 358 { 359 var networkRequest = this._inflightRequestsById[requestId]; 360 if (!networkRequest) 361 return; 362 363 networkRequest.failed = true; 364 networkRequest.canceled = canceled; 365 networkRequest.localizedFailDescription = localizedDescription; 366 this._finishNetworkRequest(networkRequest, time); 367 }, 368 369 /** 370 * @param {!NetworkAgent.RequestId} requestId 371 * @param {string} requestURL 372 */ 373 webSocketCreated: function(requestId, requestURL) 374 { 375 var networkRequest = new WebInspector.NetworkRequest(requestId, requestURL, "", "", ""); 376 networkRequest.type = WebInspector.resourceTypes.WebSocket; 377 this._startNetworkRequest(networkRequest); 378 }, 379 380 /** 381 * @param {!NetworkAgent.RequestId} requestId 382 * @param {!NetworkAgent.Timestamp} time 383 * @param {!NetworkAgent.WebSocketRequest} request 384 */ 385 webSocketWillSendHandshakeRequest: function(requestId, time, request) 386 { 387 var networkRequest = this._inflightRequestsById[requestId]; 388 if (!networkRequest) 389 return; 390 391 networkRequest.requestMethod = "GET"; 392 networkRequest.setRequestHeaders(this._headersMapToHeadersArray(request.headers)); 393 networkRequest.startTime = time; 394 395 this._updateNetworkRequest(networkRequest); 396 }, 397 398 /** 399 * @param {!NetworkAgent.RequestId} requestId 400 * @param {!NetworkAgent.Timestamp} time 401 * @param {!NetworkAgent.WebSocketResponse} response 402 */ 403 webSocketHandshakeResponseReceived: function(requestId, time, response) 404 { 405 var networkRequest = this._inflightRequestsById[requestId]; 406 if (!networkRequest) 407 return; 408 409 networkRequest.statusCode = response.status; 410 networkRequest.statusText = response.statusText; 411 networkRequest.responseHeaders = this._headersMapToHeadersArray(response.headers); 412 networkRequest.responseReceivedTime = time; 413 414 this._updateNetworkRequest(networkRequest); 415 }, 416 417 /** 418 * @param {!NetworkAgent.RequestId} requestId 419 * @param {!NetworkAgent.Timestamp} time 420 * @param {!NetworkAgent.WebSocketFrame} response 421 */ 422 webSocketFrameReceived: function(requestId, time, response) 423 { 424 var networkRequest = this._inflightRequestsById[requestId]; 425 if (!networkRequest) 426 return; 427 428 networkRequest.addFrame(response, time); 429 networkRequest.responseReceivedTime = time; 430 431 this._updateNetworkRequest(networkRequest); 432 }, 433 434 /** 435 * @param {!NetworkAgent.RequestId} requestId 436 * @param {!NetworkAgent.Timestamp} time 437 * @param {!NetworkAgent.WebSocketFrame} response 438 */ 439 webSocketFrameSent: function(requestId, time, response) 440 { 441 var networkRequest = this._inflightRequestsById[requestId]; 442 if (!networkRequest) 443 return; 444 445 networkRequest.addFrame(response, time, true); 446 networkRequest.responseReceivedTime = time; 447 448 this._updateNetworkRequest(networkRequest); 449 }, 450 451 /** 452 * @param {!NetworkAgent.RequestId} requestId 453 * @param {!NetworkAgent.Timestamp} time 454 * @param {string} errorMessage 455 */ 456 webSocketFrameError: function(requestId, time, errorMessage) 457 { 458 var networkRequest = this._inflightRequestsById[requestId]; 459 if (!networkRequest) 460 return; 461 462 networkRequest.addFrameError(errorMessage, time); 463 networkRequest.responseReceivedTime = time; 464 465 this._updateNetworkRequest(networkRequest); 466 }, 467 468 /** 469 * @param {!NetworkAgent.RequestId} requestId 470 * @param {!NetworkAgent.Timestamp} time 471 */ 472 webSocketClosed: function(requestId, time) 473 { 474 var networkRequest = this._inflightRequestsById[requestId]; 475 if (!networkRequest) 476 return; 477 this._finishNetworkRequest(networkRequest, time); 478 }, 479 480 /** 481 * @param {!NetworkAgent.RequestId} requestId 482 * @param {!NetworkAgent.Timestamp} time 483 * @param {string} redirectURL 484 * @return {!WebInspector.NetworkRequest} 485 */ 486 _appendRedirect: function(requestId, time, redirectURL) 487 { 488 var originalNetworkRequest = this._inflightRequestsById[requestId]; 489 var previousRedirects = originalNetworkRequest.redirects || []; 490 originalNetworkRequest.requestId = "redirected:" + requestId + "." + previousRedirects.length; 491 delete originalNetworkRequest.redirects; 492 if (previousRedirects.length > 0) 493 originalNetworkRequest.redirectSource = previousRedirects[previousRedirects.length - 1]; 494 this._finishNetworkRequest(originalNetworkRequest, time); 495 var newNetworkRequest = this._createNetworkRequest(requestId, originalNetworkRequest.frameId, originalNetworkRequest.loaderId, 496 redirectURL, originalNetworkRequest.documentURL, originalNetworkRequest.initiator); 497 newNetworkRequest.redirects = previousRedirects.concat(originalNetworkRequest); 498 return newNetworkRequest; 499 }, 500 501 /** 502 * @param {!WebInspector.NetworkRequest} networkRequest 503 */ 504 _startNetworkRequest: function(networkRequest) 505 { 506 this._inflightRequestsById[networkRequest.requestId] = networkRequest; 507 this._inflightRequestsByURL[networkRequest.url] = networkRequest; 508 this._dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.RequestStarted, networkRequest); 509 }, 510 511 /** 512 * @param {!WebInspector.NetworkRequest} networkRequest 513 */ 514 _updateNetworkRequest: function(networkRequest) 515 { 516 this._dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.RequestUpdated, networkRequest); 517 }, 518 519 /** 520 * @param {!WebInspector.NetworkRequest} networkRequest 521 * @param {!NetworkAgent.Timestamp} finishTime 522 */ 523 _finishNetworkRequest: function(networkRequest, finishTime) 524 { 525 networkRequest.endTime = finishTime; 526 networkRequest.finished = true; 527 this._dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.RequestFinished, networkRequest); 528 delete this._inflightRequestsById[networkRequest.requestId]; 529 delete this._inflightRequestsByURL[networkRequest.url]; 530 }, 531 532 /** 533 * @param {string} eventType 534 * @param {!WebInspector.NetworkRequest} networkRequest 535 */ 536 _dispatchEventToListeners: function(eventType, networkRequest) 537 { 538 this._manager.dispatchEventToListeners(eventType, networkRequest); 539 }, 540 541 /** 542 * @param {!NetworkAgent.RequestId} requestId 543 * @param {string} frameId 544 * @param {!NetworkAgent.LoaderId} loaderId 545 * @param {string} url 546 * @param {string} documentURL 547 * @param {!NetworkAgent.Initiator} initiator 548 */ 549 _createNetworkRequest: function(requestId, frameId, loaderId, url, documentURL, initiator) 550 { 551 var networkRequest = new WebInspector.NetworkRequest(requestId, url, documentURL, frameId, loaderId); 552 networkRequest.initiator = initiator; 553 return networkRequest; 554 } 555} 556 557/** 558 * @type {!WebInspector.NetworkManager} 559 */ 560WebInspector.networkManager; 561