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