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