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