123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- /*
- * 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.
- */
- // See http://www.softwareishard.com/blog/har-12-spec/
- // for HAR specification.
- // FIXME: Some fields are not yet supported due to back-end limitations.
- // See https://bugs.webkit.org/show_bug.cgi?id=58127 for details.
- /**
- * @constructor
- * @param {WebInspector.NetworkRequest} request
- */
- WebInspector.HAREntry = function(request)
- {
- this._request = request;
- }
- WebInspector.HAREntry.prototype = {
- /**
- * @return {Object}
- */
- build: function()
- {
- var entry = {
- startedDateTime: new Date(this._request.startTime * 1000),
- time: WebInspector.HAREntry._toMilliseconds(this._request.duration),
- request: this._buildRequest(),
- response: this._buildResponse(),
- cache: { }, // Not supported yet.
- timings: this._buildTimings()
- };
- if (this._request.connectionId)
- entry.connection = String(this._request.connectionId);
- var page = WebInspector.networkLog.pageLoadForRequest(this._request);
- if (page)
- entry.pageref = "page_" + page.id;
- return entry;
- },
- /**
- * @return {Object}
- */
- _buildRequest: function()
- {
- var res = {
- method: this._request.requestMethod,
- url: this._buildRequestURL(this._request.url),
- httpVersion: this._request.requestHttpVersion,
- headers: this._request.requestHeaders,
- queryString: this._buildParameters(this._request.queryParameters || []),
- cookies: this._buildCookies(this._request.requestCookies || []),
- headersSize: this._request.requestHeadersSize,
- bodySize: this.requestBodySize
- };
- if (this._request.requestFormData)
- res.postData = this._buildPostData();
- return res;
- },
- /**
- * @return {Object}
- */
- _buildResponse: function()
- {
- return {
- status: this._request.statusCode,
- statusText: this._request.statusText,
- httpVersion: this._request.responseHttpVersion,
- headers: this._request.responseHeaders,
- cookies: this._buildCookies(this._request.responseCookies || []),
- content: this._buildContent(),
- redirectURL: this._request.responseHeaderValue("Location") || "",
- headersSize: this._request.responseHeadersSize,
- bodySize: this.responseBodySize
- };
- },
- /**
- * @return {Object}
- */
- _buildContent: function()
- {
- var content = {
- size: this._request.resourceSize,
- mimeType: this._request.mimeType,
- // text: this._request.content // TODO: pull out into a boolean flag, as content can be huge (and needs to be requested with an async call)
- };
- var compression = this.responseCompression;
- if (typeof compression === "number")
- content.compression = compression;
- return content;
- },
- /**
- * @return {Object}
- */
- _buildTimings: function()
- {
- var waitForConnection = this._interval("connectStart", "connectEnd");
- var blocked = 0;
- var connect = -1;
- if (this._request.connectionReused)
- blocked = waitForConnection;
- else
- connect = waitForConnection;
- return {
- blocked: blocked,
- dns: this._interval("dnsStart", "dnsEnd"),
- connect: connect,
- send: this._interval("sendStart", "sendEnd"),
- wait: this._interval("sendEnd", "receiveHeadersEnd"),
- receive: WebInspector.HAREntry._toMilliseconds(this._request.receiveDuration),
- ssl: this._interval("sslStart", "sslEnd")
- };
- },
- /**
- * @return {Object}
- */
- _buildPostData: function()
- {
- var res = {
- mimeType: this._request.requestHeaderValue("Content-Type"),
- text: this._request.requestFormData
- };
- if (this._request.formParameters)
- res.params = this._buildParameters(this._request.formParameters);
- return res;
- },
- /**
- * @param {Array.<Object>} parameters
- * @return {Array.<Object>}
- */
- _buildParameters: function(parameters)
- {
- return parameters.slice();
- },
- /**
- * @param {string} url
- * @return {string}
- */
- _buildRequestURL: function(url)
- {
- return url.split("#", 2)[0];
- },
- /**
- * @param {Array.<WebInspector.Cookie>} cookies
- * @return {Array.<Object>}
- */
- _buildCookies: function(cookies)
- {
- return cookies.map(this._buildCookie.bind(this));
- },
- /**
- * @param {WebInspector.Cookie} cookie
- * @return {Object}
- */
- _buildCookie: function(cookie)
- {
- return {
- name: cookie.name(),
- value: cookie.value(),
- path: cookie.path(),
- domain: cookie.domain(),
- expires: cookie.expiresDate(new Date(this._request.startTime * 1000)),
- httpOnly: cookie.httpOnly(),
- secure: cookie.secure()
- };
- },
- /**
- * @param {string} start
- * @param {string} end
- * @return {number}
- */
- _interval: function(start, end)
- {
- var timing = this._request.timing;
- if (!timing)
- return -1;
- var startTime = timing[start];
- return typeof startTime !== "number" || startTime === -1 ? -1 : Math.round(timing[end] - startTime);
- },
- /**
- * @return {number}
- */
- get requestBodySize()
- {
- return !this._request.requestFormData ? 0 : this._request.requestFormData.length;
- },
- /**
- * @return {number}
- */
- get responseBodySize()
- {
- if (this._request.cached || this._request.statusCode === 304)
- return 0;
- return this._request.transferSize - this._request.responseHeadersSize;
- },
- /**
- * @return {number|undefined}
- */
- get responseCompression()
- {
- if (this._request.cached || this._request.statusCode === 304 || this._request.statusCode === 206)
- return;
- return this._request.resourceSize - this.responseBodySize;
- }
- }
- /**
- * @param {number} time
- * @return {number}
- */
- WebInspector.HAREntry._toMilliseconds = function(time)
- {
- return time === -1 ? -1 : Math.round(time * 1000);
- }
- /**
- * @constructor
- * @param {Array.<WebInspector.NetworkRequest>} requests
- */
- WebInspector.HARLog = function(requests)
- {
- this._requests = requests;
- }
- WebInspector.HARLog.prototype = {
- /**
- * @return {Object}
- */
- build: function()
- {
- return {
- version: "1.2",
- creator: this._creator(),
- pages: this._buildPages(),
- entries: this._requests.map(this._convertResource.bind(this))
- }
- },
- _creator: function()
- {
- var webKitVersion = /AppleWebKit\/([^ ]+)/.exec(window.navigator.userAgent);
- return {
- name: "WebInspector",
- version: webKitVersion ? webKitVersion[1] : "n/a"
- };
- },
- /**
- * @return {Array}
- */
- _buildPages: function()
- {
- var seenIdentifiers = {};
- var pages = [];
- for (var i = 0; i < this._requests.length; ++i) {
- var page = WebInspector.networkLog.pageLoadForRequest(this._requests[i]);
- if (!page || seenIdentifiers[page.id])
- continue;
- seenIdentifiers[page.id] = true;
- pages.push(this._convertPage(page));
- }
- return pages;
- },
- /**
- * @param {WebInspector.PageLoad} page
- * @return {Object}
- */
- _convertPage: function(page)
- {
- return {
- startedDateTime: new Date(page.startTime * 1000),
- id: "page_" + page.id,
- title: page.url, // We don't have actual page title here. URL is probably better than nothing.
- pageTimings: {
- onContentLoad: this._pageEventTime(page, page.contentLoadTime),
- onLoad: this._pageEventTime(page, page.loadTime)
- }
- }
- },
- /**
- * @param {WebInspector.NetworkRequest} request
- * @return {Object}
- */
- _convertResource: function(request)
- {
- return (new WebInspector.HAREntry(request)).build();
- },
- /**
- * @param {WebInspector.PageLoad} page
- * @param {number} time
- * @return {number}
- */
- _pageEventTime: function(page, time)
- {
- var startTime = page.startTime;
- if (time === -1 || startTime === -1)
- return -1;
- return WebInspector.HAREntry._toMilliseconds(time - startTime);
- }
- }
- /**
- * @constructor
- */
- WebInspector.HARWriter = function()
- {
- }
- WebInspector.HARWriter.prototype = {
- /**
- * @param {WebInspector.OutputStream} stream
- * @param {Array.<WebInspector.NetworkRequest>} requests
- * @param {WebInspector.Progress} progress
- */
- write: function(stream, requests, progress)
- {
- this._stream = stream;
- this._harLog = (new WebInspector.HARLog(requests)).build();
- this._pendingRequests = 1; // Guard against completing resource transfer before all requests are made.
- var entries = this._harLog.entries;
- for (var i = 0; i < entries.length; ++i) {
- var content = requests[i].content;
- if (typeof content === "undefined" && requests[i].finished) {
- ++this._pendingRequests;
- requests[i].requestContent(this._onContentAvailable.bind(this, entries[i]));
- } else if (content !== null)
- entries[i].response.content.text = content;
- }
- var compositeProgress = new WebInspector.CompositeProgress(progress);
- this._writeProgress = compositeProgress.createSubProgress();
- if (--this._pendingRequests) {
- this._requestsProgress = compositeProgress.createSubProgress();
- this._requestsProgress.setTitle(WebInspector.UIString("Collecting content…"));
- this._requestsProgress.setTotalWork(this._pendingRequests);
- } else
- this._beginWrite();
- },
- /**
- * @param {Object} entry
- * @param {string|null} content
- * @param {boolean} contentEncoded
- * @param {string=} mimeType
- */
- _onContentAvailable: function(entry, content, contentEncoded, mimeType)
- {
- if (content !== null)
- entry.response.content.text = content;
- if (this._requestsProgress)
- this._requestsProgress.worked();
- if (!--this._pendingRequests) {
- this._requestsProgress.done();
- this._beginWrite();
- }
- },
- _beginWrite: function()
- {
- const jsonIndent = 2;
- this._text = JSON.stringify({log: this._harLog}, null, jsonIndent);
- this._writeProgress.setTitle(WebInspector.UIString("Writing file…"));
- this._writeProgress.setTotalWork(this._text.length);
- this._bytesWritten = 0;
- this._writeNextChunk(this._stream);
- },
- /**
- * @param {WebInspector.OutputStream} stream
- * @param {string=} error
- */
- _writeNextChunk: function(stream, error)
- {
- if (this._bytesWritten >= this._text.length || error) {
- stream.close();
- this._writeProgress.done();
- return;
- }
- const chunkSize = 100000;
- var text = this._text.substring(this._bytesWritten, this._bytesWritten + chunkSize);
- this._bytesWritten += text.length;
- stream.write(text, this._writeNextChunk.bind(this));
- this._writeProgress.setWorked(this._bytesWritten);
- }
- }
|