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 |