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