1 /* 2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) IBM Corp. 2009 All rights reserved. 4 * Copyright (C) 2010 Google Inc. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 16 * its contributors may be used to endorse or promote products derived 17 * from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /** 32 * @constructor 33 * @extends {WebInspector.View} 34 * @param {WebInspector.NetworkRequest} request 35 */ 36 WebInspector.RequestHeadersView = function(request) 37 { 38 WebInspector.View.call(this); 39 this.registerRequiredCSS("resourceView.css"); 40 this.element.addStyleClass("resource-headers-view"); 41 42 this._request = request; 43 44 this._headersListElement = document.createElement("ol"); 45 this._headersListElement.className = "outline-disclosure"; 46 this.element.appendChild(this._headersListElement); 47 48 this._headersTreeOutline = new TreeOutline(this._headersListElement); 49 this._headersTreeOutline.expandTreeElementsWhenArrowing = true; 50 51 this._urlTreeElement = new TreeElement("", null, false); 52 this._urlTreeElement.selectable = false; 53 this._headersTreeOutline.appendChild(this._urlTreeElement); 54 55 this._requestMethodTreeElement = new TreeElement("", null, false); 56 this._requestMethodTreeElement.selectable = false; 57 this._headersTreeOutline.appendChild(this._requestMethodTreeElement); 58 59 this._statusCodeTreeElement = new TreeElement("", null, false); 60 this._statusCodeTreeElement.selectable = false; 61 this._headersTreeOutline.appendChild(this._statusCodeTreeElement); 62 63 this._requestHeadersTreeElement = new TreeElement("", null, true); 64 this._requestHeadersTreeElement.expanded = true; 65 this._requestHeadersTreeElement.selectable = false; 66 this._headersTreeOutline.appendChild(this._requestHeadersTreeElement); 67 68 this._decodeRequestParameters = true; 69 70 this._showRequestHeadersText = false; 71 this._showResponseHeadersText = false; 72 73 this._queryStringTreeElement = new TreeElement("", null, true); 74 this._queryStringTreeElement.expanded = true; 75 this._queryStringTreeElement.selectable = false; 76 this._queryStringTreeElement.hidden = true; 77 this._headersTreeOutline.appendChild(this._queryStringTreeElement); 78 79 this._urlFragmentTreeElement = new TreeElement("", null, true); 80 this._urlFragmentTreeElement.expanded = true; 81 this._urlFragmentTreeElement.selectable = false; 82 this._urlFragmentTreeElement.hidden = true; 83 this._headersTreeOutline.appendChild(this._urlFragmentTreeElement); 84 85 this._formDataTreeElement = new TreeElement("", null, true); 86 this._formDataTreeElement.expanded = true; 87 this._formDataTreeElement.selectable = false; 88 this._formDataTreeElement.hidden = true; 89 this._headersTreeOutline.appendChild(this._formDataTreeElement); 90 91 this._requestPayloadTreeElement = new TreeElement(WebInspector.UIString("Request Payload"), null, true); 92 this._requestPayloadTreeElement.expanded = true; 93 this._requestPayloadTreeElement.selectable = false; 94 this._requestPayloadTreeElement.hidden = true; 95 this._headersTreeOutline.appendChild(this._requestPayloadTreeElement); 96 97 this._responseHeadersTreeElement = new TreeElement("", null, true); 98 this._responseHeadersTreeElement.expanded = true; 99 this._responseHeadersTreeElement.selectable = false; 100 this._headersTreeOutline.appendChild(this._responseHeadersTreeElement); 101 } 102 103 WebInspector.RequestHeadersView.prototype = { 104 105 wasShown: function() 106 { 107 this._request.addEventListener(WebInspector.NetworkRequest.Events.RequestHeadersChanged, this._refreshRequestHeaders, this); 108 this._request.addEventListener(WebInspector.NetworkRequest.Events.ResponseHeadersChanged, this._refreshResponseHeaders, this); 109 this._request.addEventListener(WebInspector.NetworkRequest.Events.FinishedLoading, this._refreshHTTPInformation, this); 110 111 this._refreshURL(); 112 this._refreshQueryString(); 113 this._refreshUrlFragment(); 114 this._refreshRequestHeaders(); 115 this._refreshResponseHeaders(); 116 this._refreshHTTPInformation(); 117 }, 118 119 willHide: function() 120 { 121 this._request.removeEventListener(WebInspector.NetworkRequest.Events.RequestHeadersChanged, this._refreshRequestHeaders, this); 122 this._request.removeEventListener(WebInspector.NetworkRequest.Events.ResponseHeadersChanged, this._refreshResponseHeaders, this); 123 this._request.removeEventListener(WebInspector.NetworkRequest.Events.FinishedLoading, this._refreshHTTPInformation, this); 124 }, 125 126 /** 127 * @param {string} name 128 * @param {string} value 129 */ 130 _formatHeader: function(name, value) 131 { 132 var fragment = document.createDocumentFragment(); 133 fragment.createChild("div", "header-name").textContent = name + ":"; 134 fragment.createChild("div", "header-value source-code").textContent = value; 135 136 return fragment; 137 }, 138 139 /** 140 * @param {string} value 141 * @param {string} className 142 * @param {boolean} decodeParameters 143 */ 144 _formatParameter: function(value, className, decodeParameters) 145 { 146 var errorDecoding = false; 147 148 if (decodeParameters) { 149 value = value.replace(/\+/g, " "); 150 if (value.indexOf("%") >= 0) { 151 try { 152 value = decodeURIComponent(value); 153 } catch (e) { 154 errorDecoding = true; 155 } 156 } 157 } 158 var div = document.createElement("div"); 159 div.className = className; 160 if (errorDecoding) 161 div.createChild("span", "error-message").textContent = WebInspector.UIString("(unable to decode value)"); 162 else 163 div.textContent = value; 164 return div; 165 }, 166 167 _refreshURL: function() 168 { 169 this._urlTreeElement.title = this._formatHeader(WebInspector.UIString("Request URL"), this._request.url); 170 }, 171 172 _refreshQueryString: function() 173 { 174 var queryString = this._request.queryString(); 175 var queryParameters = this._request.queryParameters; 176 this._queryStringTreeElement.hidden = !queryParameters; 177 if (queryParameters) 178 this._refreshParams(WebInspector.UIString("Query String Parameters"), queryParameters, queryString, this._queryStringTreeElement); 179 }, 180 181 _refreshUrlFragment: function() 182 { 183 var urlFragment = this._request.parsedURL.fragment; 184 this._urlFragmentTreeElement.hidden = !urlFragment; 185 186 if (!urlFragment) 187 return; 188 189 var sectionTitle = WebInspector.UIString("URL fragment"); 190 191 this._urlFragmentTreeElement.removeChildren(); 192 this._urlFragmentTreeElement.listItemElement.removeChildren(); 193 this._urlFragmentTreeElement.listItemElement.appendChild(document.createTextNode(sectionTitle)); 194 195 var fragmentTreeElement = new TreeElement(null, null, false); 196 fragmentTreeElement.title = this._formatHeader("#", urlFragment); 197 fragmentTreeElement.selectable = false; 198 this._urlFragmentTreeElement.appendChild(fragmentTreeElement); 199 }, 200 201 _refreshFormData: function() 202 { 203 this._formDataTreeElement.hidden = true; 204 this._requestPayloadTreeElement.hidden = true; 205 206 var formData = this._request.requestFormData; 207 if (!formData) 208 return; 209 210 var formParameters = this._request.formParameters; 211 if (formParameters) { 212 this._formDataTreeElement.hidden = false; 213 this._refreshParams(WebInspector.UIString("Form Data"), formParameters, formData, this._formDataTreeElement); 214 } else { 215 this._requestPayloadTreeElement.hidden = false; 216 try { 217 var json = JSON.parse(formData); 218 this._refreshRequestJSONPayload(json, formData, false); 219 } catch (e) { 220 this._populateTreeElementWithSourceText(this._requestPayloadTreeElement, formData); 221 } 222 } 223 }, 224 225 _populateTreeElementWithSourceText: function(treeElement, sourceText) 226 { 227 treeElement.removeChildren(); 228 229 var sourceTreeElement = new TreeElement(null, null, false); 230 sourceTreeElement.selectable = false; 231 treeElement.appendChild(sourceTreeElement); 232 233 var sourceTextElement = document.createElement("span"); 234 sourceTextElement.addStyleClass("header-value"); 235 sourceTextElement.addStyleClass("source-code"); 236 sourceTextElement.textContent = String(sourceText).trim(); 237 sourceTreeElement.listItemElement.appendChild(sourceTextElement); 238 }, 239 240 _refreshParams: function(title, params, sourceText, paramsTreeElement) 241 { 242 paramsTreeElement.removeChildren(); 243 244 paramsTreeElement.listItemElement.removeChildren(); 245 paramsTreeElement.listItemElement.appendChild(document.createTextNode(title)); 246 247 var headerCount = document.createElement("span"); 248 headerCount.addStyleClass("header-count"); 249 headerCount.textContent = WebInspector.UIString(" (%d)", params.length); 250 paramsTreeElement.listItemElement.appendChild(headerCount); 251 252 function toggleViewSource() 253 { 254 paramsTreeElement._viewSource = !paramsTreeElement._viewSource; 255 this._refreshParams(title, params, sourceText, paramsTreeElement); 256 } 257 258 paramsTreeElement.listItemElement.appendChild(this._createViewSourceToggle(paramsTreeElement._viewSource, toggleViewSource.bind(this))); 259 260 if (paramsTreeElement._viewSource) { 261 this._populateTreeElementWithSourceText(paramsTreeElement, sourceText); 262 return; 263 } 264 265 var toggleTitle = this._decodeRequestParameters ? WebInspector.UIString("view URL encoded") : WebInspector.UIString("view decoded"); 266 var toggleButton = this._createToggleButton(toggleTitle); 267 toggleButton.addEventListener("click", this._toggleURLDecoding.bind(this)); 268 paramsTreeElement.listItemElement.appendChild(toggleButton); 269 270 for (var i = 0; i < params.length; ++i) { 271 var paramNameValue = document.createDocumentFragment(); 272 var name = this._formatParameter(params[i].name + ":", "header-name", this._decodeRequestParameters); 273 var value = this._formatParameter(params[i].value, "header-value source-code", this._decodeRequestParameters); 274 paramNameValue.appendChild(name); 275 paramNameValue.appendChild(value); 276 277 var parmTreeElement = new TreeElement(paramNameValue, null, false); 278 parmTreeElement.selectable = false; 279 paramsTreeElement.appendChild(parmTreeElement); 280 } 281 }, 282 283 /** 284 * @param {*} parsedObject 285 * @param {string} sourceText 286 * @param {boolean} viewSource 287 */ 288 _refreshRequestJSONPayload: function(parsedObject, sourceText, viewSource) 289 { 290 this._requestPayloadTreeElement.removeChildren(); 291 292 var listItem = this._requestPayloadTreeElement.listItemElement; 293 listItem.removeChildren(); 294 listItem.appendChild(document.createTextNode(this._requestPayloadTreeElement.title)); 295 296 var setViewSource = this._refreshRequestJSONPayload.bind(this, parsedObject, sourceText); 297 298 if (viewSource) { 299 listItem.appendChild(this._createViewSourceToggle(true, setViewSource.bind(this, false))); 300 this._populateTreeElementWithSourceText(this._requestPayloadTreeElement, sourceText); 301 } else { 302 listItem.appendChild(this._createViewSourceToggle(false, setViewSource.bind(this, true))); 303 var object = WebInspector.RemoteObject.fromLocalObject(parsedObject); 304 var section = new WebInspector.ObjectPropertiesSection(object, object.description); 305 section.expand(); 306 section.editable = false; 307 listItem.appendChild(section.element); 308 } 309 }, 310 311 /** 312 * @param {boolean} viewSource 313 * @param {Function} handler 314 */ 315 _createViewSourceToggle: function(viewSource, handler) 316 { 317 var viewSourceToggleTitle = viewSource ? WebInspector.UIString("view parsed") : WebInspector.UIString("view source"); 318 var viewSourceToggleButton = this._createToggleButton(viewSourceToggleTitle); 319 viewSourceToggleButton.addEventListener("click", handler); 320 return viewSourceToggleButton; 321 }, 322 323 _toggleURLDecoding: function(event) 324 { 325 this._decodeRequestParameters = !this._decodeRequestParameters; 326 this._refreshQueryString(); 327 this._refreshFormData(); 328 }, 329 330 _getHeaderValue: function(headers, key) 331 { 332 var lowerKey = key.toLowerCase(); 333 for (var testKey in headers) { 334 if (testKey.toLowerCase() === lowerKey) 335 return headers[testKey]; 336 } 337 }, 338 339 _refreshRequestHeaders: function() 340 { 341 if (this._showRequestHeadersText) 342 this._refreshHeadersText(WebInspector.UIString("Request Headers"), this._request.sortedRequestHeaders, this._request.requestHeadersText, this._requestHeadersTreeElement); 343 else 344 this._refreshHeaders(WebInspector.UIString("Request Headers"), this._request.sortedRequestHeaders, this._requestHeadersTreeElement); 345 346 if (this._request.requestHeadersText) { 347 var toggleButton = this._createHeadersToggleButton(this._showRequestHeadersText); 348 toggleButton.addEventListener("click", this._toggleRequestHeadersText.bind(this)); 349 this._requestHeadersTreeElement.listItemElement.appendChild(toggleButton); 350 } 351 352 this._refreshFormData(); 353 }, 354 355 _refreshResponseHeaders: function() 356 { 357 if (this._showResponseHeadersText) 358 this._refreshHeadersText(WebInspector.UIString("Response Headers"), this._request.sortedResponseHeaders, this._request.responseHeadersText, this._responseHeadersTreeElement); 359 else 360 this._refreshHeaders(WebInspector.UIString("Response Headers"), this._request.sortedResponseHeaders, this._responseHeadersTreeElement); 361 362 if (this._request.responseHeadersText) { 363 var toggleButton = this._createHeadersToggleButton(this._showResponseHeadersText); 364 toggleButton.addEventListener("click", this._toggleResponseHeadersText.bind(this)); 365 this._responseHeadersTreeElement.listItemElement.appendChild(toggleButton); 366 } 367 }, 368 369 _refreshHTTPInformation: function() 370 { 371 var requestMethodElement = this._requestMethodTreeElement; 372 requestMethodElement.hidden = !this._request.statusCode; 373 var statusCodeElement = this._statusCodeTreeElement; 374 statusCodeElement.hidden = !this._request.statusCode; 375 376 if (this._request.statusCode) { 377 var statusCodeFragment = document.createDocumentFragment(); 378 statusCodeFragment.createChild("div", "header-name").textContent = WebInspector.UIString("Status Code") + ":"; 379 380 var statusCodeImage = statusCodeFragment.createChild("div", "resource-status-image"); 381 statusCodeImage.title = this._request.statusCode + " " + this._request.statusText; 382 383 if (this._request.statusCode < 300 || this._request.statusCode === 304) 384 statusCodeImage.addStyleClass("green-ball"); 385 else if (this._request.statusCode < 400) 386 statusCodeImage.addStyleClass("orange-ball"); 387 else 388 statusCodeImage.addStyleClass("red-ball"); 389 390 requestMethodElement.title = this._formatHeader(WebInspector.UIString("Request Method"), this._request.requestMethod); 391 392 var value = statusCodeFragment.createChild("div", "header-value source-code"); 393 value.textContent = this._request.statusCode + " " + this._request.statusText; 394 if (this._request.cached) 395 value.createChild("span", "status-from-cache").textContent = " " + WebInspector.UIString("(from cache)"); 396 397 statusCodeElement.title = statusCodeFragment; 398 } 399 }, 400 401 _refreshHeadersTitle: function(title, headersTreeElement, headersLength) 402 { 403 headersTreeElement.listItemElement.removeChildren(); 404 headersTreeElement.listItemElement.appendChild(document.createTextNode(title)); 405 406 var headerCount = document.createElement("span"); 407 headerCount.addStyleClass("header-count"); 408 headerCount.textContent = WebInspector.UIString(" (%d)", headersLength); 409 headersTreeElement.listItemElement.appendChild(headerCount); 410 }, 411 412 _refreshHeaders: function(title, headers, headersTreeElement) 413 { 414 headersTreeElement.removeChildren(); 415 416 var length = headers.length; 417 this._refreshHeadersTitle(title, headersTreeElement, length); 418 headersTreeElement.hidden = !length; 419 for (var i = 0; i < length; ++i) { 420 var headerTreeElement = new TreeElement(null, null, false); 421 headerTreeElement.title = this._formatHeader(headers[i].name, headers[i].value); 422 headerTreeElement.selectable = false; 423 headersTreeElement.appendChild(headerTreeElement); 424 } 425 }, 426 427 _refreshHeadersText: function(title, headers, headersText, headersTreeElement) 428 { 429 this._populateTreeElementWithSourceText(headersTreeElement, headersText); 430 this._refreshHeadersTitle(title, headersTreeElement, headers.length); 431 }, 432 433 _toggleRequestHeadersText: function(event) 434 { 435 this._showRequestHeadersText = !this._showRequestHeadersText; 436 this._refreshRequestHeaders(); 437 }, 438 439 _toggleResponseHeadersText: function(event) 440 { 441 this._showResponseHeadersText = !this._showResponseHeadersText; 442 this._refreshResponseHeaders(); 443 }, 444 445 _createToggleButton: function(title) 446 { 447 var button = document.createElement("span"); 448 button.addStyleClass("header-toggle"); 449 button.textContent = title; 450 return button; 451 }, 452 453 _createHeadersToggleButton: function(isHeadersTextShown) 454 { 455 var toggleTitle = isHeadersTextShown ? WebInspector.UIString("view parsed") : WebInspector.UIString("view source"); 456 return this._createToggleButton(toggleTitle); 457 }, 458 459 __proto__: WebInspector.View.prototype 460 } 461