| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 2012 Google Inc. All rights reserved. | 2 * Copyright (C) 2012 Google Inc. All rights reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
| 6 * met: | 6 * met: |
| 7 * | 7 * |
| 8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
| 11 * copyright notice, this list of conditions and the following disclaimer | 11 * copyright notice, this list of conditions and the following disclaimer |
| 12 * in the documentation and/or other materials provided with the | 12 * in the documentation and/or other materials provided with the |
| 13 * distribution. | 13 * distribution. |
| 14 * * Neither the name of Google Inc. nor the names of its | 14 * * Neither the name of Google Inc. nor the names of its |
| 15 * contributors may be used to endorse or promote products derived from | 15 * contributors may be used to endorse or promote products derived from |
| 16 * this software without specific prior written permission. | 16 * this software without specific prior written permission. |
| 17 * | 17 * |
| 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 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. | 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 29 */ | 29 */ |
| 30 | |
| 31 // See http://www.softwareishard.com/blog/har-12-spec/ | 30 // See http://www.softwareishard.com/blog/har-12-spec/ |
| 32 // for HAR specification. | 31 // for HAR specification. |
| 33 | 32 |
| 34 // FIXME: Some fields are not yet supported due to back-end limitations. | 33 // FIXME: Some fields are not yet supported due to back-end limitations. |
| 35 // See https://bugs.webkit.org/show_bug.cgi?id=58127 for details. | 34 // See https://bugs.webkit.org/show_bug.cgi?id=58127 for details. |
| 36 | 35 |
| 37 /** | 36 /** |
| 38 * @constructor | 37 * @unrestricted |
| 39 * @param {!WebInspector.NetworkRequest} request | |
| 40 */ | 38 */ |
| 41 WebInspector.HAREntry = function(request) | 39 WebInspector.HAREntry = class { |
| 42 { | 40 /** |
| 41 * @param {!WebInspector.NetworkRequest} request |
| 42 */ |
| 43 constructor(request) { |
| 43 this._request = 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: WebInspector.HARLog.pseudoWallTime(this._request, this._r
equest.startTime), |
| 66 time: this._request.timing ? WebInspector.HAREntry._toMilliseconds(this._r
equest.duration) : 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 = this._request.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 = WebInspector.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.<!WebInspector.Cookie>} cookies |
| 206 * @return {!Array.<!Object>} |
| 207 */ |
| 208 _buildCookies(cookies) { |
| 209 return cookies.map(this._buildCookie.bind(this)); |
| 210 } |
| 211 |
| 212 /** |
| 213 * @param {!WebInspector.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(WebInspector.HARLog.pseudoWallTime(this._reque
st, 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 } |
| 44 }; | 259 }; |
| 45 | 260 |
| 46 WebInspector.HAREntry.prototype = { | 261 |
| 47 /** | 262 /** |
| 48 * @return {!Object} | 263 * @unrestricted |
| 49 */ | 264 */ |
| 50 build: function() | 265 WebInspector.HARLog = class { |
| 51 { | 266 /** |
| 52 var ipAddress = this._request.remoteAddress(); | 267 * @param {!Array.<!WebInspector.NetworkRequest>} requests |
| 53 var portPositionInString = ipAddress.lastIndexOf(":"); | 268 */ |
| 54 if (portPositionInString !== -1) | 269 constructor(requests) { |
| 55 ipAddress = ipAddress.substr(0, portPositionInString); | 270 this._requests = requests; |
| 56 | 271 } |
| 57 var entry = { | 272 |
| 58 startedDateTime: WebInspector.HARLog.pseudoWallTime(this._request, t
his._request.startTime), | 273 /** |
| 59 time: this._request.timing ? WebInspector.HAREntry._toMilliseconds(t
his._request.duration) : 0, | 274 * @param {!WebInspector.NetworkRequest} request |
| 60 request: this._buildRequest(), | 275 * @param {number} monotonicTime |
| 61 response: this._buildResponse(), | 276 * @return {!Date} |
| 62 cache: { }, // Not supported yet. | 277 */ |
| 63 timings: this._buildTimings(), | 278 static pseudoWallTime(request, monotonicTime) { |
| 64 serverIPAddress: ipAddress | 279 return new Date(request.pseudoWallTime(monotonicTime) * 1000); |
| 65 }; | 280 } |
| 66 | 281 |
| 67 if (this._request.connectionId !== "0") | 282 /** |
| 68 entry.connection = this._request.connectionId; | 283 * @return {!Object} |
| 69 var page = this._request.networkLog().pageLoadForRequest(this._request); | 284 */ |
| 70 if (page) | 285 build() { |
| 71 entry.pageref = "page_" + page.id; | 286 return { |
| 72 return entry; | 287 version: '1.2', |
| 73 }, | 288 creator: this._creator(), |
| 74 | 289 pages: this._buildPages(), |
| 75 /** | 290 entries: this._requests.map(this._convertResource.bind(this)) |
| 76 * @return {!Object} | 291 }; |
| 77 */ | 292 } |
| 78 _buildRequest: function() | 293 |
| 79 { | 294 _creator() { |
| 80 var headersText = this._request.requestHeadersText(); | 295 var webKitVersion = /AppleWebKit\/([^ ]+)/.exec(window.navigator.userAgent); |
| 81 var res = { | 296 |
| 82 method: this._request.requestMethod, | 297 return {name: 'WebInspector', version: webKitVersion ? webKitVersion[1] : 'n
/a'}; |
| 83 url: this._buildRequestURL(this._request.url), | 298 } |
| 84 httpVersion: this._request.requestHttpVersion(), | 299 |
| 85 headers: this._request.requestHeaders(), | 300 /** |
| 86 queryString: this._buildParameters(this._request.queryParameters ||
[]), | 301 * @return {!Array.<!Object>} |
| 87 cookies: this._buildCookies(this._request.requestCookies || []), | 302 */ |
| 88 headersSize: headersText ? headersText.length : -1, | 303 _buildPages() { |
| 89 bodySize: this.requestBodySize | 304 var seenIdentifiers = {}; |
| 90 }; | 305 var pages = []; |
| 91 if (this._request.requestFormData) | 306 for (var i = 0; i < this._requests.length; ++i) { |
| 92 res.postData = this._buildPostData(); | 307 var request = this._requests[i]; |
| 93 | 308 var page = request.networkLog().pageLoadForRequest(request); |
| 94 return res; | 309 if (!page || seenIdentifiers[page.id]) |
| 95 }, | 310 continue; |
| 96 | 311 seenIdentifiers[page.id] = true; |
| 97 /** | 312 pages.push(this._convertPage(page, request)); |
| 98 * @return {!Object} | |
| 99 */ | |
| 100 _buildResponse: function() | |
| 101 { | |
| 102 var headersText = this._request.responseHeadersText; | |
| 103 return { | |
| 104 status: this._request.statusCode, | |
| 105 statusText: this._request.statusText, | |
| 106 httpVersion: this._request.responseHttpVersion(), | |
| 107 headers: this._request.responseHeaders, | |
| 108 cookies: this._buildCookies(this._request.responseCookies || []), | |
| 109 content: this._buildContent(), | |
| 110 redirectURL: this._request.responseHeaderValue("Location") || "", | |
| 111 headersSize: headersText ? headersText.length : -1, | |
| 112 bodySize: this.responseBodySize, | |
| 113 _transferSize: this._request.transferSize, | |
| 114 _error: this._request.localizedFailDescription | |
| 115 }; | |
| 116 }, | |
| 117 | |
| 118 /** | |
| 119 * @return {!Object} | |
| 120 */ | |
| 121 _buildContent: function() | |
| 122 { | |
| 123 var content = { | |
| 124 size: this._request.resourceSize, | |
| 125 mimeType: this._request.mimeType || "x-unknown", | |
| 126 // 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) | |
| 127 }; | |
| 128 var compression = this.responseCompression; | |
| 129 if (typeof compression === "number") | |
| 130 content.compression = compression; | |
| 131 return content; | |
| 132 }, | |
| 133 | |
| 134 /** | |
| 135 * @return {!Object} | |
| 136 */ | |
| 137 _buildTimings: function() | |
| 138 { | |
| 139 // Order of events: request_start = 0, [proxy], [dns], [connect [ssl]],
[send], receive_headers_end | |
| 140 // HAR 'blocked' time is time before first network activity. | |
| 141 | |
| 142 var timing = this._request.timing; | |
| 143 if (!timing) | |
| 144 return {blocked: -1, dns: -1, connect: -1, send: 0, wait: 0, receive
: 0, ssl: -1}; | |
| 145 | |
| 146 function firstNonNegative(values) | |
| 147 { | |
| 148 for (var i = 0; i < values.length; ++i) { | |
| 149 if (values[i] >= 0) | |
| 150 return values[i]; | |
| 151 } | |
| 152 console.assert(false, "Incomplete request timing information."); | |
| 153 } | |
| 154 | |
| 155 var blocked = firstNonNegative([timing.dnsStart, timing.connectStart, ti
ming.sendStart]); | |
| 156 | |
| 157 var dns = -1; | |
| 158 if (timing.dnsStart >= 0) | |
| 159 dns = firstNonNegative([timing.connectStart, timing.sendStart]) - ti
ming.dnsStart; | |
| 160 | |
| 161 var connect = -1; | |
| 162 if (timing.connectStart >= 0) | |
| 163 connect = timing.sendStart - timing.connectStart; | |
| 164 | |
| 165 var send = timing.sendEnd - timing.sendStart; | |
| 166 var wait = timing.receiveHeadersEnd - timing.sendEnd; | |
| 167 var receive = WebInspector.HAREntry._toMilliseconds(this._request.durati
on) - timing.receiveHeadersEnd; | |
| 168 | |
| 169 var ssl = -1; | |
| 170 if (timing.sslStart >= 0 && timing.sslEnd >= 0) | |
| 171 ssl = timing.sslEnd - timing.sslStart; | |
| 172 | |
| 173 return {blocked: blocked, dns: dns, connect: connect, send: send, wait:
wait, receive: receive, ssl: ssl}; | |
| 174 }, | |
| 175 | |
| 176 /** | |
| 177 * @return {!Object} | |
| 178 */ | |
| 179 _buildPostData: function() | |
| 180 { | |
| 181 var res = { | |
| 182 mimeType: this._request.requestContentType(), | |
| 183 text: this._request.requestFormData | |
| 184 }; | |
| 185 if (this._request.formParameters) | |
| 186 res.params = this._buildParameters(this._request.formParameters); | |
| 187 return res; | |
| 188 }, | |
| 189 | |
| 190 /** | |
| 191 * @param {!Array.<!Object>} parameters | |
| 192 * @return {!Array.<!Object>} | |
| 193 */ | |
| 194 _buildParameters: function(parameters) | |
| 195 { | |
| 196 return parameters.slice(); | |
| 197 }, | |
| 198 | |
| 199 /** | |
| 200 * @param {string} url | |
| 201 * @return {string} | |
| 202 */ | |
| 203 _buildRequestURL: function(url) | |
| 204 { | |
| 205 return url.split("#", 2)[0]; | |
| 206 }, | |
| 207 | |
| 208 /** | |
| 209 * @param {!Array.<!WebInspector.Cookie>} cookies | |
| 210 * @return {!Array.<!Object>} | |
| 211 */ | |
| 212 _buildCookies: function(cookies) | |
| 213 { | |
| 214 return cookies.map(this._buildCookie.bind(this)); | |
| 215 }, | |
| 216 | |
| 217 /** | |
| 218 * @param {!WebInspector.Cookie} cookie | |
| 219 * @return {!Object} | |
| 220 */ | |
| 221 _buildCookie: function(cookie) | |
| 222 { | |
| 223 var c = { | |
| 224 name: cookie.name(), | |
| 225 value: cookie.value(), | |
| 226 path: cookie.path(), | |
| 227 domain: cookie.domain(), | |
| 228 expires: cookie.expiresDate(WebInspector.HARLog.pseudoWallTime(this.
_request, this._request.startTime)), | |
| 229 httpOnly: cookie.httpOnly(), | |
| 230 secure: cookie.secure() | |
| 231 }; | |
| 232 if (cookie.sameSite()) | |
| 233 c.sameSite = cookie.sameSite(); | |
| 234 return c; | |
| 235 }, | |
| 236 | |
| 237 /** | |
| 238 * @return {number} | |
| 239 */ | |
| 240 get requestBodySize() | |
| 241 { | |
| 242 return !this._request.requestFormData ? 0 : this._request.requestFormDat
a.length; | |
| 243 }, | |
| 244 | |
| 245 /** | |
| 246 * @return {number} | |
| 247 */ | |
| 248 get responseBodySize() | |
| 249 { | |
| 250 if (this._request.cached() || this._request.statusCode === 304) | |
| 251 return 0; | |
| 252 if (!this._request.responseHeadersText) | |
| 253 return -1; | |
| 254 return this._request.transferSize - this._request.responseHeadersText.le
ngth; | |
| 255 }, | |
| 256 | |
| 257 /** | |
| 258 * @return {number|undefined} | |
| 259 */ | |
| 260 get responseCompression() | |
| 261 { | |
| 262 if (this._request.cached() || this._request.statusCode === 304 || this._
request.statusCode === 206) | |
| 263 return; | |
| 264 if (!this._request.responseHeadersText) | |
| 265 return; | |
| 266 return this._request.resourceSize - this.responseBodySize; | |
| 267 } | 313 } |
| 314 return pages; |
| 315 } |
| 316 |
| 317 /** |
| 318 * @param {!WebInspector.PageLoad} page |
| 319 * @param {!WebInspector.NetworkRequest} request |
| 320 * @return {!Object} |
| 321 */ |
| 322 _convertPage(page, request) { |
| 323 return { |
| 324 startedDateTime: WebInspector.HARLog.pseudoWallTime(request, page.startTim
e), |
| 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 {!WebInspector.NetworkRequest} request |
| 336 * @return {!Object} |
| 337 */ |
| 338 _convertResource(request) { |
| 339 return (new WebInspector.HAREntry(request)).build(); |
| 340 } |
| 341 |
| 342 /** |
| 343 * @param {!WebInspector.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 WebInspector.HAREntry._toMilliseconds(time - startTime); |
| 352 } |
| 268 }; | 353 }; |
| 269 | 354 |
| 270 /** | 355 |
| 271 * @param {number} time | |
| 272 * @return {number} | |
| 273 */ | |
| 274 WebInspector.HAREntry._toMilliseconds = function(time) | |
| 275 { | |
| 276 return time === -1 ? -1 : time * 1000; | |
| 277 }; | |
| 278 | |
| 279 /** | |
| 280 * @constructor | |
| 281 * @param {!Array.<!WebInspector.NetworkRequest>} requests | |
| 282 */ | |
| 283 WebInspector.HARLog = function(requests) | |
| 284 { | |
| 285 this._requests = requests; | |
| 286 }; | |
| 287 | |
| 288 /** | |
| 289 * @param {!WebInspector.NetworkRequest} request | |
| 290 * @param {number} monotonicTime | |
| 291 * @return {!Date} | |
| 292 */ | |
| 293 WebInspector.HARLog.pseudoWallTime = function(request, monotonicTime) | |
| 294 { | |
| 295 return new Date(request.pseudoWallTime(monotonicTime) * 1000); | |
| 296 }; | |
| 297 | |
| 298 WebInspector.HARLog.prototype = { | |
| 299 /** | |
| 300 * @return {!Object} | |
| 301 */ | |
| 302 build: function() | |
| 303 { | |
| 304 return { | |
| 305 version: "1.2", | |
| 306 creator: this._creator(), | |
| 307 pages: this._buildPages(), | |
| 308 entries: this._requests.map(this._convertResource.bind(this)) | |
| 309 }; | |
| 310 }, | |
| 311 | |
| 312 _creator: function() | |
| 313 { | |
| 314 var webKitVersion = /AppleWebKit\/([^ ]+)/.exec(window.navigator.userAge
nt); | |
| 315 | |
| 316 return { | |
| 317 name: "WebInspector", | |
| 318 version: webKitVersion ? webKitVersion[1] : "n/a" | |
| 319 }; | |
| 320 }, | |
| 321 | |
| 322 /** | |
| 323 * @return {!Array.<!Object>} | |
| 324 */ | |
| 325 _buildPages: function() | |
| 326 { | |
| 327 var seenIdentifiers = {}; | |
| 328 var pages = []; | |
| 329 for (var i = 0; i < this._requests.length; ++i) { | |
| 330 var request = this._requests[i]; | |
| 331 var page = request.networkLog().pageLoadForRequest(request); | |
| 332 if (!page || seenIdentifiers[page.id]) | |
| 333 continue; | |
| 334 seenIdentifiers[page.id] = true; | |
| 335 pages.push(this._convertPage(page, request)); | |
| 336 } | |
| 337 return pages; | |
| 338 }, | |
| 339 | |
| 340 /** | |
| 341 * @param {!WebInspector.PageLoad} page | |
| 342 * @param {!WebInspector.NetworkRequest} request | |
| 343 * @return {!Object} | |
| 344 */ | |
| 345 _convertPage: function(page, request) | |
| 346 { | |
| 347 return { | |
| 348 startedDateTime: WebInspector.HARLog.pseudoWallTime(request, page.st
artTime), | |
| 349 id: "page_" + page.id, | |
| 350 title: page.url, // We don't have actual page title here. URL is pro
bably better than nothing. | |
| 351 pageTimings: { | |
| 352 onContentLoad: this._pageEventTime(page, page.contentLoadTime), | |
| 353 onLoad: this._pageEventTime(page, page.loadTime) | |
| 354 } | |
| 355 }; | |
| 356 }, | |
| 357 | |
| 358 /** | |
| 359 * @param {!WebInspector.NetworkRequest} request | |
| 360 * @return {!Object} | |
| 361 */ | |
| 362 _convertResource: function(request) | |
| 363 { | |
| 364 return (new WebInspector.HAREntry(request)).build(); | |
| 365 }, | |
| 366 | |
| 367 /** | |
| 368 * @param {!WebInspector.PageLoad} page | |
| 369 * @param {number} time | |
| 370 * @return {number} | |
| 371 */ | |
| 372 _pageEventTime: function(page, time) | |
| 373 { | |
| 374 var startTime = page.startTime; | |
| 375 if (time === -1 || startTime === -1) | |
| 376 return -1; | |
| 377 return WebInspector.HAREntry._toMilliseconds(time - startTime); | |
| 378 } | |
| 379 }; | |
| OLD | NEW |