1/* 2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) IBM Corp. 2009 All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 15 * its contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30WebInspector.ResourceView = function(resource) 31{ 32 WebInspector.View.call(this); 33 34 this.element.addStyleClass("resource-view"); 35 36 this.resource = resource; 37 38 this.tabsElement = document.createElement("div"); 39 this.tabsElement.className = "scope-bar"; 40 this.element.appendChild(this.tabsElement); 41 42 this.headersTabElement = document.createElement("li"); 43 this.headersTabElement.textContent = WebInspector.UIString("Headers"); 44 this.contentTabElement = document.createElement("li"); 45 this.contentTabElement.textContent = WebInspector.UIString("Content"); 46 this.tabsElement.appendChild(this.headersTabElement); 47 this.tabsElement.appendChild(this.contentTabElement); 48 49 this.headersTabElement.addEventListener("click", this._selectHeadersTab.bind(this), false); 50 this.contentTabElement.addEventListener("click", this._selectContentTab.bind(this), false); 51 52 this.headersElement = document.createElement("div"); 53 this.headersElement.className = "resource-view-headers"; 54 this.element.appendChild(this.headersElement); 55 56 this.contentElement = document.createElement("div"); 57 this.contentElement.className = "resource-view-content"; 58 this.element.appendChild(this.contentElement); 59 60 this.headersListElement = document.createElement("ol"); 61 this.headersListElement.className = "outline-disclosure"; 62 this.headersElement.appendChild(this.headersListElement); 63 64 this.headersTreeOutline = new TreeOutline(this.headersListElement); 65 this.headersTreeOutline.expandTreeElementsWhenArrowing = true; 66 67 this.urlTreeElement = new TreeElement("", null, false); 68 this.urlTreeElement.selectable = false; 69 this.headersTreeOutline.appendChild(this.urlTreeElement); 70 71 this.requestMethodTreeElement = new TreeElement("", null, false); 72 this.requestMethodTreeElement.selectable = false; 73 this.headersTreeOutline.appendChild(this.requestMethodTreeElement); 74 75 this.statusCodeTreeElement = new TreeElement("", null, false); 76 this.statusCodeTreeElement.selectable = false; 77 this.headersTreeOutline.appendChild(this.statusCodeTreeElement); 78 79 this.requestHeadersTreeElement = new TreeElement("", null, true); 80 this.requestHeadersTreeElement.expanded = true; 81 this.requestHeadersTreeElement.selectable = false; 82 this.headersTreeOutline.appendChild(this.requestHeadersTreeElement); 83 84 this._decodeHover = WebInspector.UIString("Double-Click to toggle between URL encoded and decoded formats"); 85 this._decodeRequestParameters = true; 86 87 this.queryStringTreeElement = new TreeElement("", null, true); 88 this.queryStringTreeElement.expanded = true; 89 this.queryStringTreeElement.selectable = false; 90 this.queryStringTreeElement.hidden = true; 91 this.headersTreeOutline.appendChild(this.queryStringTreeElement); 92 93 this.formDataTreeElement = new TreeElement("", null, true); 94 this.formDataTreeElement.expanded = true; 95 this.formDataTreeElement.selectable = false; 96 this.formDataTreeElement.hidden = true; 97 this.headersTreeOutline.appendChild(this.formDataTreeElement); 98 99 this.requestPayloadTreeElement = new TreeElement(WebInspector.UIString("Request Payload"), null, true); 100 this.requestPayloadTreeElement.expanded = true; 101 this.requestPayloadTreeElement.selectable = false; 102 this.requestPayloadTreeElement.hidden = true; 103 this.headersTreeOutline.appendChild(this.requestPayloadTreeElement); 104 105 this.responseHeadersTreeElement = new TreeElement("", null, true); 106 this.responseHeadersTreeElement.expanded = true; 107 this.responseHeadersTreeElement.selectable = false; 108 this.headersTreeOutline.appendChild(this.responseHeadersTreeElement); 109 110 this.headersVisible = true; 111 112 resource.addEventListener("url changed", this._refreshURL, this); 113 resource.addEventListener("requestHeaders changed", this._refreshRequestHeaders, this); 114 resource.addEventListener("responseHeaders changed", this._refreshResponseHeaders, this); 115 resource.addEventListener("finished", this._refreshHTTPInformation, this); 116 117 this._refreshURL(); 118 this._refreshRequestHeaders(); 119 this._refreshResponseHeaders(); 120 this._refreshHTTPInformation(); 121 this._selectTab(); 122} 123 124WebInspector.ResourceView.prototype = { 125 attach: function() 126 { 127 if (!this.element.parentNode) { 128 var parentElement = (document.getElementById("resource-views") || document.getElementById("script-resource-views")); 129 if (parentElement) 130 parentElement.appendChild(this.element); 131 } 132 }, 133 134 show: function(parentElement) 135 { 136 WebInspector.View.prototype.show.call(this, parentElement); 137 this._selectTab(); 138 }, 139 140 set headersVisible(x) 141 { 142 if (x === this._headersVisible) 143 return; 144 this._headersVisible = x; 145 if (x) 146 this.element.addStyleClass("headers-visible"); 147 else 148 this.element.removeStyleClass("headers-visible"); 149 this._selectTab(); 150 }, 151 152 _selectTab: function() 153 { 154 if (this._headersVisible) { 155 if (WebInspector.settings.resourceViewTab === "headers") 156 this._selectHeadersTab(); 157 else 158 this._selectContentTab(); 159 } else 160 this._innerSelectContentTab(); 161 }, 162 163 _selectHeadersTab: function() 164 { 165 WebInspector.settings.resourceViewTab = "headers"; 166 this.headersTabElement.addStyleClass("selected"); 167 this.contentTabElement.removeStyleClass("selected"); 168 this.headersElement.removeStyleClass("hidden"); 169 this.contentElement.addStyleClass("hidden"); 170 }, 171 172 _selectContentTab: function() 173 { 174 WebInspector.settings.resourceViewTab = "content"; 175 this._innerSelectContentTab(); 176 }, 177 178 _innerSelectContentTab: function() 179 { 180 this.contentTabElement.addStyleClass("selected"); 181 this.headersTabElement.removeStyleClass("selected"); 182 this.contentElement.removeStyleClass("hidden"); 183 this.headersElement.addStyleClass("hidden"); 184 if ("resize" in this) 185 this.resize(); 186 if ("contentTabSelected" in this) 187 this.contentTabSelected(); 188 }, 189 190 _refreshURL: function() 191 { 192 this.urlTreeElement.title = "<div class=\"header-name\">" + WebInspector.UIString("Request URL") + ":</div>" + 193 "<div class=\"header-value source-code\">" + this.resource.url.escapeHTML() + "</div>"; 194 }, 195 196 _refreshQueryString: function() 197 { 198 var url = this.resource.url; 199 var hasQueryString = url.indexOf("?") >= 0; 200 201 if (!hasQueryString) { 202 this.queryStringTreeElement.hidden = true; 203 return; 204 } 205 206 this.queryStringTreeElement.hidden = false; 207 var parmString = url.split("?", 2)[1]; 208 this._refreshParms(WebInspector.UIString("Query String Parameters"), parmString, this.queryStringTreeElement); 209 }, 210 211 _refreshFormData: function() 212 { 213 this.formDataTreeElement.hidden = true; 214 this.requestPayloadTreeElement.hidden = true; 215 216 var isFormData = this.resource.requestFormData; 217 if (!isFormData) 218 return; 219 220 var isFormEncoded = false; 221 var requestContentType = this._getHeaderValue(this.resource.requestHeaders, "Content-Type"); 222 if (requestContentType && requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i)) 223 isFormEncoded = true; 224 225 if (isFormEncoded) { 226 this.formDataTreeElement.hidden = false; 227 this._refreshParms(WebInspector.UIString("Form Data"), this.resource.requestFormData, this.formDataTreeElement); 228 } else { 229 this.requestPayloadTreeElement.hidden = false; 230 this._refreshRequestPayload(this.resource.requestFormData); 231 } 232 }, 233 234 _refreshRequestPayload: function(formData) 235 { 236 this.requestPayloadTreeElement.removeChildren(); 237 238 var title = "<div class=\"header-name\"> </div>"; 239 title += "<div class=\"raw-form-data header-value source-code\">" + formData.escapeHTML() + "</div>"; 240 var parmTreeElement = new TreeElement(title, null, false); 241 parmTreeElement.selectable = false; 242 this.requestPayloadTreeElement.appendChild(parmTreeElement); 243 }, 244 245 _refreshParms: function(title, parmString, parmsTreeElement) 246 { 247 var parms = parmString.split("&"); 248 for (var i = 0; i < parms.length; ++i) { 249 var parm = parms[i]; 250 parm = parm.split("=", 2); 251 if (parm.length == 1) 252 parm.push(""); 253 parms[i] = parm; 254 } 255 256 parmsTreeElement.removeChildren(); 257 258 parmsTreeElement.title = title + "<span class=\"header-count\">" + WebInspector.UIString(" (%d)", parms.length) + "</span>"; 259 260 for (var i = 0; i < parms.length; ++i) { 261 var key = parms[i][0]; 262 var value = parms[i][1]; 263 264 var errorDecoding = false; 265 if (this._decodeRequestParameters) { 266 if (value.indexOf("%") >= 0) { 267 try { 268 value = decodeURIComponent(value); 269 } catch(e) { 270 errorDecoding = true; 271 } 272 } 273 274 value = value.replace(/\+/g, " "); 275 } 276 277 valueEscaped = value.escapeHTML(); 278 if (errorDecoding) 279 valueEscaped += " <span class=\"error-message\">" + WebInspector.UIString("(unable to decode value)").escapeHTML() + "</span>"; 280 281 var title = "<div class=\"header-name\">" + key.escapeHTML() + ":</div>"; 282 title += "<div class=\"header-value source-code\">" + valueEscaped + "</div>"; 283 284 var parmTreeElement = new TreeElement(title, null, false); 285 parmTreeElement.selectable = false; 286 parmTreeElement.tooltip = this._decodeHover; 287 parmTreeElement.ondblclick = this._toggleURLdecoding.bind(this); 288 parmsTreeElement.appendChild(parmTreeElement); 289 } 290 }, 291 292 _toggleURLdecoding: function(event) 293 { 294 this._decodeRequestParameters = !this._decodeRequestParameters; 295 this._refreshQueryString(); 296 this._refreshFormData(); 297 }, 298 299 _getHeaderValue: function(headers, key) 300 { 301 var lowerKey = key.toLowerCase(); 302 for (var testKey in headers) { 303 if (testKey.toLowerCase() === lowerKey) 304 return headers[testKey]; 305 } 306 }, 307 308 _refreshRequestHeaders: function() 309 { 310 this._refreshHeaders(WebInspector.UIString("Request Headers"), this.resource.sortedRequestHeaders, this.requestHeadersTreeElement); 311 this._refreshFormData(); 312 }, 313 314 _refreshResponseHeaders: function() 315 { 316 this._refreshHeaders(WebInspector.UIString("Response Headers"), this.resource.sortedResponseHeaders, this.responseHeadersTreeElement); 317 }, 318 319 _refreshHTTPInformation: function() 320 { 321 var requestMethodElement = this.requestMethodTreeElement; 322 requestMethodElement.hidden = !this.resource.statusCode; 323 var statusCodeElement = this.statusCodeTreeElement; 324 statusCodeElement.hidden = !this.resource.statusCode; 325 var statusCodeImage = ""; 326 327 if (this.resource.statusCode) { 328 var statusImageSource = ""; 329 if (this.resource.statusCode < 300) 330 statusImageSource = "Images/successGreenDot.png"; 331 else if (this.resource.statusCode < 400) 332 statusImageSource = "Images/warningOrangeDot.png"; 333 else 334 statusImageSource = "Images/errorRedDot.png"; 335 statusCodeImage = "<img class=\"resource-status-image\" src=\"" + statusImageSource + "\" title=\"" + WebInspector.Resource.StatusTextForCode(this.resource.statusCode) + "\">"; 336 337 requestMethodElement.title = "<div class=\"header-name\">" + WebInspector.UIString("Request Method") + ":</div>" + 338 "<div class=\"header-value source-code\">" + this.resource.requestMethod + "</div>"; 339 340 statusCodeElement.title = "<div class=\"header-name\">" + WebInspector.UIString("Status Code") + ":</div>" + 341 statusCodeImage + "<div class=\"header-value source-code\">" + WebInspector.Resource.StatusTextForCode(this.resource.statusCode) + "</div>"; 342 } 343 }, 344 345 _refreshHeaders: function(title, headers, headersTreeElement) 346 { 347 headersTreeElement.removeChildren(); 348 349 var length = headers.length; 350 headersTreeElement.title = title.escapeHTML() + "<span class=\"header-count\">" + WebInspector.UIString(" (%d)", length) + "</span>"; 351 headersTreeElement.hidden = !length; 352 353 var length = headers.length; 354 for (var i = 0; i < length; ++i) { 355 var title = "<div class=\"header-name\">" + headers[i].header.escapeHTML() + ":</div>"; 356 title += "<div class=\"header-value source-code\">" + headers[i].value.escapeHTML() + "</div>" 357 358 var headerTreeElement = new TreeElement(title, null, false); 359 headerTreeElement.selectable = false; 360 headersTreeElement.appendChild(headerTreeElement); 361 } 362 } 363} 364 365WebInspector.ResourceView.prototype.__proto__ = WebInspector.View.prototype; 366