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.Object}
34 * @implements {WebInspector.ContentProvider}
35 * @param {!NetworkAgent.RequestId} requestId
37 * @param {string} documentURL
38 * @param {!PageAgent.FrameId} frameId
39 * @param {!NetworkAgent.LoaderId} loaderId
41 WebInspector.NetworkRequest = function(requestId, url, documentURL, frameId, loaderId)
43 this._requestId = requestId;
45 this._documentURL = documentURL;
46 this._frameId = frameId;
47 this._loaderId = loaderId;
53 this.requestMethod = "";
56 this._type = WebInspector.resourceTypes.Other;
57 this._contentEncoded = false;
58 this._pendingContentCallbacks = [];
61 this._responseHeaderValues = {};
63 this._remoteAddress = "";
66 WebInspector.NetworkRequest.Events = {
67 FinishedLoading: "FinishedLoading",
68 TimingChanged: "TimingChanged",
69 RemoteAddressChanged: "RemoteAddressChanged",
70 RequestHeadersChanged: "RequestHeadersChanged",
71 ResponseHeadersChanged: "ResponseHeadersChanged",
75 WebInspector.NetworkRequest.InitiatorType = {
82 /** @typedef {!{name: string, value: string}} */
83 WebInspector.NetworkRequest.NameValue;
85 WebInspector.NetworkRequest.prototype = {
87 * @return {!NetworkAgent.RequestId}
91 return this._requestId;
94 set requestId(requestId)
96 this._requestId = requestId;
113 this._parsedURL = new WebInspector.ParsedURL(x);
114 delete this._queryString;
115 delete this._parsedQueryParameters;
125 return this._documentURL;
130 return this._parsedURL;
134 * @return {!PageAgent.FrameId}
138 return this._frameId;
142 * @return {!NetworkAgent.LoaderId}
146 return this._loaderId;
151 * @param {number} port
153 setRemoteAddress: function(ip, port)
155 this._remoteAddress = ip + ":" + port;
156 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RemoteAddressChanged, this);
162 remoteAddress: function()
164 return this._remoteAddress;
172 return this._startTime || -1;
183 get responseReceivedTime()
185 return this._responseReceivedTime || -1;
188 set responseReceivedTime(x)
190 this._responseReceivedTime = x;
198 return this._endTime || -1;
203 if (this.timing && this.timing.requestTime) {
204 // Check against accurate responseReceivedTime.
205 this._endTime = Math.max(x, this.responseReceivedTime);
207 // Prefer endTime since it might be from the network stack.
209 if (this._responseReceivedTime > x)
210 this._responseReceivedTime = x;
212 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.TimingChanged, this);
220 if (this._endTime === -1 || this._startTime === -1)
222 return this._endTime - this._startTime;
230 if (this._responseReceivedTime === -1 || this._startTime === -1)
232 return this._responseReceivedTime - this._startTime;
240 return this._resourceSize || 0;
245 this._resourceSize = x;
253 return this._transferSize || 0;
259 increaseTransferSize: function(x)
261 this._transferSize = (this._transferSize || 0) + x;
267 setTransferSize: function(x)
269 this._transferSize = x;
277 return this._finished;
282 if (this._finished === x)
288 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.FinishedLoading, this);
289 if (this._pendingContentCallbacks.length)
290 this._innerRequestContent();
312 return this._canceled;
325 return !!this._cached && !this._transferSize;
336 * @return {!NetworkAgent.ResourceTiming|undefined}
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;
352 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.TimingChanged, this);
361 return this._mimeType;
374 return this._parsedURL.displayName;
384 this._parseNameAndPathFromURL();
395 this._parseNameAndPathFromURL();
399 _parseNameAndPathFromURL: function()
401 if (this._parsedURL.isDataURL()) {
402 this._name = this._parsedURL.dataURLDisplayName();
404 } else if (this._parsedURL.isAboutBlank()) {
405 this._name = this._parsedURL.url;
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("/"));
416 this._name = this._parsedURL.host;
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) : "";
436 * @return {!WebInspector.ResourceType}
453 return this._parsedURL.host;
461 return this._parsedURL.scheme;
465 * @return {?WebInspector.NetworkRequest}
469 if (this.redirects && this.redirects.length > 0)
470 return this.redirects[this.redirects.length - 1];
471 return this._redirectSource;
474 set redirectSource(x)
476 this._redirectSource = x;
477 delete this._initiatorInfo;
481 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
483 requestHeaders: function()
485 return this._requestHeaders || [];
489 * @param {!Array.<!WebInspector.NetworkRequest.NameValue>} headers
491 setRequestHeaders: function(headers)
493 this._requestHeaders = headers;
494 delete this._requestCookies;
496 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RequestHeadersChanged);
500 * @return {string|undefined}
502 requestHeadersText: function()
504 return this._requestHeadersText;
508 * @param {string} text
510 setRequestHeadersText: function(text)
512 this._requestHeadersText = text;
514 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RequestHeadersChanged);
518 * @param {string} headerName
519 * @return {string|undefined}
521 requestHeaderValue: function(headerName)
523 return this._headerValue(this.requestHeaders(), headerName);
527 * @return {!Array.<!WebInspector.Cookie>}
531 if (!this._requestCookies)
532 this._requestCookies = WebInspector.CookieParser.parseCookie(this.requestHeaderValue("Cookie"));
533 return this._requestCookies;
537 * @return {string|undefined}
539 get requestFormData()
541 return this._requestFormData;
544 set requestFormData(x)
546 this._requestFormData = x;
547 delete this._parsedFormParameters;
551 * @return {string|undefined}
553 requestHttpVersion: function()
555 var headersText = this.requestHeadersText();
558 return this.requestHeaderValue(":version");
560 var firstLine = headersText.split(/\r\n/)[0];
561 var match = firstLine.match(/(HTTP\/\d+\.\d+)$/);
562 return match ? match[1] : undefined;
566 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
568 get responseHeaders()
570 return this._responseHeaders || [];
573 set responseHeaders(x)
575 this._responseHeaders = x;
576 delete this._sortedResponseHeaders;
577 delete this._responseCookies;
578 this._responseHeaderValues = {};
580 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.ResponseHeadersChanged);
586 get responseHeadersText()
588 return this._responseHeadersText;
591 set responseHeadersText(x)
593 this._responseHeadersText = x;
595 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.ResponseHeadersChanged);
599 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
601 get sortedResponseHeaders()
603 if (this._sortedResponseHeaders !== undefined)
604 return this._sortedResponseHeaders;
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;
612 * @param {string} headerName
613 * @return {string|undefined}
615 responseHeaderValue: function(headerName)
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;
622 return (value !== null) ? value : undefined;
626 * @return {!Array.<!WebInspector.Cookie>}
628 get responseCookies()
630 if (!this._responseCookies)
631 this._responseCookies = WebInspector.CookieParser.parseSetCookie(this.responseHeaderValue("Set-Cookie"));
632 return this._responseCookies;
638 queryString: function()
640 if (this._queryString !== undefined)
641 return this._queryString;
643 var queryString = null;
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);
652 this._queryString = queryString;
653 return this._queryString;
657 * @return {?Array.<!WebInspector.NetworkRequest.NameValue>}
659 get queryParameters()
661 if (this._parsedQueryParameters)
662 return this._parsedQueryParameters;
663 var queryString = this.queryString();
666 this._parsedQueryParameters = this._parseParameters(queryString);
667 return this._parsedQueryParameters;
671 * @return {?Array.<!WebInspector.NetworkRequest.NameValue>}
675 if (this._parsedFormParameters)
676 return this._parsedFormParameters;
677 if (!this.requestFormData)
679 var requestContentType = this.requestContentType();
680 if (!requestContentType || !requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i))
682 this._parsedFormParameters = this._parseParameters(this.requestFormData);
683 return this._parsedFormParameters;
687 * @return {string|undefined}
689 get responseHttpVersion()
691 var headersText = this._responseHeadersText;
694 return this.responseHeaderValue(":version");
696 var match = headersText.match(/^(HTTP\/\d+\.\d+)/);
697 return match ? match[1] : undefined;
701 * @param {string} queryString
702 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
704 _parseParameters: function(queryString)
706 function parseNameValue(pair)
708 var splitPair = pair.split("=", 2);
709 return {name: splitPair[0], value: splitPair[1] || ""};
711 return queryString.split("&").map(parseNameValue);
715 * @param {!Array.<!WebInspector.NetworkRequest.NameValue>} headers
716 * @param {string} headerName
717 * @return {string|undefined}
719 _headerValue: function(headers, headerName)
721 headerName = headerName.toLowerCase();
724 for (var i = 0; i < headers.length; ++i) {
725 if (headers[i].name.toLowerCase() === headerName)
726 values.push(headers[i].value);
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(", ");
737 * @return {?string|undefined}
741 return this._content;
745 * @return {?Protocol.Error|undefined}
747 contentError: function()
749 return this._contentError;
757 return this._contentEncoded;
763 contentURL: function()
769 * @return {!WebInspector.ResourceType}
771 contentType: function()
777 * @param {function(?string)} callback
779 requestContent: function(callback)
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) {
788 if (typeof this._content !== "undefined") {
789 callback(this.content || null);
792 this._pendingContentCallbacks.push(callback);
794 this._innerRequestContent();
798 * @param {string} query
799 * @param {boolean} caseSensitive
800 * @param {boolean} isRegex
801 * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback
803 searchInContent: function(query, caseSensitive, isRegex, callback)
811 isHttpFamily: function()
813 return !!this.url.match(/^https?:/i);
817 * @return {string|undefined}
819 requestContentType: function()
821 return this.requestHeaderValue("Content-Type");
827 isPingRequest: function()
829 return "text/ping" === this.requestContentType();
835 hasErrorStatusCode: function()
837 return this.statusCode >= 400;
841 * @param {!Element} image
843 populateImageSource: function(image)
846 * @this {WebInspector.NetworkRequest}
847 * @param {?string} content
849 function onResourceContent(content)
851 var imageSrc = this.asDataURL();
852 if (imageSrc === null)
854 image.src = imageSrc;
857 this.requestContent(onResourceContent.bind(this));
863 asDataURL: function()
865 return WebInspector.contentAsDataURL(this._content, this.mimeType, this._contentEncoded);
868 _innerRequestContent: function()
870 if (this._contentRequested)
872 this._contentRequested = true;
875 * @param {?Protocol.Error} error
876 * @param {string} content
877 * @param {boolean} contentEncoded
878 * @this {WebInspector.NetworkRequest}
880 function onResourceContent(error, content, contentEncoded)
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;
891 NetworkAgent.getResponseBody(this._requestId, onResourceContent.bind(this));
895 * @return {!{type: !WebInspector.NetworkRequest.InitiatorType, url: string, source: string, lineNumber: number, columnNumber: number}}
897 initiatorInfo: function()
899 if (this._initiatorInfo)
900 return this._initiatorInfo;
902 var type = WebInspector.NetworkRequest.InitiatorType.Other;
904 var lineNumber = -Infinity;
905 var columnNumber = -Infinity;
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];
918 type = WebInspector.NetworkRequest.InitiatorType.Script;
920 lineNumber = topFrame.lineNumber;
921 columnNumber = topFrame.columnNumber;
926 this._initiatorInfo = {type: type, url: url, source: WebInspector.displayNameForURL(url), lineNumber: lineNumber, columnNumber: columnNumber};
927 return this._initiatorInfo;
931 * @return {!Array.<!Object>}
939 * @param {number} position
940 * @return {!Object|undefined}
942 frame: function(position)
944 return this._frames[position];
948 * @param {string} errorMessage
949 * @param {number} time
951 addFrameError: function(errorMessage, time)
953 this._pushFrame({errorMessage: errorMessage, time: time});
957 * @param {!NetworkAgent.WebSocketFrame} response
958 * @param {number} time
959 * @param {boolean} sent
961 addFrame: function(response, time, sent)
963 response.time = time;
965 response.sent = sent;
966 this._pushFrame(response);
970 * @param {!Object} frameOrError
972 _pushFrame: function(frameOrError)
974 if (this._frames.length >= 100)
975 this._frames.splice(0, 10);
976 this._frames.push(frameOrError);
979 __proto__: WebInspector.Object.prototype