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