/* * Copyright (C) 2012 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @constructor * @extends {WebInspector.Object} * @implements {WebInspector.ContentProvider} * @param {NetworkAgent.RequestId} requestId * @param {string} url * @param {string} documentURL * @param {PageAgent.FrameId} frameId * @param {NetworkAgent.LoaderId} loaderId */ WebInspector.NetworkRequest = function(requestId, url, documentURL, frameId, loaderId) { this._requestId = requestId; this.url = url; this._documentURL = documentURL; this._frameId = frameId; this._loaderId = loaderId; this._startTime = -1; this._endTime = -1; this.statusCode = 0; this.statusText = ""; this.requestMethod = ""; this.requestTime = 0; this.receiveHeadersEnd = 0; this._type = WebInspector.resourceTypes.Other; this._contentEncoded = false; this._pendingContentCallbacks = []; this._frames = []; this._responseHeaderValues = {}; } WebInspector.NetworkRequest.Events = { FinishedLoading: "FinishedLoading", TimingChanged: "TimingChanged", RequestHeadersChanged: "RequestHeadersChanged", ResponseHeadersChanged: "ResponseHeadersChanged", } /** @enum {string} */ WebInspector.NetworkRequest.InitiatorType = { Other: "other", Parser: "parser", Redirect: "redirect", Script: "script" } /** @typedef {{name: string, value: string}} */ WebInspector.NetworkRequest.NameValue; WebInspector.NetworkRequest.prototype = { /** * @return {NetworkAgent.RequestId} */ get requestId() { return this._requestId; }, set requestId(requestId) { this._requestId = requestId; }, /** * @return {string} */ get url() { return this._url; }, set url(x) { if (this._url === x) return; this._url = x; this._parsedURL = new WebInspector.ParsedURL(x); delete this._parsedQueryParameters; delete this._name; delete this._path; }, /** * @return {string} */ get documentURL() { return this._documentURL; }, get parsedURL() { return this._parsedURL; }, /** * @return {PageAgent.FrameId} */ get frameId() { return this._frameId; }, /** * @return {NetworkAgent.LoaderId} */ get loaderId() { return this._loaderId; }, /** * @return {number} */ get startTime() { return this._startTime || -1; }, set startTime(x) { this._startTime = x; }, /** * @return {number} */ get responseReceivedTime() { return this._responseReceivedTime || -1; }, set responseReceivedTime(x) { this._responseReceivedTime = x; }, /** * @return {number} */ get endTime() { return this._endTime || -1; }, set endTime(x) { if (this.timing && this.timing.requestTime) { // Check against accurate responseReceivedTime. this._endTime = Math.max(x, this.responseReceivedTime); } else { // Prefer endTime since it might be from the network stack. this._endTime = x; if (this._responseReceivedTime > x) this._responseReceivedTime = x; } }, /** * @return {number} */ get duration() { if (this._endTime === -1 || this._startTime === -1) return -1; return this._endTime - this._startTime; }, /** * @return {number} */ get latency() { if (this._responseReceivedTime === -1 || this._startTime === -1) return -1; return this._responseReceivedTime - this._startTime; }, /** * @return {number} */ get receiveDuration() { if (this._endTime === -1 || this._responseReceivedTime === -1) return -1; return this._endTime - this._responseReceivedTime; }, /** * @return {number} */ get resourceSize() { return this._resourceSize || 0; }, set resourceSize(x) { this._resourceSize = x; }, /** * @return {number} */ get transferSize() { if (typeof this._transferSize === "number") return this._transferSize; if (this.statusCode === 304) // Not modified return this.responseHeadersSize; if (this._cached) return 0; // If we did not receive actual transfer size from network // stack, we prefer using Content-Length over resourceSize as // resourceSize may differ from actual transfer size if platform's // network stack performed decoding (e.g. gzip decompression). // The Content-Length, though, is expected to come from raw // response headers and will reflect actual transfer length. // This won't work for chunked content encoding, so fall back to // resourceSize when we don't have Content-Length. This still won't // work for chunks with non-trivial encodings. We need a way to // get actual transfer size from the network stack. var bodySize = Number(this.responseHeaderValue("Content-Length") || this.resourceSize); return this.responseHeadersSize + bodySize; }, /** * @param {number} x */ increaseTransferSize: function(x) { this._transferSize = (this._transferSize || 0) + x; }, /** * @return {boolean} */ get finished() { return this._finished; }, set finished(x) { if (this._finished === x) return; this._finished = x; if (x) { this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.FinishedLoading, this); if (this._pendingContentCallbacks.length) this._innerRequestContent(); } }, /** * @return {boolean} */ get failed() { return this._failed; }, set failed(x) { this._failed = x; }, /** * @return {boolean} */ get canceled() { return this._canceled; }, set canceled(x) { this._canceled = x; }, /** * @return {boolean} */ get cached() { return this._cached && !this._transferSize; }, set cached(x) { this._cached = x; if (x) delete this._timing; }, /** * @return {NetworkAgent.ResourceTiming|undefined} */ get timing() { return this._timing; }, set timing(x) { if (x && !this._cached) { // Take startTime and responseReceivedTime from timing data for better accuracy. // Timing's requestTime is a baseline in seconds, rest of the numbers there are ticks in millis. this._startTime = x.requestTime; this._responseReceivedTime = x.requestTime + x.receiveHeadersEnd / 1000.0; this._timing = x; this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.TimingChanged, this); } }, /** * @return {string} */ get mimeType() { return this._mimeType; }, set mimeType(x) { this._mimeType = x; }, /** * @return {string} */ get displayName() { return this._parsedURL.displayName; }, name: function() { if (this._name) return this._name; this._parseNameAndPathFromURL(); return this._name; }, path: function() { if (this._path) return this._path; this._parseNameAndPathFromURL(); return this._path; }, _parseNameAndPathFromURL: function() { if (this._parsedURL.isDataURL()) { this._name = this._parsedURL.dataURLDisplayName(); this._path = ""; } else if (this._parsedURL.isAboutBlank()) { this._name = this._parsedURL.url; this._path = ""; } else { this._path = this._parsedURL.host + this._parsedURL.folderPathComponents; this._path = this._path.trimURL(WebInspector.inspectedPageDomain ? WebInspector.inspectedPageDomain : ""); if (this._parsedURL.lastPathComponent || this._parsedURL.queryParams) this._name = this._parsedURL.lastPathComponent + (this._parsedURL.queryParams ? "?" + this._parsedURL.queryParams : ""); else if (this._parsedURL.folderPathComponents) { this._name = this._parsedURL.folderPathComponents.substring(this._parsedURL.folderPathComponents.lastIndexOf("/") + 1) + "/"; this._path = this._path.substring(0, this._path.lastIndexOf("/")); } else { this._name = this._parsedURL.host; this._path = ""; } } }, /** * @return {string} */ get folder() { var path = this._parsedURL.path; var indexOfQuery = path.indexOf("?"); if (indexOfQuery !== -1) path = path.substring(0, indexOfQuery); var lastSlashIndex = path.lastIndexOf("/"); return lastSlashIndex !== -1 ? path.substring(0, lastSlashIndex) : ""; }, /** * @return {WebInspector.ResourceType} */ get type() { return this._type; }, set type(x) { this._type = x; }, /** * @return {string} */ get domain() { return this._parsedURL.host; }, /** * @return {string} */ get scheme() { return this._parsedURL.scheme; }, /** * @return {?WebInspector.NetworkRequest} */ get redirectSource() { if (this.redirects && this.redirects.length > 0) return this.redirects[this.redirects.length - 1]; return this._redirectSource; }, set redirectSource(x) { this._redirectSource = x; delete this._initiatorInfo; }, /** * @return {!Array.} */ get requestHeaders() { return this._requestHeaders || []; }, set requestHeaders(x) { this._requestHeaders = x; delete this._sortedRequestHeaders; delete this._requestCookies; this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RequestHeadersChanged); }, /** * @return {string} */ get requestHeadersText() { if (typeof this._requestHeadersText === "undefined") { this._requestHeadersText = this.requestMethod + " " + this.url + " HTTP/1.1\r\n"; for (var i = 0; i < this.requestHeaders.length; ++i) this._requestHeadersText += this.requestHeaders[i].name + ": " + this.requestHeaders[i].value + "\r\n"; } return this._requestHeadersText; }, set requestHeadersText(x) { this._requestHeadersText = x; this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.RequestHeadersChanged); }, /** * @return {number} */ get requestHeadersSize() { return this.requestHeadersText.length; }, /** * @return {!Array.} */ get sortedRequestHeaders() { if (this._sortedRequestHeaders !== undefined) return this._sortedRequestHeaders; this._sortedRequestHeaders = []; this._sortedRequestHeaders = this.requestHeaders.slice(); this._sortedRequestHeaders.sort(function(a,b) { return a.name.toLowerCase().compareTo(b.name.toLowerCase()) }); return this._sortedRequestHeaders; }, /** * @param {string} headerName * @return {string|undefined} */ requestHeaderValue: function(headerName) { return this._headerValue(this.requestHeaders, headerName); }, /** * @return {Array.} */ get requestCookies() { if (!this._requestCookies) this._requestCookies = WebInspector.CookieParser.parseCookie(this.requestHeaderValue("Cookie")); return this._requestCookies; }, /** * @return {string|undefined} */ get requestFormData() { return this._requestFormData; }, set requestFormData(x) { this._requestFormData = x; delete this._parsedFormParameters; }, /** * @return {string|undefined} */ get requestHttpVersion() { var firstLine = this.requestHeadersText.split(/\r\n/)[0]; var match = firstLine.match(/(HTTP\/\d+\.\d+)$/); return match ? match[1] : undefined; }, /** * @return {!Array.} */ get responseHeaders() { return this._responseHeaders || []; }, set responseHeaders(x) { this._responseHeaders = x; delete this._sortedResponseHeaders; delete this._responseCookies; this._responseHeaderValues = {}; this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.ResponseHeadersChanged); }, /** * @return {string} */ get responseHeadersText() { if (typeof this._responseHeadersText === "undefined") { this._responseHeadersText = "HTTP/1.1 " + this.statusCode + " " + this.statusText + "\r\n"; for (var i = 0; i < this.responseHeaders.length; ++i) this._responseHeadersText += this.responseHeaders[i].name + ": " + this.responseHeaders[i].value + "\r\n"; } return this._responseHeadersText; }, set responseHeadersText(x) { this._responseHeadersText = x; this.dispatchEventToListeners(WebInspector.NetworkRequest.Events.ResponseHeadersChanged); }, /** * @return {number} */ get responseHeadersSize() { return this.responseHeadersText.length; }, /** * @return {!Array.} */ get sortedResponseHeaders() { if (this._sortedResponseHeaders !== undefined) return this._sortedResponseHeaders; this._sortedResponseHeaders = []; this._sortedResponseHeaders = this.responseHeaders.slice(); this._sortedResponseHeaders.sort(function(a, b) { return a.name.toLowerCase().compareTo(b.name.toLowerCase()); }); return this._sortedResponseHeaders; }, /** * @param {string} headerName * @return {string|undefined} */ responseHeaderValue: function(headerName) { var value = this._responseHeaderValues[headerName]; if (value === undefined) { value = this._headerValue(this.responseHeaders, headerName); this._responseHeaderValues[headerName] = (value !== undefined) ? value : null; } return (value !== null) ? value : undefined; }, /** * @return {Array.} */ get responseCookies() { if (!this._responseCookies) this._responseCookies = WebInspector.CookieParser.parseSetCookie(this.responseHeaderValue("Set-Cookie")); return this._responseCookies; }, /** * @return {?string} */ queryString: function() { if (this._queryString) return this._queryString; var queryString = this.url.split("?", 2)[1]; if (!queryString) return null; this._queryString = queryString.split("#", 2)[0]; return this._queryString; }, /** * @return {?Array.} */ get queryParameters() { if (this._parsedQueryParameters) return this._parsedQueryParameters; var queryString = this.queryString(); if (!queryString) return null; this._parsedQueryParameters = this._parseParameters(queryString); return this._parsedQueryParameters; }, /** * @return {?Array.} */ get formParameters() { if (this._parsedFormParameters) return this._parsedFormParameters; if (!this.requestFormData) return null; var requestContentType = this.requestContentType(); if (!requestContentType || !requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i)) return null; this._parsedFormParameters = this._parseParameters(this.requestFormData); return this._parsedFormParameters; }, /** * @return {string|undefined} */ get responseHttpVersion() { var match = this.responseHeadersText.match(/^(HTTP\/\d+\.\d+)/); return match ? match[1] : undefined; }, /** * @param {string} queryString * @return {!Array.} */ _parseParameters: function(queryString) { function parseNameValue(pair) { var splitPair = pair.split("=", 2); return {name: splitPair[0], value: splitPair[1] || ""}; } return queryString.split("&").map(parseNameValue); }, /** * @param {!Array.} headers * @param {string} headerName * @return {string|undefined} */ _headerValue: function(headers, headerName) { headerName = headerName.toLowerCase(); var values = []; for (var i = 0; i < headers.length; ++i) { if (headers[i].name.toLowerCase() === headerName) values.push(headers[i].value); } if (!values.length) return undefined; // Set-Cookie values should be separated by '\n', not comma, otherwise cookies could not be parsed. if (headerName === "set-cookie") return values.join("\n"); return values.join(", "); }, /** * @return {?string|undefined} */ get content() { return this._content; }, /** * @return {boolean} */ get contentEncoded() { return this._contentEncoded; }, /** * @return {string} */ contentURL: function() { return this._url; }, /** * @return {WebInspector.ResourceType} */ contentType: function() { return this._type; }, /** * @param {function(?string, boolean, string)} callback */ requestContent: function(callback) { // We do not support content retrieval for WebSockets at the moment. // Since WebSockets are potentially long-living, fail requests immediately // to prevent caller blocking until resource is marked as finished. if (this.type === WebInspector.resourceTypes.WebSocket) { callback(null, false, this._mimeType); return; } if (typeof this._content !== "undefined") { callback(this.content || null, this._contentEncoded, this.type.canonicalMimeType() || this._mimeType); return; } this._pendingContentCallbacks.push(callback); if (this.finished) this._innerRequestContent(); }, /** * @param {string} query * @param {boolean} caseSensitive * @param {boolean} isRegex * @param {function(Array.)} callback */ searchInContent: function(query, caseSensitive, isRegex, callback) { callback([]); }, /** * @return {boolean} */ isHttpFamily: function() { return !!this.url.match(/^https?:/i); }, /** * @return {string|undefined} */ requestContentType: function() { return this.requestHeaderValue("Content-Type"); }, /** * @return {boolean} */ isPingRequest: function() { return "text/ping" === this.requestContentType(); }, /** * @return {boolean} */ hasErrorStatusCode: function() { return this.statusCode >= 400; }, /** * @param {Element} image */ populateImageSource: function(image) { /** * @this {WebInspector.NetworkRequest} * @param {?string} content * @param {boolean} contentEncoded * @param {string} mimeType */ function onResourceContent(content, contentEncoded, mimeType) { var imageSrc = this.asDataURL(); if (imageSrc === null) imageSrc = this.url; image.src = imageSrc; } this.requestContent(onResourceContent.bind(this)); }, /** * @return {?string} */ asDataURL: function() { return WebInspector.contentAsDataURL(this._content, this.mimeType, this._contentEncoded); }, _innerRequestContent: function() { if (this._contentRequested) return; this._contentRequested = true; /** * @param {?Protocol.Error} error * @param {string} content * @param {boolean} contentEncoded */ function onResourceContent(error, content, contentEncoded) { this._content = error ? null : content; this._contentEncoded = contentEncoded; var callbacks = this._pendingContentCallbacks.slice(); for (var i = 0; i < callbacks.length; ++i) callbacks[i](this._content, this._contentEncoded, this._mimeType); this._pendingContentCallbacks.length = 0; delete this._contentRequested; } NetworkAgent.getResponseBody(this._requestId, onResourceContent.bind(this)); }, /** * @return {{type: WebInspector.NetworkRequest.InitiatorType, url: string, source: string, lineNumber: number, columnNumber: number}} */ initiatorInfo: function() { if (this._initiatorInfo) return this._initiatorInfo; var type = WebInspector.NetworkRequest.InitiatorType.Other; var url = ""; var lineNumber = -Infinity; var columnNumber = -Infinity; if (this.redirectSource) { type = WebInspector.NetworkRequest.InitiatorType.Redirect; url = this.redirectSource.url; } else if (this.initiator) { if (this.initiator.type === NetworkAgent.InitiatorType.Parser) { type = WebInspector.NetworkRequest.InitiatorType.Parser; url = this.initiator.url; lineNumber = this.initiator.lineNumber; } else if (this.initiator.type === NetworkAgent.InitiatorType.Script) { var topFrame = this.initiator.stackTrace[0]; if (topFrame.url) { type = WebInspector.NetworkRequest.InitiatorType.Script; url = topFrame.url; lineNumber = topFrame.lineNumber; columnNumber = topFrame.columnNumber; } } } this._initiatorInfo = {type: type, url: url, source: WebInspector.displayNameForURL(url), lineNumber: lineNumber, columnNumber: columnNumber}; return this._initiatorInfo; }, /** * @return {!Array.} */ frames: function() { return this._frames; }, /** * @param {number} position * @return {Object|undefined} */ frame: function(position) { return this._frames[position]; }, /** * @param {string} errorMessage * @param {number} time */ addFrameError: function(errorMessage, time) { this._pushFrame({errorMessage: errorMessage, time: time}); }, /** * @param {!NetworkAgent.WebSocketFrame} response * @param {number} time * @param {boolean} sent */ addFrame: function(response, time, sent) { response.time = time; if (sent) response.sent = sent; this._pushFrame(response); }, /** * @param {!Object} frameOrError */ _pushFrame: function(frameOrError) { if (this._frames.length >= 100) this._frames.splice(0, 10); this._frames.push(frameOrError); }, __proto__: WebInspector.Object.prototype }