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 |