1 /* 2 * Copyright (C) 2012 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 * @implements {WebInspector.ContentProvider} 35 * @param {!NetworkAgent.RequestId} requestId 36 * @param {string} url 37 * @param {string} documentURL 38 * @param {!PageAgent.FrameId} frameId 39 * @param {!NetworkAgent.LoaderId} loaderId 40 */ 41 WebInspector.NetworkRequest = function(requestId, url, documentURL, frameId, loaderId) 42 { 43 this._requestId = requestId; 44 this.url = url; 45 this._documentURL = documentURL; 46 this._frameId = frameId; 47 this._loaderId = loaderId; 48 this._startTime = -1; 49 this._endTime = -1; 50 51 this.statusCode = 0; 52 this.statusText = ""; 53 this.requestMethod = ""; 54 this.requestTime = 0; 55 56 this._type = WebInspector.resourceTypes.Other; 57 this._contentEncoded = false; 58 this._pendingContentCallbacks = []; 59 this._frames = []; 60 61 this._responseHeaderValues = {}; 62 } 63 64 WebInspector.NetworkRequest.Events = { 65 FinishedLoading: "FinishedLoading", 66 TimingChanged: "TimingChanged", 67 RequestHeadersChanged: "RequestHeadersChanged", 68 ResponseHeadersChanged: "ResponseHeadersChanged", 69 } 70 71 /** @enum {string} */ 72 WebInspector.NetworkRequest.InitiatorType = { 73 Other: "other", 74 Parser: "parser", 75 Redirect: "redirect", 76 Script: "script" 77 } 78 79 /** @typedef {!{name: string, value: string}} */ 80 WebInspector.NetworkRequest.NameValue; 81 82 WebInspector.NetworkRequest.prototype = { 83 /** 84 * @return {!NetworkAgent.RequestId} 85 */ 86 get requestId() 87 { 88 return this._requestId; 89 }, 90 91 set requestId(requestId) 92 { 93 this._requestId = requestId; 94 }, 95 96 /** 97 * @return {string} 98 */ 99 get url() 100 { 101 return this._url; 102 }, 103 104 set url(x) 105 { 106 if (this._url === x) 107 return; 108 109 this._url = x; 110 this._parsedURL = new WebInspector.ParsedURL(x); 111 delete this._queryString; 112 delete this._parsedQueryParameters; 113 delete this._name; 114 delete this._path; 115 }, 116 117 /** 118 * @return {string} 119 */ 120 get documentURL() 121 { 122 return this._documentURL; 123 }, 124 125 get parsedURL() 126 { 127 return this._parsedURL; 128 }, 129 130 /** 131 * @return {!PageAgent.FrameId} 132 */ 133 get frameId() 134 { 135 return this._frameId; 136 }, 137 138 /** 139 * @return {!NetworkAgent.LoaderId} 140 */ 141 get loaderId() 142 { 143 return this._loaderId; 144 }, 145 146 /** 147 * @return {number} 148 */ 149 get startTime() 150 { 151 return this._startTime || -1; 152 }, 153 154 set startTime(x) 155 { 156 this._startTime = x; 157 }, 158 159 /** 160 * @return {number} 161 */ 162 get responseReceivedTime() 163 { 164 return this._responseReceivedTime || -1; 165 }, 166 167 set responseReceivedTime(x) 168 { 169 this._responseReceivedTime = x; 170 }, 171 172 /** 173 * @return {number} 174 */ 175 get endTime() 176 { 177 return this._endTime || -1; 178 }, 179 180 set endTime(x) 181 { 182 if (this.timing && this.timing.requestTime) { 183 // Check against accurate responseReceivedTime. 184 this._endTime = Math.max(x, this.responseReceivedTime); 185 } else { 186 // Prefer endTime since it might be from the network stack. 187 this._endTime = x; 188 if (this._responseReceivedTime > x) 189 this._responseReceivedTime = x; 190 } 191 }, 192 193 /** 194 * @return {number} 195 */ 196 get duration() 197 { 198 if (this._endTime === -1 || this._startTime === -1) 199 return -1; 200 return this._endTime - this._startTime; 201 }, 202 203 /** 204 * @return {number} 205 */ 206 get latency() 207 { 208 if (this._responseReceivedTime === -1 || this._startTime === -1) 209 return -1; 210 return this._responseReceivedTime - this._startTime; 211 }, 212 213 /** 214 * @return {number} 215 */ 216 get resourceSize() 217 { 218 return this._resourceSize || 0; 219 }, 220 221 set resourceSize(x) 222 { 223 this._resourceSize = x; 224 }, 225 226 /** 227 * @return {number} 228 */ 229 get transferSize() 230 { 231 if (typeof this._transferSize === "number") 232 return this._transferSize; 233 if (this.statusCode === 304) // Not modified 234 return this.responseHeadersSize; 235 if (this._cached) 236 return 0; 237 // If we did not receive actual transfer size from network 238 // stack, we prefer using Content-Length over resourceSize as 239 // resourceSize may differ from actual transfer size if platform's 240 // network stack performed decoding (e.g. gzip decompression). 241 // The Content-Length, though, is expected to come from raw 242 // response headers and will reflect actual transfer length. 243 // This won't work for chunked content encoding, so fall back to 244 // resourceSize when we don't have Content-Length. This still won't 245 // work for chunks with non-trivial encodings. We need a way to 246 // get actual transfer size from the network stack. 247 var bodySize = Number(this.responseHeaderValue("Content-Length") || this.resourceSize); 248 return this.responseHeadersSize + bodySize; 249 }, 250 251 /** 252 * @param {number} x 253 */ 254 increaseTransferSize: function(x) 255 { 256 this._transferSize = (this._transferSize || 0) + x; 257 }, 258 259 /** 260 * @return {boolean} 261 */ 262 get finished() 263 { 264 return this._finished; 265 }, 266 267 set finished(x) 268 { 269 if (this._finished === x) 270 return; 271 272 this._finished = x; 273 274 if (x) { 275 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.FinishedLoading, this); 276 if (this._pendingContentCallbacks.length) 277 this._innerRequestContent(); 278 } 279 }, 280 281 /** 282 * @return {boolean} 283 */ 284 get failed() 285 { 286 return this._failed; 287 }, 288 289 set failed(x) 290 { 291 this._failed = x; 292 }, 293 294 /** 295 * @return {boolean} 296 */ 297 get canceled() 298 { 299 return this._canceled; 300 }, 301 302 set canceled(x) 303 { 304 this._canceled = x; 305 }, 306 307 /** 308 * @return {boolean} 309 */ 310 get cached() 311 { 312 return !!this._cached && !this._transferSize; 313 }, 314 315 set cached(x) 316 { 317 this._cached = x; 318 if (x) 319 delete this._timing; 320 }, 321 322 /** 323 * @return {!NetworkAgent.ResourceTiming|undefined} 324 */ 325 get timing() 326 { 327 return this._timing; 328 }, 329 330 set timing(x) 331 { 332 if (x && !this._cached) { 333 // Take startTime and responseReceivedTime from timing data for better accuracy. 334 // Timing's requestTime is a baseline in seconds, rest of the numbers there are ticks in millis. 335 this._startTime = x.requestTime; 336 this._responseReceivedTime = x.requestTime + x.receiveHeadersEnd / 1000.0; 337 338 this._timing = x; 339 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.TimingChanged, this); 340 } 341 }, 342 343 /** 344 * @return {string} 345 */ 346 get mimeType() 347 { 348 return this._mimeType; 349 }, 350 351 set mimeType(x) 352 { 353 this._mimeType = x; 354 }, 355 356 /** 357 * @return {string} 358 */ 359 get displayName() 360 { 361 return this._parsedURL.displayName; 362 }, 363 364 name: function() 365 { 366 if (this._name) 367 return this._name; 368 this._parseNameAndPathFromURL(); 369 return this._name; 370 }, 371 372 path: function() 373 { 374 if (this._path) 375 return this._path; 376 this._parseNameAndPathFromURL(); 377 return this._path; 378 }, 379 380 _parseNameAndPathFromURL: function() 381 { 382 if (this._parsedURL.isDataURL()) { 383 this._name = this._parsedURL.dataURLDisplayName(); 384 this._path = ""; 385 } else if (this._parsedURL.isAboutBlank()) { 386 this._name = this._parsedURL.url; 387 this._path = ""; 388 } else { 389 this._path = this._parsedURL.host + this._parsedURL.folderPathComponents; 390 this._path = this._path.trimURL(WebInspector.inspectedPageDomain ? WebInspector.inspectedPageDomain : ""); 391 if (this._parsedURL.lastPathComponent || this._parsedURL.queryParams) 392 this._name = this._parsedURL.lastPathComponent + (this._parsedURL.queryParams ? "?" + this._parsedURL.queryParams : ""); 393 else if (this._parsedURL.folderPathComponents) { 394 this._name = this._parsedURL.folderPathComponents.substring(this._parsedURL.folderPathComponents.lastIndexOf("/") + 1) + "/"; 395 this._path = this._path.substring(0, this._path.lastIndexOf("/")); 396 } else { 397 this._name = this._parsedURL.host; 398 this._path = ""; 399 } 400 } 401 }, 402 403 /** 404 * @return {string} 405 */ 406 get folder() 407 { 408 var path = this._parsedURL.path; 409 var indexOfQuery = path.indexOf("?"); 410 if (indexOfQuery !== -1) 411 path = path.substring(0, indexOfQuery); 412 var lastSlashIndex = path.lastIndexOf("/"); 413 return lastSlashIndex !== -1 ? path.substring(0, lastSlashIndex) : ""; 414 }, 415 416 /** 417 * @return {!WebInspector.ResourceType} 418 */ 419 get type() 420 { 421 return this._type; 422 }, 423 424 set type(x) 425 { 426 this._type = x; 427 }, 428 429 /** 430 * @return {string} 431 */ 432 get domain() 433 { 434 return this._parsedURL.host; 435 }, 436 437 /** 438 * @return {string} 439 */ 440 get scheme() 441 { 442 return this._parsedURL.scheme; 443 }, 444 445 /** 446 * @return {?WebInspector.NetworkRequest} 447 */ 448 get redirectSource() 449 { 450 if (this.redirects && this.redirects.length > 0) 451 return this.redirects[this.redirects.length - 1]; 452 return this._redirectSource; 453 }, 454 455 set redirectSource(x) 456 { 457 this._redirectSource = x; 458 delete this._initiatorInfo; 459 }, 460 461 /** 462 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>} 463 */ 464 requestHeaders: function() 465 { 466 return this._requestHeaders || []; 467 }, 468 469 /** 470 * @param {!Array.<!WebInspector.NetworkRequest.NameValue>} headers 471 */ 472 setRequestHeaders: function(headers) 473 { 474 this._requestHeaders = headers; 475 delete this._requestCookies; 476 477 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RequestHeadersChanged); 478 }, 479 480 /** 481 * @return {string|undefined} 482 */ 483 requestHeadersText: function() 484 { 485 return this._requestHeadersText; 486 }, 487 488 /** 489 * @param {string} text 490 */ 491 setRequestHeadersText: function(text) 492 { 493 this._requestHeadersText = text; 494 495 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RequestHeadersChanged); 496 }, 497 498 /** 499 * @param {string} headerName 500 * @return {string|undefined} 501 */ 502 requestHeaderValue: function(headerName) 503 { 504 return this._headerValue(this.requestHeaders(), headerName); 505 }, 506 507 /** 508 * @return {!Array.<!WebInspector.Cookie>} 509 */ 510 get requestCookies() 511 { 512 if (!this._requestCookies) 513 this._requestCookies = WebInspector.CookieParser.parseCookie(this.requestHeaderValue("Cookie")); 514 return this._requestCookies; 515 }, 516 517 /** 518 * @return {string|undefined} 519 */ 520 get requestFormData() 521 { 522 return this._requestFormData; 523 }, 524 525 set requestFormData(x) 526 { 527 this._requestFormData = x; 528 delete this._parsedFormParameters; 529 }, 530 531 /** 532 * @return {string|undefined} 533 */ 534 requestHttpVersion: function() 535 { 536 var headersText = this.requestHeadersText(); 537 if (!headersText) 538 return undefined; 539 var firstLine = headersText.split(/\r\n/)[0]; 540 var match = firstLine.match(/(HTTP\/\d+\.\d+)$/); 541 return match ? match[1] : undefined; 542 }, 543 544 /** 545 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>} 546 */ 547 get responseHeaders() 548 { 549 return this._responseHeaders || []; 550 }, 551 552 set responseHeaders(x) 553 { 554 this._responseHeaders = x; 555 delete this._sortedResponseHeaders; 556 delete this._responseCookies; 557 this._responseHeaderValues = {}; 558 559 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.ResponseHeadersChanged); 560 }, 561 562 /** 563 * @return {string} 564 */ 565 get responseHeadersText() 566 { 567 if (typeof this._responseHeadersText === "undefined") { 568 this._responseHeadersText = "HTTP/1.1 " + this.statusCode + " " + this.statusText + "\r\n"; 569 for (var i = 0; i < this.responseHeaders.length; ++i) 570 this._responseHeadersText += this.responseHeaders[i].name + ": " + this.responseHeaders[i].value + "\r\n"; 571 } 572 return this._responseHeadersText; 573 }, 574 575 set responseHeadersText(x) 576 { 577 this._responseHeadersText = x; 578 579 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.ResponseHeadersChanged); 580 }, 581 582 /** 583 * @return {number} 584 */ 585 get responseHeadersSize() 586 { 587 return this.responseHeadersText.length; 588 }, 589 590 /** 591 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>} 592 */ 593 get sortedResponseHeaders() 594 { 595 if (this._sortedResponseHeaders !== undefined) 596 return this._sortedResponseHeaders; 597 598 this._sortedResponseHeaders = this.responseHeaders.slice(); 599 this._sortedResponseHeaders.sort(function(a, b) { return a.name.toLowerCase().compareTo(b.name.toLowerCase()); }); 600 return this._sortedResponseHeaders; 601 }, 602 603 /** 604 * @param {string} headerName 605 * @return {string|undefined} 606 */ 607 responseHeaderValue: function(headerName) 608 { 609 var value = this._responseHeaderValues[headerName]; 610 if (value === undefined) { 611 value = this._headerValue(this.responseHeaders, headerName); 612 this._responseHeaderValues[headerName] = (value !== undefined) ? value : null; 613 } 614 return (value !== null) ? value : undefined; 615 }, 616 617 /** 618 * @return {!Array.<!WebInspector.Cookie>} 619 */ 620 get responseCookies() 621 { 622 if (!this._responseCookies) 623 this._responseCookies = WebInspector.CookieParser.parseSetCookie(this.responseHeaderValue("Set-Cookie")); 624 return this._responseCookies; 625 }, 626 627 /** 628 * @return {?string} 629 */ 630 queryString: function() 631 { 632 if (this._queryString !== undefined) 633 return this._queryString; 634 635 var queryString = null; 636 var url = this.url; 637 var questionMarkPosition = url.indexOf("?"); 638 if (questionMarkPosition !== -1) { 639 queryString = url.substring(questionMarkPosition + 1); 640 var hashSignPosition = queryString.indexOf("#"); 641 if (hashSignPosition !== -1) 642 queryString = queryString.substring(0, hashSignPosition); 643 } 644 this._queryString = queryString; 645 return this._queryString; 646 }, 647 648 /** 649 * @return {?Array.<!WebInspector.NetworkRequest.NameValue>} 650 */ 651 get queryParameters() 652 { 653 if (this._parsedQueryParameters) 654 return this._parsedQueryParameters; 655 var queryString = this.queryString(); 656 if (!queryString) 657 return null; 658 this._parsedQueryParameters = this._parseParameters(queryString); 659 return this._parsedQueryParameters; 660 }, 661 662 /** 663 * @return {?Array.<!WebInspector.NetworkRequest.NameValue>} 664 */ 665 get formParameters() 666 { 667 if (this._parsedFormParameters) 668 return this._parsedFormParameters; 669 if (!this.requestFormData) 670 return null; 671 var requestContentType = this.requestContentType(); 672 if (!requestContentType || !requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i)) 673 return null; 674 this._parsedFormParameters = this._parseParameters(this.requestFormData); 675 return this._parsedFormParameters; 676 }, 677 678 /** 679 * @return {string|undefined} 680 */ 681 get responseHttpVersion() 682 { 683 var match = this.responseHeadersText.match(/^(HTTP\/\d+\.\d+)/); 684 return match ? match[1] : undefined; 685 }, 686 687 /** 688 * @param {string} queryString 689 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>} 690 */ 691 _parseParameters: function(queryString) 692 { 693 function parseNameValue(pair) 694 { 695 var splitPair = pair.split("=", 2); 696 return {name: splitPair[0], value: splitPair[1] || ""}; 697 } 698 return queryString.split("&").map(parseNameValue); 699 }, 700 701 /** 702 * @param {!Array.<!WebInspector.NetworkRequest.NameValue>} headers 703 * @param {string} headerName 704 * @return {string|undefined} 705 */ 706 _headerValue: function(headers, headerName) 707 { 708 headerName = headerName.toLowerCase(); 709 710 var values = []; 711 for (var i = 0; i < headers.length; ++i) { 712 if (headers[i].name.toLowerCase() === headerName) 713 values.push(headers[i].value); 714 } 715 if (!values.length) 716 return undefined; 717 // Set-Cookie values should be separated by '\n', not comma, otherwise cookies could not be parsed. 718 if (headerName === "set-cookie") 719 return values.join("\n"); 720 return values.join(", "); 721 }, 722 723 /** 724 * @return {?string|undefined} 725 */ 726 get content() 727 { 728 return this._content; 729 }, 730 731 /** 732 * @return {boolean} 733 */ 734 get contentEncoded() 735 { 736 return this._contentEncoded; 737 }, 738 739 /** 740 * @return {string} 741 */ 742 contentURL: function() 743 { 744 return this._url; 745 }, 746 747 /** 748 * @return {!WebInspector.ResourceType} 749 */ 750 contentType: function() 751 { 752 return this._type; 753 }, 754 755 /** 756 * @param {function(?string)} callback 757 */ 758 requestContent: function(callback) 759 { 760 // We do not support content retrieval for WebSockets at the moment. 761 // Since WebSockets are potentially long-living, fail requests immediately 762 // to prevent caller blocking until resource is marked as finished. 763 if (this.type === WebInspector.resourceTypes.WebSocket) { 764 callback(null); 765 return; 766 } 767 if (typeof this._content !== "undefined") { 768 callback(this.content || null); 769 return; 770 } 771 this._pendingContentCallbacks.push(callback); 772 if (this.finished) 773 this._innerRequestContent(); 774 }, 775 776 /** 777 * @param {string} query 778 * @param {boolean} caseSensitive 779 * @param {boolean} isRegex 780 * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback 781 */ 782 searchInContent: function(query, caseSensitive, isRegex, callback) 783 { 784 callback([]); 785 }, 786 787 /** 788 * @return {boolean} 789 */ 790 isHttpFamily: function() 791 { 792 return !!this.url.match(/^https?:/i); 793 }, 794 795 /** 796 * @return {string|undefined} 797 */ 798 requestContentType: function() 799 { 800 return this.requestHeaderValue("Content-Type"); 801 }, 802 803 /** 804 * @return {boolean} 805 */ 806 isPingRequest: function() 807 { 808 return "text/ping" === this.requestContentType(); 809 }, 810 811 /** 812 * @return {boolean} 813 */ 814 hasErrorStatusCode: function() 815 { 816 return this.statusCode >= 400; 817 }, 818 819 /** 820 * @param {!Element} image 821 */ 822 populateImageSource: function(image) 823 { 824 /** 825 * @this {WebInspector.NetworkRequest} 826 * @param {?string} content 827 */ 828 function onResourceContent(content) 829 { 830 var imageSrc = this.asDataURL(); 831 if (imageSrc === null) 832 imageSrc = this.url; 833 image.src = imageSrc; 834 } 835 836 this.requestContent(onResourceContent.bind(this)); 837 }, 838 839 /** 840 * @return {?string} 841 */ 842 asDataURL: function() 843 { 844 return WebInspector.contentAsDataURL(this._content, this.mimeType, this._contentEncoded); 845 }, 846 847 _innerRequestContent: function() 848 { 849 if (this._contentRequested) 850 return; 851 this._contentRequested = true; 852 853 /** 854 * @param {?Protocol.Error} error 855 * @param {string} content 856 * @param {boolean} contentEncoded 857 * @this {WebInspector.NetworkRequest} 858 */ 859 function onResourceContent(error, content, contentEncoded) 860 { 861 this._content = error ? null : content; 862 this._contentEncoded = contentEncoded; 863 var callbacks = this._pendingContentCallbacks.slice(); 864 for (var i = 0; i < callbacks.length; ++i) 865 callbacks[i](this._content); 866 this._pendingContentCallbacks.length = 0; 867 delete this._contentRequested; 868 } 869 NetworkAgent.getResponseBody(this._requestId, onResourceContent.bind(this)); 870 }, 871 872 /** 873 * @return {!{type: !WebInspector.NetworkRequest.InitiatorType, url: string, source: string, lineNumber: number, columnNumber: number}} 874 */ 875 initiatorInfo: function() 876 { 877 if (this._initiatorInfo) 878 return this._initiatorInfo; 879 880 var type = WebInspector.NetworkRequest.InitiatorType.Other; 881 var url = ""; 882 var lineNumber = -Infinity; 883 var columnNumber = -Infinity; 884 885 if (this.redirectSource) { 886 type = WebInspector.NetworkRequest.InitiatorType.Redirect; 887 url = this.redirectSource.url; 888 } else if (this.initiator) { 889 if (this.initiator.type === NetworkAgent.InitiatorType.Parser) { 890 type = WebInspector.NetworkRequest.InitiatorType.Parser; 891 url = this.initiator.url; 892 lineNumber = this.initiator.lineNumber; 893 } else if (this.initiator.type === NetworkAgent.InitiatorType.Script) { 894 var topFrame = this.initiator.stackTrace[0]; 895 if (topFrame.url) { 896 type = WebInspector.NetworkRequest.InitiatorType.Script; 897 url = topFrame.url; 898 lineNumber = topFrame.lineNumber; 899 columnNumber = topFrame.columnNumber; 900 } 901 } 902 } 903 904 this._initiatorInfo = {type: type, url: url, source: WebInspector.displayNameForURL(url), lineNumber: lineNumber, columnNumber: columnNumber}; 905 return this._initiatorInfo; 906 }, 907 908 /** 909 * @return {!Array.<!Object>} 910 */ 911 frames: function() 912 { 913 return this._frames; 914 }, 915 916 /** 917 * @param {number} position 918 * @return {!Object|undefined} 919 */ 920 frame: function(position) 921 { 922 return this._frames[position]; 923 }, 924 925 /** 926 * @param {string} errorMessage 927 * @param {number} time 928 */ 929 addFrameError: function(errorMessage, time) 930 { 931 this._pushFrame({errorMessage: errorMessage, time: time}); 932 }, 933 934 /** 935 * @param {!NetworkAgent.WebSocketFrame} response 936 * @param {number} time 937 * @param {boolean} sent 938 */ 939 addFrame: function(response, time, sent) 940 { 941 response.time = time; 942 if (sent) 943 response.sent = sent; 944 this._pushFrame(response); 945 }, 946 947 /** 948 * @param {!Object} frameOrError 949 */ 950 _pushFrame: function(frameOrError) 951 { 952 if (this._frames.length >= 100) 953 this._frames.splice(0, 10); 954 this._frames.push(frameOrError); 955 }, 956 957 __proto__: WebInspector.Object.prototype 958 } 959