| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright (C) 2012 Google Inc. All rights reserved. | |
| 3 * | |
| 4 * Redistribution and use in source and binary forms, with or without | |
| 5 * modification, are permitted provided that the following conditions are | |
| 6 * met: | |
| 7 * | |
| 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 | |
| 13 * distribution. | |
| 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. | |
| 17 * | |
| 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. | |
| 29 */ | |
| 30 // See http://www.softwareishard.com/blog/har-12-spec/ | |
| 31 // for HAR specification. | |
| 32 | |
| 33 // FIXME: Some fields are not yet supported due to back-end limitations. | |
| 34 // See https://bugs.webkit.org/show_bug.cgi?id=58127 for details. | |
| 35 | |
| 36 /** | |
| 37 * @unrestricted | |
| 38 */ | |
| 39 SDK.HAREntry = class { | |
| 40 /** | |
| 41 * @param {!SDK.NetworkRequest} request | |
| 42 */ | |
| 43 constructor(request) { | |
| 44 this._request = request; | |
| 45 } | |
| 46 | |
| 47 /** | |
| 48 * @param {number} time | |
| 49 * @return {number} | |
| 50 */ | |
| 51 static _toMilliseconds(time) { | |
| 52 return time === -1 ? -1 : time * 1000; | |
| 53 } | |
| 54 | |
| 55 /** | |
| 56 * @return {!Object} | |
| 57 */ | |
| 58 build() { | |
| 59 var ipAddress = this._request.remoteAddress(); | |
| 60 var portPositionInString = ipAddress.lastIndexOf(':'); | |
| 61 if (portPositionInString !== -1) | |
| 62 ipAddress = ipAddress.substr(0, portPositionInString); | |
| 63 | |
| 64 var entry = { | |
| 65 startedDateTime: SDK.HARLog.pseudoWallTime(this._request, this._request.st
artTime), | |
| 66 time: this._request.timing ? SDK.HAREntry._toMilliseconds(this._request.du
ration) : 0, | |
| 67 request: this._buildRequest(), | |
| 68 response: this._buildResponse(), | |
| 69 cache: {}, // Not supported yet. | |
| 70 timings: this._buildTimings(), | |
| 71 serverIPAddress: ipAddress | |
| 72 }; | |
| 73 | |
| 74 if (this._request.connectionId !== '0') | |
| 75 entry.connection = this._request.connectionId; | |
| 76 var page = SDK.networkLog.pageLoadForRequest(this._request); | |
| 77 if (page) | |
| 78 entry.pageref = 'page_' + page.id; | |
| 79 return entry; | |
| 80 } | |
| 81 | |
| 82 /** | |
| 83 * @return {!Object} | |
| 84 */ | |
| 85 _buildRequest() { | |
| 86 var headersText = this._request.requestHeadersText(); | |
| 87 var res = { | |
| 88 method: this._request.requestMethod, | |
| 89 url: this._buildRequestURL(this._request.url()), | |
| 90 httpVersion: this._request.requestHttpVersion(), | |
| 91 headers: this._request.requestHeaders(), | |
| 92 queryString: this._buildParameters(this._request.queryParameters || []), | |
| 93 cookies: this._buildCookies(this._request.requestCookies || []), | |
| 94 headersSize: headersText ? headersText.length : -1, | |
| 95 bodySize: this.requestBodySize | |
| 96 }; | |
| 97 if (this._request.requestFormData) | |
| 98 res.postData = this._buildPostData(); | |
| 99 | |
| 100 return res; | |
| 101 } | |
| 102 | |
| 103 /** | |
| 104 * @return {!Object} | |
| 105 */ | |
| 106 _buildResponse() { | |
| 107 var headersText = this._request.responseHeadersText; | |
| 108 return { | |
| 109 status: this._request.statusCode, | |
| 110 statusText: this._request.statusText, | |
| 111 httpVersion: this._request.responseHttpVersion(), | |
| 112 headers: this._request.responseHeaders, | |
| 113 cookies: this._buildCookies(this._request.responseCookies || []), | |
| 114 content: this._buildContent(), | |
| 115 redirectURL: this._request.responseHeaderValue('Location') || '', | |
| 116 headersSize: headersText ? headersText.length : -1, | |
| 117 bodySize: this.responseBodySize, | |
| 118 _transferSize: this._request.transferSize, | |
| 119 _error: this._request.localizedFailDescription | |
| 120 }; | |
| 121 } | |
| 122 | |
| 123 /** | |
| 124 * @return {!Object} | |
| 125 */ | |
| 126 _buildContent() { | |
| 127 var content = { | |
| 128 size: this._request.resourceSize, | |
| 129 mimeType: this._request.mimeType || 'x-unknown', | |
| 130 // text: this._request.content // TODO: pull out into a boolean flag, as c
ontent can be huge (and needs to be requested with an async call) | |
| 131 }; | |
| 132 var compression = this.responseCompression; | |
| 133 if (typeof compression === 'number') | |
| 134 content.compression = compression; | |
| 135 return content; | |
| 136 } | |
| 137 | |
| 138 /** | |
| 139 * @return {!Object} | |
| 140 */ | |
| 141 _buildTimings() { | |
| 142 // Order of events: request_start = 0, [proxy], [dns], [connect [ssl]], [sen
d], receive_headers_end | |
| 143 // HAR 'blocked' time is time before first network activity. | |
| 144 | |
| 145 var timing = this._request.timing; | |
| 146 if (!timing) | |
| 147 return {blocked: -1, dns: -1, connect: -1, send: 0, wait: 0, receive: 0, s
sl: -1}; | |
| 148 | |
| 149 function firstNonNegative(values) { | |
| 150 for (var i = 0; i < values.length; ++i) { | |
| 151 if (values[i] >= 0) | |
| 152 return values[i]; | |
| 153 } | |
| 154 console.assert(false, 'Incomplete request timing information.'); | |
| 155 } | |
| 156 | |
| 157 var blocked = firstNonNegative([timing.dnsStart, timing.connectStart, timing
.sendStart]); | |
| 158 | |
| 159 var dns = -1; | |
| 160 if (timing.dnsStart >= 0) | |
| 161 dns = firstNonNegative([timing.connectStart, timing.sendStart]) - timing.d
nsStart; | |
| 162 | |
| 163 var connect = -1; | |
| 164 if (timing.connectStart >= 0) | |
| 165 connect = timing.sendStart - timing.connectStart; | |
| 166 | |
| 167 var send = timing.sendEnd - timing.sendStart; | |
| 168 var wait = timing.receiveHeadersEnd - timing.sendEnd; | |
| 169 var receive = SDK.HAREntry._toMilliseconds(this._request.duration) - timing.
receiveHeadersEnd; | |
| 170 | |
| 171 var ssl = -1; | |
| 172 if (timing.sslStart >= 0 && timing.sslEnd >= 0) | |
| 173 ssl = timing.sslEnd - timing.sslStart; | |
| 174 | |
| 175 return {blocked: blocked, dns: dns, connect: connect, send: send, wait: wait
, receive: receive, ssl: ssl}; | |
| 176 } | |
| 177 | |
| 178 /** | |
| 179 * @return {!Object} | |
| 180 */ | |
| 181 _buildPostData() { | |
| 182 var res = {mimeType: this._request.requestContentType(), text: this._request
.requestFormData}; | |
| 183 if (this._request.formParameters) | |
| 184 res.params = this._buildParameters(this._request.formParameters); | |
| 185 return res; | |
| 186 } | |
| 187 | |
| 188 /** | |
| 189 * @param {!Array.<!Object>} parameters | |
| 190 * @return {!Array.<!Object>} | |
| 191 */ | |
| 192 _buildParameters(parameters) { | |
| 193 return parameters.slice(); | |
| 194 } | |
| 195 | |
| 196 /** | |
| 197 * @param {string} url | |
| 198 * @return {string} | |
| 199 */ | |
| 200 _buildRequestURL(url) { | |
| 201 return url.split('#', 2)[0]; | |
| 202 } | |
| 203 | |
| 204 /** | |
| 205 * @param {!Array.<!SDK.Cookie>} cookies | |
| 206 * @return {!Array.<!Object>} | |
| 207 */ | |
| 208 _buildCookies(cookies) { | |
| 209 return cookies.map(this._buildCookie.bind(this)); | |
| 210 } | |
| 211 | |
| 212 /** | |
| 213 * @param {!SDK.Cookie} cookie | |
| 214 * @return {!Object} | |
| 215 */ | |
| 216 _buildCookie(cookie) { | |
| 217 var c = { | |
| 218 name: cookie.name(), | |
| 219 value: cookie.value(), | |
| 220 path: cookie.path(), | |
| 221 domain: cookie.domain(), | |
| 222 expires: cookie.expiresDate(SDK.HARLog.pseudoWallTime(this._request, this.
_request.startTime)), | |
| 223 httpOnly: cookie.httpOnly(), | |
| 224 secure: cookie.secure() | |
| 225 }; | |
| 226 if (cookie.sameSite()) | |
| 227 c.sameSite = cookie.sameSite(); | |
| 228 return c; | |
| 229 } | |
| 230 | |
| 231 /** | |
| 232 * @return {number} | |
| 233 */ | |
| 234 get requestBodySize() { | |
| 235 return !this._request.requestFormData ? 0 : this._request.requestFormData.le
ngth; | |
| 236 } | |
| 237 | |
| 238 /** | |
| 239 * @return {number} | |
| 240 */ | |
| 241 get responseBodySize() { | |
| 242 if (this._request.cached() || this._request.statusCode === 304) | |
| 243 return 0; | |
| 244 if (!this._request.responseHeadersText) | |
| 245 return -1; | |
| 246 return this._request.transferSize - this._request.responseHeadersText.length
; | |
| 247 } | |
| 248 | |
| 249 /** | |
| 250 * @return {number|undefined} | |
| 251 */ | |
| 252 get responseCompression() { | |
| 253 if (this._request.cached() || this._request.statusCode === 304 || this._requ
est.statusCode === 206) | |
| 254 return; | |
| 255 if (!this._request.responseHeadersText) | |
| 256 return; | |
| 257 return this._request.resourceSize - this.responseBodySize; | |
| 258 } | |
| 259 }; | |
| 260 | |
| 261 | |
| 262 /** | |
| 263 * @unrestricted | |
| 264 */ | |
| 265 SDK.HARLog = class { | |
| 266 /** | |
| 267 * @param {!Array.<!SDK.NetworkRequest>} requests | |
| 268 */ | |
| 269 constructor(requests) { | |
| 270 this._requests = requests; | |
| 271 } | |
| 272 | |
| 273 /** | |
| 274 * @param {!SDK.NetworkRequest} request | |
| 275 * @param {number} monotonicTime | |
| 276 * @return {!Date} | |
| 277 */ | |
| 278 static pseudoWallTime(request, monotonicTime) { | |
| 279 return new Date(request.pseudoWallTime(monotonicTime) * 1000); | |
| 280 } | |
| 281 | |
| 282 /** | |
| 283 * @return {!Object} | |
| 284 */ | |
| 285 build() { | |
| 286 return { | |
| 287 version: '1.2', | |
| 288 creator: this._creator(), | |
| 289 pages: this._buildPages(), | |
| 290 entries: this._requests.map(this._convertResource.bind(this)) | |
| 291 }; | |
| 292 } | |
| 293 | |
| 294 _creator() { | |
| 295 var webKitVersion = /AppleWebKit\/([^ ]+)/.exec(window.navigator.userAgent); | |
| 296 | |
| 297 return {name: 'WebInspector', version: webKitVersion ? webKitVersion[1] : 'n
/a'}; | |
| 298 } | |
| 299 | |
| 300 /** | |
| 301 * @return {!Array.<!Object>} | |
| 302 */ | |
| 303 _buildPages() { | |
| 304 var seenIdentifiers = {}; | |
| 305 var pages = []; | |
| 306 for (var i = 0; i < this._requests.length; ++i) { | |
| 307 var request = this._requests[i]; | |
| 308 var page = SDK.networkLog.pageLoadForRequest(request); | |
| 309 if (!page || seenIdentifiers[page.id]) | |
| 310 continue; | |
| 311 seenIdentifiers[page.id] = true; | |
| 312 pages.push(this._convertPage(page, request)); | |
| 313 } | |
| 314 return pages; | |
| 315 } | |
| 316 | |
| 317 /** | |
| 318 * @param {!SDK.PageLoad} page | |
| 319 * @param {!SDK.NetworkRequest} request | |
| 320 * @return {!Object} | |
| 321 */ | |
| 322 _convertPage(page, request) { | |
| 323 return { | |
| 324 startedDateTime: SDK.HARLog.pseudoWallTime(request, page.startTime), | |
| 325 id: 'page_' + page.id, | |
| 326 title: page.url, // We don't have actual page title here. URL is probably
better than nothing. | |
| 327 pageTimings: { | |
| 328 onContentLoad: this._pageEventTime(page, page.contentLoadTime), | |
| 329 onLoad: this._pageEventTime(page, page.loadTime) | |
| 330 } | |
| 331 }; | |
| 332 } | |
| 333 | |
| 334 /** | |
| 335 * @param {!SDK.NetworkRequest} request | |
| 336 * @return {!Object} | |
| 337 */ | |
| 338 _convertResource(request) { | |
| 339 return (new SDK.HAREntry(request)).build(); | |
| 340 } | |
| 341 | |
| 342 /** | |
| 343 * @param {!SDK.PageLoad} page | |
| 344 * @param {number} time | |
| 345 * @return {number} | |
| 346 */ | |
| 347 _pageEventTime(page, time) { | |
| 348 var startTime = page.startTime; | |
| 349 if (time === -1 || startTime === -1) | |
| 350 return -1; | |
| 351 return SDK.HAREntry._toMilliseconds(time - startTime); | |
| 352 } | |
| 353 }; | |
| OLD | NEW |