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 */ 36WebInspector.RequestHeadersView = function(request) 37{ 38 WebInspector.View.call(this); 39 this.registerRequiredCSS("resourceView.css"); 40 this.element.classList.add("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._formDataTreeElement = new TreeElement("", null, true); 80 this._formDataTreeElement.expanded = true; 81 this._formDataTreeElement.selectable = false; 82 this._formDataTreeElement.hidden = true; 83 this._headersTreeOutline.appendChild(this._formDataTreeElement); 84 85 this._requestPayloadTreeElement = new TreeElement(WebInspector.UIString("Request Payload"), null, true); 86 this._requestPayloadTreeElement.expanded = true; 87 this._requestPayloadTreeElement.selectable = false; 88 this._requestPayloadTreeElement.hidden = true; 89 this._headersTreeOutline.appendChild(this._requestPayloadTreeElement); 90 91 this._responseHeadersTreeElement = new TreeElement("", null, true); 92 this._responseHeadersTreeElement.expanded = true; 93 this._responseHeadersTreeElement.selectable = false; 94 this._headersTreeOutline.appendChild(this._responseHeadersTreeElement); 95} 96 97WebInspector.RequestHeadersView.prototype = { 98 99 wasShown: function() 100 { 101 this._request.addEventListener(WebInspector.NetworkRequest.Events.RequestHeadersChanged, this._refreshRequestHeaders, this); 102 this._request.addEventListener(WebInspector.NetworkRequest.Events.ResponseHeadersChanged, this._refreshResponseHeaders, this); 103 this._request.addEventListener(WebInspector.NetworkRequest.Events.FinishedLoading, this._refreshHTTPInformation, this); 104 105 this._refreshURL(); 106 this._refreshQueryString(); 107 this._refreshRequestHeaders(); 108 this._refreshResponseHeaders(); 109 this._refreshHTTPInformation(); 110 }, 111 112 willHide: function() 113 { 114 this._request.removeEventListener(WebInspector.NetworkRequest.Events.RequestHeadersChanged, this._refreshRequestHeaders, this); 115 this._request.removeEventListener(WebInspector.NetworkRequest.Events.ResponseHeadersChanged, this._refreshResponseHeaders, this); 116 this._request.removeEventListener(WebInspector.NetworkRequest.Events.FinishedLoading, this._refreshHTTPInformation, this); 117 }, 118 119 /** 120 * @param {string} name 121 * @param {string} value 122 * @return {!DocumentFragment} 123 */ 124 _formatHeader: function(name, value) 125 { 126 var fragment = document.createDocumentFragment(); 127 fragment.createChild("div", "header-name").textContent = name + ":"; 128 fragment.createChild("div", "header-value source-code").textContent = value; 129 130 return fragment; 131 }, 132 133 /** 134 * @param {string} value 135 * @param {string} className 136 * @param {boolean} decodeParameters 137 * @return {!Element} 138 */ 139 _formatParameter: function(value, className, decodeParameters) 140 { 141 var errorDecoding = false; 142 143 if (decodeParameters) { 144 value = value.replace(/\+/g, " "); 145 if (value.indexOf("%") >= 0) { 146 try { 147 value = decodeURIComponent(value); 148 } catch (e) { 149 errorDecoding = true; 150 } 151 } 152 } 153 var div = document.createElement("div"); 154 div.className = className; 155 if (errorDecoding) 156 div.createChild("span", "error-message").textContent = WebInspector.UIString("(unable to decode value)"); 157 else 158 div.textContent = value; 159 return div; 160 }, 161 162 _refreshURL: function() 163 { 164 this._urlTreeElement.title = this._formatHeader(WebInspector.UIString("Request URL"), this._request.url); 165 }, 166 167 _refreshQueryString: function() 168 { 169 var queryString = this._request.queryString(); 170 var queryParameters = this._request.queryParameters; 171 this._queryStringTreeElement.hidden = !queryParameters; 172 if (queryParameters) 173 this._refreshParams(WebInspector.UIString("Query String Parameters"), queryParameters, queryString, this._queryStringTreeElement); 174 }, 175 176 _refreshFormData: function() 177 { 178 this._formDataTreeElement.hidden = true; 179 this._requestPayloadTreeElement.hidden = true; 180 181 var formData = this._request.requestFormData; 182 if (!formData) 183 return; 184 185 var formParameters = this._request.formParameters; 186 if (formParameters) { 187 this._formDataTreeElement.hidden = false; 188 this._refreshParams(WebInspector.UIString("Form Data"), formParameters, formData, this._formDataTreeElement); 189 } else { 190 this._requestPayloadTreeElement.hidden = false; 191 try { 192 var json = JSON.parse(formData); 193 this._refreshRequestJSONPayload(json, formData); 194 } catch (e) { 195 this._populateTreeElementWithSourceText(this._requestPayloadTreeElement, formData); 196 } 197 } 198 }, 199 200 /** 201 * @param {!TreeElement} treeElement 202 * @param {?string} sourceText 203 */ 204 _populateTreeElementWithSourceText: function(treeElement, sourceText) 205 { 206 var sourceTextElement = document.createElement("span"); 207 sourceTextElement.classList.add("header-value"); 208 sourceTextElement.classList.add("source-code"); 209 sourceTextElement.textContent = String(sourceText || "").trim(); 210 211 var sourceTreeElement = new TreeElement(sourceTextElement); 212 sourceTreeElement.selectable = false; 213 treeElement.removeChildren(); 214 treeElement.appendChild(sourceTreeElement); 215 }, 216 217 /** 218 * @param {string} title 219 * @param {?Array.<!WebInspector.NetworkRequest.NameValue>} params 220 * @param {?string} sourceText 221 * @param {!TreeElement} paramsTreeElement 222 */ 223 _refreshParams: function(title, params, sourceText, paramsTreeElement) 224 { 225 paramsTreeElement.removeChildren(); 226 227 paramsTreeElement.listItemElement.removeChildren(); 228 paramsTreeElement.listItemElement.appendChild(document.createTextNode(title)); 229 230 var headerCount = document.createElement("span"); 231 headerCount.classList.add("header-count"); 232 headerCount.textContent = WebInspector.UIString(" (%d)", params.length); 233 paramsTreeElement.listItemElement.appendChild(headerCount); 234 235 /** 236 * @param {?Event} event 237 * @this {WebInspector.RequestHeadersView} 238 */ 239 function toggleViewSource(event) 240 { 241 paramsTreeElement._viewSource = !paramsTreeElement._viewSource; 242 this._refreshParams(title, params, sourceText, paramsTreeElement); 243 } 244 245 paramsTreeElement.listItemElement.appendChild(this._createViewSourceToggle(paramsTreeElement._viewSource, toggleViewSource.bind(this))); 246 247 if (paramsTreeElement._viewSource) { 248 this._populateTreeElementWithSourceText(paramsTreeElement, sourceText); 249 return; 250 } 251 252 var toggleTitle = this._decodeRequestParameters ? WebInspector.UIString("view URL encoded") : WebInspector.UIString("view decoded"); 253 var toggleButton = this._createToggleButton(toggleTitle); 254 toggleButton.addEventListener("click", this._toggleURLDecoding.bind(this), false); 255 paramsTreeElement.listItemElement.appendChild(toggleButton); 256 257 for (var i = 0; i < params.length; ++i) { 258 var paramNameValue = document.createDocumentFragment(); 259 var name = this._formatParameter(params[i].name + ":", "header-name", this._decodeRequestParameters); 260 var value = this._formatParameter(params[i].value, "header-value source-code", this._decodeRequestParameters); 261 paramNameValue.appendChild(name); 262 paramNameValue.appendChild(value); 263 264 var parmTreeElement = new TreeElement(paramNameValue, null, false); 265 parmTreeElement.selectable = false; 266 paramsTreeElement.appendChild(parmTreeElement); 267 } 268 }, 269 270 /** 271 * @param {*} parsedObject 272 * @param {string} sourceText 273 */ 274 _refreshRequestJSONPayload: function(parsedObject, sourceText) 275 { 276 var treeElement = this._requestPayloadTreeElement; 277 treeElement.removeChildren(); 278 279 var listItem = this._requestPayloadTreeElement.listItemElement; 280 listItem.removeChildren(); 281 listItem.appendChild(document.createTextNode(this._requestPayloadTreeElement.title)); 282 283 /** 284 * @param {?Event} event 285 * @this {WebInspector.RequestHeadersView} 286 */ 287 function toggleViewSource(event) 288 { 289 treeElement._viewSource = !treeElement._viewSource; 290 this._refreshRequestJSONPayload(parsedObject, sourceText); 291 } 292 293 listItem.appendChild(this._createViewSourceToggle(treeElement._viewSource, toggleViewSource.bind(this))); 294 if (treeElement._viewSource) { 295 this._populateTreeElementWithSourceText(this._requestPayloadTreeElement, sourceText); 296 } else { 297 var object = WebInspector.RemoteObject.fromLocalObject(parsedObject); 298 var section = new WebInspector.ObjectPropertiesSection(object, object.description); 299 section.expand(); 300 section.editable = false; 301 listItem.appendChild(section.element); 302 } 303 }, 304 305 /** 306 * @param {boolean} viewSource 307 * @param {function(?Event)} handler 308 * @return {!Element} 309 */ 310 _createViewSourceToggle: function(viewSource, handler) 311 { 312 var viewSourceToggleTitle = viewSource ? WebInspector.UIString("view parsed") : WebInspector.UIString("view source"); 313 var viewSourceToggleButton = this._createToggleButton(viewSourceToggleTitle); 314 viewSourceToggleButton.addEventListener("click", handler, false); 315 return viewSourceToggleButton; 316 }, 317 318 /** 319 * @param {?Event} event 320 */ 321 _toggleURLDecoding: function(event) 322 { 323 this._decodeRequestParameters = !this._decodeRequestParameters; 324 this._refreshQueryString(); 325 this._refreshFormData(); 326 }, 327 328 _refreshRequestHeaders: function() 329 { 330 var treeElement = this._requestHeadersTreeElement; 331 332 var headers = this._request.requestHeaders(); 333 headers = headers.slice(); 334 headers.sort(function(a, b) { return a.name.toLowerCase().compareTo(b.name.toLowerCase()) }); 335 var headersText = this._request.requestHeadersText(); 336 337 if (this._showRequestHeadersText && headersText) 338 this._refreshHeadersText(WebInspector.UIString("Request Headers"), headers.length, headersText, treeElement); 339 else 340 this._refreshHeaders(WebInspector.UIString("Request Headers"), headers, treeElement); 341 342 if (headersText === undefined) { 343 var caution = WebInspector.UIString(" CAUTION: Provisional headers are shown."); 344 treeElement.listItemElement.createChild("span", "caution").textContent = caution; 345 } 346 347 if (headersText) { 348 var toggleButton = this._createHeadersToggleButton(this._showRequestHeadersText); 349 toggleButton.addEventListener("click", this._toggleRequestHeadersText.bind(this), false); 350 treeElement.listItemElement.appendChild(toggleButton); 351 } 352 353 this._refreshFormData(); 354 }, 355 356 _refreshResponseHeaders: function() 357 { 358 var treeElement = this._responseHeadersTreeElement; 359 var headers = this._request.sortedResponseHeaders; 360 var headersText = this._request.responseHeadersText; 361 362 if (this._showResponseHeadersText) 363 this._refreshHeadersText(WebInspector.UIString("Response Headers"), headers.length, headersText, treeElement); 364 else 365 this._refreshHeaders(WebInspector.UIString("Response Headers"), headers, treeElement); 366 367 if (headersText) { 368 var toggleButton = this._createHeadersToggleButton(this._showResponseHeadersText); 369 toggleButton.addEventListener("click", this._toggleResponseHeadersText.bind(this), false); 370 treeElement.listItemElement.appendChild(toggleButton); 371 } 372 }, 373 374 _refreshHTTPInformation: function() 375 { 376 var requestMethodElement = this._requestMethodTreeElement; 377 requestMethodElement.hidden = !this._request.statusCode; 378 var statusCodeElement = this._statusCodeTreeElement; 379 statusCodeElement.hidden = !this._request.statusCode; 380 381 if (this._request.statusCode) { 382 var statusCodeFragment = document.createDocumentFragment(); 383 statusCodeFragment.createChild("div", "header-name").textContent = WebInspector.UIString("Status Code") + ":"; 384 385 var statusCodeImage = statusCodeFragment.createChild("div", "resource-status-image"); 386 statusCodeImage.title = this._request.statusCode + " " + this._request.statusText; 387 388 if (this._request.statusCode < 300 || this._request.statusCode === 304) 389 statusCodeImage.classList.add("green-ball"); 390 else if (this._request.statusCode < 400) 391 statusCodeImage.classList.add("orange-ball"); 392 else 393 statusCodeImage.classList.add("red-ball"); 394 395 requestMethodElement.title = this._formatHeader(WebInspector.UIString("Request Method"), this._request.requestMethod); 396 397 var value = statusCodeFragment.createChild("div", "header-value source-code"); 398 value.textContent = this._request.statusCode + " " + this._request.statusText; 399 if (this._request.cached) 400 value.createChild("span", "status-from-cache").textContent = " " + WebInspector.UIString("(from cache)"); 401 402 statusCodeElement.title = statusCodeFragment; 403 } 404 }, 405 406 /** 407 * @param {string} title 408 * @param {!TreeElement} headersTreeElement 409 * @param {number} headersLength 410 */ 411 _refreshHeadersTitle: function(title, headersTreeElement, headersLength) 412 { 413 headersTreeElement.listItemElement.removeChildren(); 414 headersTreeElement.listItemElement.createTextChild(title); 415 416 var headerCount = WebInspector.UIString(" (%d)", headersLength); 417 headersTreeElement.listItemElement.createChild("span", "header-count").textContent = headerCount; 418 }, 419 420 /** 421 * @param {string} title 422 * @param {!Array.<!WebInspector.NetworkRequest.NameValue>} headers 423 * @param {!TreeElement} headersTreeElement 424 */ 425 _refreshHeaders: function(title, headers, headersTreeElement) 426 { 427 headersTreeElement.removeChildren(); 428 429 var length = headers.length; 430 this._refreshHeadersTitle(title, headersTreeElement, length); 431 headersTreeElement.hidden = !length; 432 for (var i = 0; i < length; ++i) { 433 var headerTreeElement = new TreeElement(this._formatHeader(headers[i].name, headers[i].value)); 434 headerTreeElement.selectable = false; 435 headersTreeElement.appendChild(headerTreeElement); 436 } 437 }, 438 439 /** 440 * @param {string} title 441 * @param {number} count 442 * @param {string} headersText 443 * @param {!TreeElement} headersTreeElement 444 */ 445 _refreshHeadersText: function(title, count, headersText, headersTreeElement) 446 { 447 this._populateTreeElementWithSourceText(headersTreeElement, headersText); 448 this._refreshHeadersTitle(title, headersTreeElement, count); 449 }, 450 451 /** 452 * @param {?Event} event 453 */ 454 _toggleRequestHeadersText: function(event) 455 { 456 this._showRequestHeadersText = !this._showRequestHeadersText; 457 this._refreshRequestHeaders(); 458 }, 459 460 /** 461 * @param {?Event} event 462 */ 463 _toggleResponseHeadersText: function(event) 464 { 465 this._showResponseHeadersText = !this._showResponseHeadersText; 466 this._refreshResponseHeaders(); 467 }, 468 469 /** 470 * @param {string} title 471 * @return {!Element} 472 */ 473 _createToggleButton: function(title) 474 { 475 var button = document.createElement("span"); 476 button.classList.add("header-toggle"); 477 button.textContent = title; 478 return button; 479 }, 480 481 /** 482 * @param {boolean} isHeadersTextShown 483 * @return {!Element} 484 */ 485 _createHeadersToggleButton: function(isHeadersTextShown) 486 { 487 var toggleTitle = isHeadersTextShown ? WebInspector.UIString("view parsed") : WebInspector.UIString("view source"); 488 return this._createToggleButton(toggleTitle); 489 }, 490 491 __proto__: WebInspector.View.prototype 492} 493