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 = {};
64 WebInspector.NetworkRequest.Events = {
65 FinishedLoading: "FinishedLoading",
66 TimingChanged: "TimingChanged",
67 RequestHeadersChanged: "RequestHeadersChanged",
68 ResponseHeadersChanged: "ResponseHeadersChanged",
72 WebInspector.NetworkRequest.InitiatorType = {
79 /** @typedef {!{name: string, value: string}} */
80 WebInspector.NetworkRequest.NameValue;
82 WebInspector.NetworkRequest.prototype = {
84 * @return {!NetworkAgent.RequestId}
88 return this._requestId;
91 set requestId(requestId)
93 this._requestId = requestId;
110 this._parsedURL = new WebInspector.ParsedURL(x);
111 delete this._queryString;
112 delete this._parsedQueryParameters;
122 return this._documentURL;
127 return this._parsedURL;
131 * @return {!PageAgent.FrameId}
135 return this._frameId;
139 * @return {!NetworkAgent.LoaderId}
143 return this._loaderId;
151 return this._startTime || -1;
162 get responseReceivedTime()
164 return this._responseReceivedTime || -1;
167 set responseReceivedTime(x)
169 this._responseReceivedTime = x;
177 return this._endTime || -1;
182 if (this.timing && this.timing.requestTime) {
183 // Check against accurate responseReceivedTime.
184 this._endTime = Math.max(x, this.responseReceivedTime);
186 // Prefer endTime since it might be from the network stack.
188 if (this._responseReceivedTime > x)
189 this._responseReceivedTime = x;
198 if (this._endTime === -1 || this._startTime === -1)
200 return this._endTime - this._startTime;
208 if (this._responseReceivedTime === -1 || this._startTime === -1)
210 return this._responseReceivedTime - this._startTime;
218 return this._resourceSize || 0;
223 this._resourceSize = x;
231 if (typeof this._transferSize === "number")
232 return this._transferSize;
233 if (this.statusCode === 304) // Not modified
234 return this.responseHeadersSize;
237 // If we did not receive actual transfer size from network
238 // stack, we prefer using Content-Length over resourceSize as
239 // resourceSize may differ from actual transfer size if platform's
240 // network stack performed decoding (e.g. gzip decompression).
241 // The Content-Length, though, is expected to come from raw
242 // response headers and will reflect actual transfer length.
243 // This won't work for chunked content encoding, so fall back to
244 // resourceSize when we don't have Content-Length. This still won't
245 // work for chunks with non-trivial encodings. We need a way to
246 // get actual transfer size from the network stack.
247 var bodySize = Number(this.responseHeaderValue("Content-Length") || this.resourceSize);
248 return this.responseHeadersSize + bodySize;
254 increaseTransferSize: function(x)
256 this._transferSize = (this._transferSize || 0) + x;
264 return this._finished;
269 if (this._finished === x)
275 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.FinishedLoading, this);
276 if (this._pendingContentCallbacks.length)
277 this._innerRequestContent();
299 return this._canceled;
312 return !!this._cached && !this._transferSize;
323 * @return {!NetworkAgent.ResourceTiming|undefined}
332 if (x && !this._cached) {
333 // Take startTime and responseReceivedTime from timing data for better accuracy.
334 // Timing's requestTime is a baseline in seconds, rest of the numbers there are ticks in millis.
335 this._startTime = x.requestTime;
336 this._responseReceivedTime = x.requestTime + x.receiveHeadersEnd / 1000.0;
339 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.TimingChanged, this);
348 return this._mimeType;
361 return this._parsedURL.displayName;
371 this._parseNameAndPathFromURL();
382 this._parseNameAndPathFromURL();
386 _parseNameAndPathFromURL: function()
388 if (this._parsedURL.isDataURL()) {
389 this._name = this._parsedURL.dataURLDisplayName();
391 } else if (this._parsedURL.isAboutBlank()) {
392 this._name = this._parsedURL.url;
395 this._path = this._parsedURL.host + this._parsedURL.folderPathComponents;
396 this._path = this._path.trimURL(WebInspector.inspectedPageDomain ? WebInspector.inspectedPageDomain : "");
397 if (this._parsedURL.lastPathComponent || this._parsedURL.queryParams)
398 this._name = this._parsedURL.lastPathComponent + (this._parsedURL.queryParams ? "?" + this._parsedURL.queryParams : "");
399 else if (this._parsedURL.folderPathComponents) {
400 this._name = this._parsedURL.folderPathComponents.substring(this._parsedURL.folderPathComponents.lastIndexOf("/") + 1) + "/";
401 this._path = this._path.substring(0, this._path.lastIndexOf("/"));
403 this._name = this._parsedURL.host;
414 var path = this._parsedURL.path;
415 var indexOfQuery = path.indexOf("?");
416 if (indexOfQuery !== -1)
417 path = path.substring(0, indexOfQuery);
418 var lastSlashIndex = path.lastIndexOf("/");
419 return lastSlashIndex !== -1 ? path.substring(0, lastSlashIndex) : "";
423 * @return {!WebInspector.ResourceType}
440 return this._parsedURL.host;
448 return this._parsedURL.scheme;
452 * @return {?WebInspector.NetworkRequest}
456 if (this.redirects && this.redirects.length > 0)
457 return this.redirects[this.redirects.length - 1];
458 return this._redirectSource;
461 set redirectSource(x)
463 this._redirectSource = x;
464 delete this._initiatorInfo;
468 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
470 requestHeaders: function()
472 return this._requestHeaders || [];
476 * @param {!Array.<!WebInspector.NetworkRequest.NameValue>} headers
478 setRequestHeaders: function(headers)
480 this._requestHeaders = headers;
481 delete this._requestCookies;
483 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RequestHeadersChanged);
487 * @return {string|undefined}
489 requestHeadersText: function()
491 return this._requestHeadersText;
495 * @param {string} text
497 setRequestHeadersText: function(text)
499 this._requestHeadersText = text;
501 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RequestHeadersChanged);
505 * @param {string} headerName
506 * @return {string|undefined}
508 requestHeaderValue: function(headerName)
510 return this._headerValue(this.requestHeaders(), headerName);
514 * @return {!Array.<!WebInspector.Cookie>}
518 if (!this._requestCookies)
519 this._requestCookies = WebInspector.CookieParser.parseCookie(this.requestHeaderValue("Cookie"));
520 return this._requestCookies;
524 * @return {string|undefined}
526 get requestFormData()
528 return this._requestFormData;
531 set requestFormData(x)
533 this._requestFormData = x;
534 delete this._parsedFormParameters;
538 * @return {string|undefined}
540 requestHttpVersion: function()
542 var headersText = this.requestHeadersText();
545 var firstLine = headersText.split(/\r\n/)[0];
546 var match = firstLine.match(/(HTTP\/\d+\.\d+)$/);
547 return match ? match[1] : undefined;
551 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
553 get responseHeaders()
555 return this._responseHeaders || [];
558 set responseHeaders(x)
560 this._responseHeaders = x;
561 delete this._sortedResponseHeaders;
562 delete this._responseCookies;
563 this._responseHeaderValues = {};
565 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.ResponseHeadersChanged);
571 get responseHeadersText()
573 if (typeof this._responseHeadersText === "undefined") {
574 this._responseHeadersText = "HTTP/1.1 " + this.statusCode + " " + this.statusText + "\r\n";
575 for (var i = 0; i < this.responseHeaders.length; ++i)
576 this._responseHeadersText += this.responseHeaders[i].name + ": " + this.responseHeaders[i].value + "\r\n";
578 return this._responseHeadersText;
581 set responseHeadersText(x)
583 this._responseHeadersText = x;
585 this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.ResponseHeadersChanged);
591 get responseHeadersSize()
593 return this.responseHeadersText.length;
597 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
599 get sortedResponseHeaders()
601 if (this._sortedResponseHeaders !== undefined)
602 return this._sortedResponseHeaders;
604 this._sortedResponseHeaders = this.responseHeaders.slice();
605 this._sortedResponseHeaders.sort(function(a, b) { return a.name.toLowerCase().compareTo(b.name.toLowerCase()); });
606 return this._sortedResponseHeaders;
610 * @param {string} headerName
611 * @return {string|undefined}
613 responseHeaderValue: function(headerName)
615 var value = this._responseHeaderValues[headerName];
616 if (value === undefined) {
617 value = this._headerValue(this.responseHeaders, headerName);
618 this._responseHeaderValues[headerName] = (value !== undefined) ? value : null;
620 return (value !== null) ? value : undefined;
624 * @return {!Array.<!WebInspector.Cookie>}
626 get responseCookies()
628 if (!this._responseCookies)
629 this._responseCookies = WebInspector.CookieParser.parseSetCookie(this.responseHeaderValue("Set-Cookie"));
630 return this._responseCookies;
636 queryString: function()
638 if (this._queryString !== undefined)
639 return this._queryString;
641 var queryString = null;
643 var questionMarkPosition = url.indexOf("?");
644 if (questionMarkPosition !== -1) {
645 queryString = url.substring(questionMarkPosition + 1);
646 var hashSignPosition = queryString.indexOf("#");
647 if (hashSignPosition !== -1)
648 queryString = queryString.substring(0, hashSignPosition);
650 this._queryString = queryString;
651 return this._queryString;
655 * @return {?Array.<!WebInspector.NetworkRequest.NameValue>}
657 get queryParameters()
659 if (this._parsedQueryParameters)
660 return this._parsedQueryParameters;
661 var queryString = this.queryString();
664 this._parsedQueryParameters = this._parseParameters(queryString);
665 return this._parsedQueryParameters;
669 * @return {?Array.<!WebInspector.NetworkRequest.NameValue>}
673 if (this._parsedFormParameters)
674 return this._parsedFormParameters;
675 if (!this.requestFormData)
677 var requestContentType = this.requestContentType();
678 if (!requestContentType || !requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i))
680 this._parsedFormParameters = this._parseParameters(this.requestFormData);
681 return this._parsedFormParameters;
685 * @return {string|undefined}
687 get responseHttpVersion()
689 var match = this.responseHeadersText.match(/^(HTTP\/\d+\.\d+)/);
690 return match ? match[1] : undefined;
694 * @param {string} queryString
695 * @return {!Array.<!WebInspector.NetworkRequest.NameValue>}
697 _parseParameters: function(queryString)
699 function parseNameValue(pair)
701 var splitPair = pair.split("=", 2);
702 return {name: splitPair[0], value: splitPair[1] || ""};
704 return queryString.split("&").map(parseNameValue);
708 * @param {!Array.<!WebInspector.NetworkRequest.NameValue>} headers
709 * @param {string} headerName
710 * @return {string|undefined}
712 _headerValue: function(headers, headerName)
714 headerName = headerName.toLowerCase();
717 for (var i = 0; i < headers.length; ++i) {
718 if (headers[i].name.toLowerCase() === headerName)
719 values.push(headers[i].value);
723 // Set-Cookie values should be separated by '\n', not comma, otherwise cookies could not be parsed.
724 if (headerName === "set-cookie")
725 return values.join("\n");
726 return values.join(", ");
730 * @return {?string|undefined}
734 return this._content;
742 return this._contentEncoded;
748 contentURL: function()
754 * @return {!WebInspector.ResourceType}
756 contentType: function()
762 * @param {function(?string)} callback
764 requestContent: function(callback)
766 // We do not support content retrieval for WebSockets at the moment.
767 // Since WebSockets are potentially long-living, fail requests immediately
768 // to prevent caller blocking until resource is marked as finished.
769 if (this.type === WebInspector.resourceTypes.WebSocket) {
773 if (typeof this._content !== "undefined") {
774 callback(this.content || null);
777 this._pendingContentCallbacks.push(callback);
779 this._innerRequestContent();
783 * @param {string} query
784 * @param {boolean} caseSensitive
785 * @param {boolean} isRegex
786 * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback
788 searchInContent: function(query, caseSensitive, isRegex, callback)
796 isHttpFamily: function()
798 return !!this.url.match(/^https?:/i);
802 * @return {string|undefined}
804 requestContentType: function()
806 return this.requestHeaderValue("Content-Type");
812 isPingRequest: function()
814 return "text/ping" === this.requestContentType();
820 hasErrorStatusCode: function()
822 return this.statusCode >= 400;
826 * @param {!Element} image
828 populateImageSource: function(image)
831 * @this {WebInspector.NetworkRequest}
832 * @param {?string} content
834 function onResourceContent(content)
836 var imageSrc = this.asDataURL();
837 if (imageSrc === null)
839 image.src = imageSrc;
842 this.requestContent(onResourceContent.bind(this));
848 asDataURL: function()
850 return WebInspector.contentAsDataURL(this._content, this.mimeType, this._contentEncoded);
853 _innerRequestContent: function()
855 if (this._contentRequested)
857 this._contentRequested = true;
860 * @param {?Protocol.Error} error
861 * @param {string} content
862 * @param {boolean} contentEncoded
863 * @this {WebInspector.NetworkRequest}
865 function onResourceContent(error, content, contentEncoded)
867 this._content = error ? null : content;
868 this._contentEncoded = contentEncoded;
869 var callbacks = this._pendingContentCallbacks.slice();
870 for (var i = 0; i < callbacks.length; ++i)
871 callbacks[i](this._content);
872 this._pendingContentCallbacks.length = 0;
873 delete this._contentRequested;
875 NetworkAgent.getResponseBody(this._requestId, onResourceContent.bind(this));
879 * @return {!{type: !WebInspector.NetworkRequest.InitiatorType, url: string, source: string, lineNumber: number, columnNumber: number}}
881 initiatorInfo: function()
883 if (this._initiatorInfo)
884 return this._initiatorInfo;
886 var type = WebInspector.NetworkRequest.InitiatorType.Other;
888 var lineNumber = -Infinity;
889 var columnNumber = -Infinity;
891 if (this.redirectSource) {
892 type = WebInspector.NetworkRequest.InitiatorType.Redirect;
893 url = this.redirectSource.url;
894 } else if (this.initiator) {
895 if (this.initiator.type === NetworkAgent.InitiatorType.Parser) {
896 type = WebInspector.NetworkRequest.InitiatorType.Parser;
897 url = this.initiator.url;
898 lineNumber = this.initiator.lineNumber;
899 } else if (this.initiator.type === NetworkAgent.InitiatorType.Script) {
900 var topFrame = this.initiator.stackTrace[0];
902 type = WebInspector.NetworkRequest.InitiatorType.Script;
904 lineNumber = topFrame.lineNumber;
905 columnNumber = topFrame.columnNumber;
910 this._initiatorInfo = {type: type, url: url, source: WebInspector.displayNameForURL(url), lineNumber: lineNumber, columnNumber: columnNumber};
911 return this._initiatorInfo;
915 * @return {!Array.<!Object>}
923 * @param {number} position
924 * @return {!Object|undefined}
926 frame: function(position)
928 return this._frames[position];
932 * @param {string} errorMessage
933 * @param {number} time
935 addFrameError: function(errorMessage, time)
937 this._pushFrame({errorMessage: errorMessage, time: time});
941 * @param {!NetworkAgent.WebSocketFrame} response
942 * @param {number} time
943 * @param {boolean} sent
945 addFrame: function(response, time, sent)
947 response.time = time;
949 response.sent = sent;
950 this._pushFrame(response);
954 * @param {!Object} frameOrError
956 _pushFrame: function(frameOrError)
958 if (this._frames.length >= 100)
959 this._frames.splice(0, 10);
960 this._frames.push(frameOrError);
963 __proto__: WebInspector.Object.prototype