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