1 /* 2 * Copyright (C) 2011 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 31 // See http://groups.google.com/group/http-archive-specification/web/har-1-2-spec 32 // for HAR specification. 33 34 // 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. 36 37 WebInspector.HAREntry = function(resource) 38 { 39 this._resource = resource; 40 } 41 42 WebInspector.HAREntry.prototype = { 43 build: function() 44 { 45 return { 46 pageref: this._resource.documentURL, 47 startedDateTime: new Date(this._resource.startTime * 1000), 48 time: WebInspector.HAREntry._toMilliseconds(this._resource.duration), 49 request: this._buildRequest(), 50 response: this._buildResponse(), 51 cache: { }, // Not supproted yet. 52 timings: this._buildTimings() 53 }; 54 }, 55 56 _buildRequest: function() 57 { 58 var res = { 59 method: this._resource.requestMethod, 60 url: this._resource.url, 61 // httpVersion: "HTTP/1.1" -- Not available. 62 headers: this._buildHeaders(this._resource.requestHeaders), 63 queryString: this._buildParameters(this._resource.queryParameters || []), 64 cookies: this._buildCookies(this._resource.requestCookies || []), 65 headersSize: -1, // Not available. 66 bodySize: -1 // Not available. 67 }; 68 if (this._resource.requestFormData) 69 res.postData = this._buildPostData(); 70 return res; 71 }, 72 73 _buildResponse: function() 74 { 75 return { 76 status: this._resource.statusCode, 77 statusText: this._resource.statusText, 78 // "httpVersion": "HTTP/1.1" -- Not available. 79 headers: this._buildHeaders(this._resource.responseHeaders), 80 cookies: this._buildCookies(this._resource.responseCookies || []), 81 content: this._buildContent(), 82 redirectURL: this._resource.responseHeaderValue("Location") || "", 83 headersSize: -1, // Not available. 84 bodySize: this._resource.resourceSize 85 }; 86 }, 87 88 _buildContent: function() 89 { 90 return { 91 size: this._resource.resourceSize, 92 // compression: 0, -- Not available. 93 mimeType: this._resource.mimeType, 94 // text: -- Not available. 95 }; 96 }, 97 98 _buildTimings: function() 99 { 100 var waitForConnection = this._interval("connectStart", "connectEnd"); 101 var blocked; 102 var connect; 103 var dns = this._interval("dnsStart", "dnsEnd"); 104 var send = this._interval("sendStart", "sendEnd"); 105 var ssl = this._interval("sslStart", "sslEnd"); 106 107 if (ssl !== -1 && send !== -1) 108 send -= ssl; 109 110 if (this._resource.connectionReused) { 111 connect = -1; 112 blocked = waitForConnection; 113 } else { 114 blocked = 0; 115 connect = waitForConnection; 116 if (dns !== -1) 117 connect -= dns; 118 } 119 120 return { 121 blocked: blocked, 122 dns: dns, 123 connect: connect, 124 send: send, 125 wait: this._interval("sendEnd", "receiveHeadersEnd"), 126 receive: WebInspector.HAREntry._toMilliseconds(this._resource.receiveDuration), 127 ssl: ssl 128 }; 129 }, 130 131 _buildHeaders: function(headers) 132 { 133 var result = []; 134 for (var name in headers) 135 result.push({ name: name, value: headers[name] }); 136 return result; 137 }, 138 139 _buildPostData: function() 140 { 141 var res = { 142 mimeType: this._resource.requestHeaderValue("Content-Type"), 143 text: this._resource.requestFormData 144 }; 145 if (this._resource.formParameters) 146 res.params = this._buildParameters(this._resource.formParameters); 147 return res; 148 }, 149 150 _buildParameters: function(parameters) 151 { 152 return parameters.slice(); 153 }, 154 155 _buildCookies: function(cookies) 156 { 157 return cookies.map(this._buildCookie.bind(this)); 158 }, 159 160 _buildCookie: function(cookie) 161 { 162 163 return { 164 name: cookie.name, 165 value: cookie.value, 166 path: cookie.path, 167 domain: cookie.domain, 168 expires: cookie.expires(new Date(this._resource.startTime * 1000)), 169 httpOnly: cookie.httpOnly, 170 secure: cookie.secure 171 }; 172 }, 173 174 _interval: function(start, end) 175 { 176 var timing = this._resource.timing; 177 if (!timing) 178 return -1; 179 var startTime = timing[start]; 180 return typeof startTime !== "number" || startTime === -1 ? -1 : Math.round(timing[end] - startTime); 181 } 182 } 183 184 WebInspector.HAREntry._toMilliseconds = function(time) 185 { 186 return time === -1 ? -1 : Math.round(time * 1000); 187 } 188 189 WebInspector.HARLog = function() 190 { 191 } 192 193 WebInspector.HARLog.prototype = { 194 build: function() 195 { 196 var webKitVersion = /AppleWebKit\/([^ ]+)/.exec(window.navigator.userAgent); 197 198 return { 199 version: "1.2", 200 creator: { 201 name: "WebInspector", 202 version: webKitVersion ? webKitVersion[1] : "n/a" 203 }, 204 pages: this._buildPages(), 205 entries: WebInspector.networkResources.map(this._convertResource.bind(this)) 206 } 207 }, 208 209 _buildPages: function() 210 { 211 return [ 212 { 213 startedDateTime: new Date(WebInspector.mainResource.startTime * 1000), 214 id: WebInspector.mainResource.documentURL, 215 title: "", 216 pageTimings: this.buildMainResourceTimings() 217 } 218 ]; 219 }, 220 221 buildMainResourceTimings: function() 222 { 223 return { 224 onContentLoad: this._pageEventTime(WebInspector.mainResourceDOMContentTime), 225 onLoad: this._pageEventTime(WebInspector.mainResourceLoadTime), 226 } 227 }, 228 229 _convertResource: function(resource) 230 { 231 return (new WebInspector.HAREntry(resource)).build(); 232 }, 233 234 _pageEventTime: function(time) 235 { 236 var startTime = WebInspector.mainResource.startTime; 237 if (time === -1 || startTime === -1) 238 return -1; 239 return WebInspector.HAREntry._toMilliseconds(time - startTime); 240 } 241 } 242