2 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
33 * @extends {WebInspector.SDKObject}
34 * @implements {WebInspector.ContentProvider}
35 * @param {!NetworkAgent.RequestId} requestId
36 * @param {!WebInspector.Target} target
38 * @param {string} documentURL
39 * @param {!PageAgent.FrameId} frameId
40 * @param {!NetworkAgent.LoaderId} loaderId
41 * @param {?NetworkAgent.Initiator} initiator
43 WebInspector.NetworkRequest = function(target, requestId, url, documentURL, frameId, loaderId, initiator)
45 WebInspector.SDKObject.call(this, target);
47 this._requestId = requestId;
49 this._documentURL = documentURL;
50 this._frameId = frameId;
51 this._loaderId = loaderId;
52 /** @type {?NetworkAgent.Initiator} */
53 this._initiator = initiator;
59 this.requestMethod = "";
62 this._type = WebInspector.resourceTypes.Other;
63 this._contentEncoded = false;
64 this._pendingContentCallbacks = [];
65 /** @type {!Array.<!WebInspector.NetworkRequest.WebSocketFrame>} */
68 this._responseHeaderValues = {};
70 this._remoteAddress = "";
73 this.connectionId = "0";
76 WebInspector.NetworkRequest.Events = {
77 FinishedLoading: "FinishedLoading",
78 TimingChanged: "TimingChanged",
79 RemoteAddressChanged: "RemoteAddressChanged",
80 RequestHeadersChanged: "RequestHeadersChanged",
81 ResponseHeadersChanged: "ResponseHeadersChanged",
85 WebInspector.NetworkRequest.InitiatorType = {
92 /** @typedef {!{name: string, value: string}} */
93 WebInspector.NetworkRequest.NameValue;
96 WebInspector.NetworkRequest.WebSocketFrameType = {
102 /** @typedef {!{type: WebInspector.NetworkRequest.WebSocketFrameType, time: number, text: string, opCode: number, mask: boolean}} */
103 WebInspector.NetworkRequest.WebSocketFrame;
105 WebInspector.NetworkRequest.prototype = {
107 * @param {!WebInspector.NetworkRequest} other
110 indentityCompare: function(other) {
111 if (this._requestId > other._requestId)
113 if (this._requestId < other._requestId)
119 * @return {!NetworkAgent.RequestId}
123 return this._requestId;
126 set requestId(requestId)
128 this._requestId = requestId;
145 this._parsedURL = new WebInspector.ParsedURL(x);
146 delete this._queryString;
147 delete this._parsedQueryParameters;
157 return this._documentURL;
162 return this._parsedURL;
166 * @return {!PageAgent.FrameId}
170 return this._frameId;
174 * @return {!NetworkAgent.LoaderId}
178 return this._loaderId;
183 * @param {number} port
185 setRemoteAddress: function(ip, port)
187 if (ip.indexOf(":") !== -1)
189 this._remoteAddress = ip + ":" + port;
190 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RemoteAddressChanged, this);
196 remoteAddress: function()
198 return this._remoteAddress;
206 return this._startTime || -1;
217 get responseReceivedTime()
219 return this._responseReceivedTime || -1;
222 set responseReceivedTime(x)
224 this._responseReceivedTime = x;
232 return this._endTime || -1;
237 if (this.timing && this.timing.requestTime) {
238 // Check against accurate responseReceivedTime.
239 this._endTime = Math.max(x, this.responseReceivedTime);
241 // Prefer endTime since it might be from the network stack.
243 if (this._responseReceivedTime > x)
244 this._responseReceivedTime = x;
246 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.TimingChanged, this);
254 if (this._endTime === -1 || this._startTime === -1)
256 return this._endTime - this._startTime;
264 if (this._responseReceivedTime === -1 || this._startTime === -1)
266 return this._responseReceivedTime - this._startTime;
274 return this._resourceSize || 0;
279 this._resourceSize = x;
287 return this._transferSize || 0;
293 increaseTransferSize: function(x)
295 this._transferSize = (this._transferSize || 0) + x;
301 setTransferSize: function(x)
303 this._transferSize = x;
311 return this._finished;
316 if (this._finished === x)
322 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.FinishedLoading, this);
323 if (this._pendingContentCallbacks.length)
324 this._innerRequestContent();
346 return this._canceled;
359 return !!this._cached && !this._transferSize;
372 get fetchedViaServiceWorker()
374 return this._fetchedViaServiceWorker;
377 set fetchedViaServiceWorker(x)
379 this._fetchedViaServiceWorker = x;
383 * @return {!NetworkAgent.ResourceTiming|undefined}
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;
399 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.TimingChanged, this);
408 return this._mimeType;
421 return this._parsedURL.displayName;
431 this._parseNameAndPathFromURL();
442 this._parseNameAndPathFromURL();
446 _parseNameAndPathFromURL: function()
448 if (this._parsedURL.isDataURL()) {
449 this._name = this._parsedURL.dataURLDisplayName();
451 } else if (this._parsedURL.isAboutBlank()) {
452 this._name = this._parsedURL.url;
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("/"));
463 this._name = this._parsedURL.host;
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) : "";
483 * @return {!WebInspector.ResourceType}
500 return this._parsedURL.host;
508 return this._parsedURL.scheme;
512 * @return {?WebInspector.NetworkRequest}
516 if (this.redirects && this.redirects.length > 0)
517 return this.redirects[this.redirects.length - 1];
518 return this._redirectSource;
521 set redirectSource(x)
523 this._redirectSource = x;
524 delete this._initiatorInfo;
528 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
530 requestHeaders: function()
532 return this._requestHeaders || [];
536 * @param {!Array.<!WebInspector.NetworkRequest.NameValue>} headers
538 setRequestHeaders: function(headers)
540 this._requestHeaders = headers;
541 delete this._requestCookies;
543 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RequestHeadersChanged);
547 * @return {string|undefined}
549 requestHeadersText: function()
551 return this._requestHeadersText;
555 * @param {string} text
557 setRequestHeadersText: function(text)
559 this._requestHeadersText = text;
561 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RequestHeadersChanged);
565 * @param {string} headerName
566 * @return {string|undefined}
568 requestHeaderValue: function(headerName)
570 return this._headerValue(this.requestHeaders(), headerName);
574 * @return {!Array.<!WebInspector.Cookie>}
578 if (!this._requestCookies)
579 this._requestCookies = WebInspector.CookieParser.parseCookie(this.requestHeaderValue("Cookie"));
580 return this._requestCookies;
584 * @return {string|undefined}
586 get requestFormData()
588 return this._requestFormData;
591 set requestFormData(x)
593 this._requestFormData = x;
594 delete this._parsedFormParameters;
600 requestHttpVersion: function()
602 var headersText = this.requestHeadersText();
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";
611 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
613 get responseHeaders()
615 return this._responseHeaders || [];
618 set responseHeaders(x)
620 this._responseHeaders = x;
621 delete this._sortedResponseHeaders;
622 delete this._responseCookies;
623 this._responseHeaderValues = {};
625 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.ResponseHeadersChanged);
631 get responseHeadersText()
633 return this._responseHeadersText;
636 set responseHeadersText(x)
638 this._responseHeadersText = x;
640 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.ResponseHeadersChanged);
644 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
646 get sortedResponseHeaders()
648 if (this._sortedResponseHeaders !== undefined)
649 return this._sortedResponseHeaders;
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;
657 * @param {string} headerName
658 * @return {string|undefined}
660 responseHeaderValue: function(headerName)
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;
667 return (value !== null) ? value : undefined;
671 * @return {!Array.<!WebInspector.Cookie>}
673 get responseCookies()
675 if (!this._responseCookies)
676 this._responseCookies = WebInspector.CookieParser.parseSetCookie(this.responseHeaderValue("Set-Cookie"));
677 return this._responseCookies;
683 queryString: function()
685 if (this._queryString !== undefined)
686 return this._queryString;
688 var queryString = null;
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);
697 this._queryString = queryString;
698 return this._queryString;
702 * @return {?Array.<!WebInspector.NetworkRequest.NameValue>}
704 get queryParameters()
706 if (this._parsedQueryParameters)
707 return this._parsedQueryParameters;
708 var queryString = this.queryString();
711 this._parsedQueryParameters = this._parseParameters(queryString);
712 return this._parsedQueryParameters;
716 * @return {?Array.<!WebInspector.NetworkRequest.NameValue>}
720 if (this._parsedFormParameters)
721 return this._parsedFormParameters;
722 if (!this.requestFormData)
724 var requestContentType = this.requestContentType();
725 if (!requestContentType || !requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i))
727 this._parsedFormParameters = this._parseParameters(this.requestFormData);
728 return this._parsedFormParameters;
734 responseHttpVersion: function()
736 var headersText = this._responseHeadersText;
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";
745 * @param {string} queryString
746 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
748 _parseParameters: function(queryString)
750 function parseNameValue(pair)
752 var position = pair.indexOf("=");
754 return {name: pair, value: ""};
756 return {name: pair.substring(0, position), value: pair.substring(position + 1)};
758 return queryString.split("&").map(parseNameValue);
762 * @param {!Array.<!WebInspector.NetworkRequest.NameValue>} headers
763 * @param {string} headerName
764 * @return {string|undefined}
766 _headerValue: function(headers, headerName)
768 headerName = headerName.toLowerCase();
771 for (var i = 0; i < headers.length; ++i) {
772 if (headers[i].name.toLowerCase() === headerName)
773 values.push(headers[i].value);
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(", ");
784 * @return {?string|undefined}
788 return this._content;
792 * @return {?Protocol.Error|undefined}
794 contentError: function()
796 return this._contentError;
804 return this._contentEncoded;
810 contentURL: function()
816 * @return {!WebInspector.ResourceType}
818 contentType: function()
824 * @param {function(?string)} callback
826 requestContent: function(callback)
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) {
835 if (typeof this._content !== "undefined") {
836 callback(this.content || null);
839 this._pendingContentCallbacks.push(callback);
841 this._innerRequestContent();
845 * @param {string} query
846 * @param {boolean} caseSensitive
847 * @param {boolean} isRegex
848 * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback
850 searchInContent: function(query, caseSensitive, isRegex, callback)
858 isHttpFamily: function()
860 return !!this.url.match(/^https?:/i);
864 * @return {string|undefined}
866 requestContentType: function()
868 return this.requestHeaderValue("Content-Type");
874 hasErrorStatusCode: function()
876 return this.statusCode >= 400;
880 * @param {!Element} image
882 populateImageSource: function(image)
885 * @this {WebInspector.NetworkRequest}
886 * @param {?string} content
888 function onResourceContent(content)
890 var imageSrc = this.asDataURL();
891 if (imageSrc === null)
893 image.src = imageSrc;
896 this.requestContent(onResourceContent.bind(this));
902 asDataURL: function()
904 return WebInspector.Resource.contentAsDataURL(this._content, this.mimeType, this._contentEncoded);
907 _innerRequestContent: function()
909 if (this._contentRequested)
911 this._contentRequested = true;
914 * @param {?Protocol.Error} error
915 * @param {string} content
916 * @param {boolean} contentEncoded
917 * @this {WebInspector.NetworkRequest}
919 function onResourceContent(error, content, contentEncoded)
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;
930 NetworkAgent.getResponseBody(this._requestId, onResourceContent.bind(this));
934 * @return {?NetworkAgent.Initiator}
936 initiator: function()
938 return this._initiator;
942 * @return {!{type: !WebInspector.NetworkRequest.InitiatorType, url: string, lineNumber: number, columnNumber: number}}
944 initiatorInfo: function()
946 if (this._initiatorInfo)
947 return this._initiatorInfo;
949 var type = WebInspector.NetworkRequest.InitiatorType.Other;
951 var lineNumber = -Infinity;
952 var columnNumber = -Infinity;
953 var initiator = this._initiator;
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];
966 type = WebInspector.NetworkRequest.InitiatorType.Script;
968 lineNumber = topFrame.lineNumber;
969 columnNumber = topFrame.columnNumber;
974 this._initiatorInfo = {type: type, url: url, lineNumber: lineNumber, columnNumber: columnNumber};
975 return this._initiatorInfo;
979 * @return {!Array.<!WebInspector.NetworkRequest.WebSocketFrame>}
987 * @param {string} errorMessage
988 * @param {number} time
990 addFrameError: function(errorMessage, time)
992 this._frames.push({ type: WebInspector.NetworkRequest.WebSocketFrameType.Error, text: errorMessage, time: time, opCode: -1, mask: false });
996 * @param {!NetworkAgent.WebSocketFrame} response
997 * @param {number} time
998 * @param {boolean} sent
1000 addFrame: function(response, time, sent)
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 });
1006 __proto__: WebInspector.SDKObject.prototype