1/* 2 * Copyright (C) 2013 Apple 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 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26WebInspector.ResourceDetailsSidebarPanel = function() { 27 WebInspector.DetailsSidebarPanel.call(this, "resource-details", WebInspector.UIString("Resource"), WebInspector.UIString("Resource"), "Images/NavigationItemFile.svg", "1"); 28 29 this.element.classList.add(WebInspector.ResourceDetailsSidebarPanel.StyleClassName); 30 31 this._resource = null; 32 33 this._typeMIMETypeRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("MIME Type")); 34 this._typeResourceTypeRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Resource Type")); 35 36 this._typeSection = new WebInspector.DetailsSection("resource-type", WebInspector.UIString("Type")); 37 this._typeSection.groups = [new WebInspector.DetailsSectionGroup([this._typeMIMETypeRow, this._typeResourceTypeRow])]; 38 39 this._locationFullURLRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Full URL")); 40 this._locationSchemeRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Scheme")); 41 this._locationHostRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Host")); 42 this._locationPortRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Port")); 43 this._locationPathRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Path")); 44 this._locationQueryStringRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Query String")); 45 this._locationFragmentRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Fragment")); 46 this._locationFilenameRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Filename")); 47 this._initiatorRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Initiator")); 48 49 var firstGroup = [this._locationFullURLRow]; 50 var secondGroup = [this._locationSchemeRow, this._locationHostRow, this._locationPortRow, this._locationPathRow, 51 this._locationQueryStringRow, this._locationFragmentRow, this._locationFilenameRow]; 52 var thirdGroup = [this._initiatorRow]; 53 54 this._fullURLGroup = new WebInspector.DetailsSectionGroup(firstGroup); 55 this._locationURLComponentsGroup = new WebInspector.DetailsSectionGroup(secondGroup); 56 this._initiatorGroup = new WebInspector.DetailsSectionGroup(thirdGroup); 57 58 this._locationSection = new WebInspector.DetailsSection("resource-location", WebInspector.UIString("Location"), [this._fullURLGroup, this._locationURLComponentsGroup, this._initiatorGroup]); 59 60 this._queryParametersRow = new WebInspector.DetailsSectionDataGridRow(null, WebInspector.UIString("No Query Parameters")); 61 this._queryParametersSection = new WebInspector.DetailsSection("resource-query-parameters", WebInspector.UIString("Query Parameters")); 62 this._queryParametersSection.groups = [new WebInspector.DetailsSectionGroup([this._queryParametersRow])]; 63 64 this._requestDataSection = new WebInspector.DetailsSection("resource-request-data", WebInspector.UIString("Request Data")); 65 66 this._requestMethodRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Method")); 67 this._cachedRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Cached")); 68 69 this._statusTextRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Status")); 70 this._statusCodeRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Code")); 71 72 this._encodedSizeRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Encoded")); 73 this._decodedSizeRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Decoded")); 74 this._transferSizeRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Transfered")); 75 76 this._compressedRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Compressed")); 77 this._compressionRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Compression")); 78 79 var requestGroup = new WebInspector.DetailsSectionGroup([this._requestMethodRow, this._cachedRow]); 80 var statusGroup = new WebInspector.DetailsSectionGroup([this._statusTextRow, this._statusCodeRow]); 81 var sizeGroup = new WebInspector.DetailsSectionGroup([this._encodedSizeRow, this._decodedSizeRow, this._transferSizeRow]); 82 var compressionGroup = new WebInspector.DetailsSectionGroup([this._compressedRow, this._compressionRow]); 83 84 this._requestAndResponseSection = new WebInspector.DetailsSection("resource-request-response", WebInspector.UIString("Request & Response"), [requestGroup, statusGroup, sizeGroup, compressionGroup]); 85 86 this._requestHeadersRow = new WebInspector.DetailsSectionDataGridRow(null, WebInspector.UIString("No Request Headers")); 87 this._requestHeadersSection = new WebInspector.DetailsSection("resource-request-headers", WebInspector.UIString("Request Headers")); 88 this._requestHeadersSection.groups = [new WebInspector.DetailsSectionGroup([this._requestHeadersRow])]; 89 90 this._responseHeadersRow = new WebInspector.DetailsSectionDataGridRow(null, WebInspector.UIString("No Response Headers")); 91 this._responseHeadersSection = new WebInspector.DetailsSection("resource-response-headers", WebInspector.UIString("Response Headers")); 92 this._responseHeadersSection.groups = [new WebInspector.DetailsSectionGroup([this._responseHeadersRow])]; 93 94 // Rows for the "Image Size" section. 95 this._imageWidthRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Width")); 96 this._imageHeightRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Height")); 97 98 // "Image Size" section where we display intrinsic metrics for image resources. 99 this._imageSizeSection = new WebInspector.DetailsSection("resource-type", WebInspector.UIString("Image Size")); 100 this._imageSizeSection.groups = [new WebInspector.DetailsSectionGroup([this._imageWidthRow, this._imageHeightRow])]; 101 102 this.element.appendChild(this._typeSection.element); 103 this.element.appendChild(this._locationSection.element); 104 this.element.appendChild(this._requestAndResponseSection.element); 105 this.element.appendChild(this._requestHeadersSection.element); 106 this.element.appendChild(this._responseHeadersSection.element); 107}; 108 109WebInspector.ResourceDetailsSidebarPanel.StyleClassName = "resource"; 110 111WebInspector.ResourceDetailsSidebarPanel.prototype = { 112 constructor: WebInspector.ResourceDetailsSidebarPanel, 113 114 // Public 115 116 inspect: function(objects) 117 { 118 // Convert to a single item array if needed. 119 if (!(objects instanceof Array)) 120 objects = [objects]; 121 122 var resourceToInspect = null; 123 124 // Iterate over the objects to find a WebInspector.Resource to inspect. 125 for (var i = 0; i < objects.length; ++i) { 126 if (objects[i] instanceof WebInspector.Resource) { 127 resourceToInspect = objects[i]; 128 break; 129 } 130 131 if (objects[i] instanceof WebInspector.Frame) { 132 resourceToInspect = objects[i].mainResource; 133 break; 134 } 135 } 136 137 this.resource = resourceToInspect; 138 139 return !!this._resource; 140 }, 141 142 get resource() 143 { 144 return this._resource; 145 }, 146 147 set resource(resource) 148 { 149 if (resource === this._resource) 150 return; 151 152 if (this._resource) { 153 this._resource.removeEventListener(WebInspector.Resource.Event.URLDidChange, this._refreshURL, this); 154 this._resource.removeEventListener(WebInspector.Resource.Event.MIMETypeDidChange, this._refreshMIMEType, this); 155 this._resource.removeEventListener(WebInspector.Resource.Event.TypeDidChange, this._refreshResourceType, this); 156 this._resource.removeEventListener(WebInspector.Resource.Event.RequestHeadersDidChange, this._refreshRequestHeaders, this); 157 this._resource.removeEventListener(WebInspector.Resource.Event.ResponseReceived, this._refreshRequestAndResponse, this); 158 this._resource.removeEventListener(WebInspector.Resource.Event.CacheStatusDidChange, this._refreshRequestAndResponse, this); 159 this._resource.removeEventListener(WebInspector.Resource.Event.SizeDidChange, this._refreshDecodedSize, this); 160 this._resource.removeEventListener(WebInspector.Resource.Event.TransferSizeDidChange, this._refreshTransferSize, this); 161 } 162 163 this._resource = resource; 164 165 if (this._resource) { 166 this._resource.addEventListener(WebInspector.Resource.Event.URLDidChange, this._refreshURL, this); 167 this._resource.addEventListener(WebInspector.Resource.Event.MIMETypeDidChange, this._refreshMIMEType, this); 168 this._resource.addEventListener(WebInspector.Resource.Event.TypeDidChange, this._refreshResourceType, this); 169 this._resource.addEventListener(WebInspector.Resource.Event.RequestHeadersDidChange, this._refreshRequestHeaders, this); 170 this._resource.addEventListener(WebInspector.Resource.Event.ResponseReceived, this._refreshRequestAndResponse, this); 171 this._resource.addEventListener(WebInspector.Resource.Event.CacheStatusDidChange, this._refreshRequestAndResponse, this); 172 this._resource.addEventListener(WebInspector.Resource.Event.SizeDidChange, this._refreshDecodedSize, this); 173 this._resource.addEventListener(WebInspector.Resource.Event.TransferSizeDidChange, this._refreshTransferSize, this); 174 } 175 176 this.needsRefresh(); 177 }, 178 179 refresh: function() 180 { 181 if (!this._resource) 182 return; 183 184 this._refreshURL(); 185 this._refreshMIMEType(); 186 this._refreshResourceType(); 187 this._refreshRequestAndResponse(); 188 this._refreshDecodedSize(); 189 this._refreshTransferSize(); 190 this._refreshRequestHeaders(); 191 this._refreshImageSizeSection(); 192 this._refreshRequestDataSection(); 193 }, 194 195 // Private 196 197 _refreshURL: function() 198 { 199 if (!this._resource) 200 return; 201 202 this._locationFullURLRow.value = this._resource.url.insertWordBreakCharacters(); 203 204 var urlComponents = this._resource.urlComponents; 205 if (urlComponents.scheme) { 206 if (this._resource.initiatorSourceCodeLocation) 207 this._locationSection.groups = [this._fullURLGroup, this._locationURLComponentsGroup, this._initiatorGroup]; 208 else 209 this._locationSection.groups = [this._fullURLGroup, this._locationURLComponentsGroup]; 210 211 this._locationSchemeRow.value = urlComponents.scheme ? urlComponents.scheme : null; 212 this._locationHostRow.value = urlComponents.host ? urlComponents.host : null; 213 this._locationPortRow.value = urlComponents.port ? urlComponents.port : null; 214 this._locationPathRow.value = urlComponents.path ? urlComponents.path.insertWordBreakCharacters() : null; 215 this._locationQueryStringRow.value = urlComponents.queryString ? urlComponents.queryString.insertWordBreakCharacters() : null; 216 this._locationFragmentRow.value = urlComponents.fragment ? urlComponents.fragment.insertWordBreakCharacters() : null; 217 this._locationFilenameRow.value = urlComponents.lastPathComponent ? urlComponents.lastPathComponent.insertWordBreakCharacters() : null; 218 } else { 219 if (this._resource.initiatorSourceCodeLocation) 220 this._locationSection.groups = [this._fullURLGroup, this._initiatorGroup]; 221 else 222 this._locationSection.groups = [this._fullURLGroup]; 223 } 224 225 if (this._resource.initiatorSourceCodeLocation) 226 this._initiatorRow.value = WebInspector.createSourceCodeLocationLink(this._resource.initiatorSourceCodeLocation, true); 227 228 if (urlComponents.queryString) { 229 // Ensure the "Query Parameters" section is displayed, right after the "Request & Response" section. 230 this.element.insertBefore(this._queryParametersSection.element, this._requestAndResponseSection.element.nextSibling); 231 232 this._queryParametersRow.dataGrid = this._createNameValueDataGrid(parseQueryString(urlComponents.queryString, true)); 233 } else { 234 // Hide the "Query Parameters" section if we don't have a query string. 235 var queryParametersSectionElement = this._queryParametersSection.element; 236 if (queryParametersSectionElement.parentNode) 237 queryParametersSectionElement.parentNode.removeChild(queryParametersSectionElement); 238 } 239 }, 240 241 _refreshResourceType: function() 242 { 243 if (!this._resource) 244 return; 245 246 this._typeResourceTypeRow.value = WebInspector.Resource.Type.displayName(this._resource.type); 247 }, 248 249 _refreshMIMEType: function() 250 { 251 if (!this._resource) 252 return; 253 254 this._typeMIMETypeRow.value = this._resource.mimeType; 255 }, 256 257 _refreshRequestAndResponse: function() 258 { 259 var resource = this._resource; 260 if (!resource) 261 return; 262 263 // If we don't have a value, we set an em-dash to keep the row from hiding. 264 // This keeps the UI from shifting around as data comes in. 265 const emDash = "\u2014"; 266 267 this._requestMethodRow.value = resource.requestMethod || emDash; 268 269 this._cachedRow.value = resource.cached ? WebInspector.UIString("Yes") : WebInspector.UIString("No"); 270 271 this._statusCodeRow.value = resource.statusCode || emDash; 272 this._statusTextRow.value = resource.statusText || emDash; 273 274 this._refreshResponseHeaders(); 275 this._refreshCompressed(); 276 }, 277 278 _valueForSize: function(size) 279 { 280 // If we don't have a value, we set an em-dash to keep the row from hiding. 281 // This keeps the UI from shifting around as data comes in. 282 const emDash = "\u2014"; 283 return size > 0 ? Number.bytesToString(size) : emDash; 284 }, 285 286 _refreshCompressed: function() 287 { 288 this._compressedRow.value = this._resource.compressed ? WebInspector.UIString("Yes") : WebInspector.UIString("No"); 289 this._compressionRow.value = this._resource.compressed ? WebInspector.UIString("%.2f\u00d7").format(this._resource.size / this._resource.encodedSize) : null; 290 }, 291 292 _refreshDecodedSize: function() 293 { 294 if (!this._resource) 295 return; 296 297 this._encodedSizeRow.value = this._valueForSize(this._resource.encodedSize); 298 this._decodedSizeRow.value = this._valueForSize(this._resource.size); 299 300 this._refreshCompressed(); 301 }, 302 303 _refreshTransferSize: function() 304 { 305 if (!this._resource) 306 return; 307 308 this._encodedSizeRow.value = this._valueForSize(this._resource.encodedSize); 309 this._transferSizeRow.value = this._valueForSize(this._resource.transferSize); 310 311 this._refreshCompressed(); 312 }, 313 314 _refreshRequestHeaders: function() 315 { 316 if (!this._resource) 317 return; 318 319 this._requestHeadersRow.dataGrid = this._createNameValueDataGrid(this._resource.requestHeaders); 320 }, 321 322 _refreshResponseHeaders: function() 323 { 324 if (!this._resource) 325 return; 326 327 this._responseHeadersRow.dataGrid = this._createNameValueDataGrid(this._resource.responseHeaders); 328 }, 329 330 _createNameValueDataGrid: function(data) 331 { 332 if (!data || data instanceof Array ? !data.length : isEmptyObject(data)) 333 return null; 334 335 var dataGrid = new WebInspector.DataGrid({ 336 name: {title: WebInspector.UIString("Name"), width: "30%", sortable: true}, 337 value: {title: WebInspector.UIString("Value"), sortable: true} 338 }); 339 340 function addDataGridNode(nodeValue) 341 { 342 console.assert(typeof nodeValue.name === "string"); 343 console.assert(!nodeValue.value || typeof nodeValue.value === "string"); 344 345 var node = new WebInspector.DataGridNode({name: nodeValue.name, value: nodeValue.value || ""}, false); 346 node.selectable = true; 347 dataGrid.appendChild(node); 348 } 349 350 if (data instanceof Array) { 351 for (var i = 0; i < data.length; ++i) 352 addDataGridNode(data[i]); 353 } else { 354 for (var name in data) 355 addDataGridNode({name: name, value: data[name] || ""}); 356 } 357 358 dataGrid.addEventListener(WebInspector.DataGrid.Event.SortChanged, sortDataGrid, this); 359 360 function sortDataGrid() 361 { 362 var sortColumnIdentifier = dataGrid.sortColumnIdentifier; 363 364 function comparator(a, b) 365 { 366 var item1 = a.data[sortColumnIdentifier]; 367 var item2 = b.data[sortColumnIdentifier]; 368 return item1.localeCompare(item2); 369 } 370 371 dataGrid.sortNodes(comparator); 372 } 373 374 return dataGrid; 375 }, 376 377 _refreshImageSizeSection: function() 378 { 379 var resource = this._resource; 380 381 if (!resource) 382 return; 383 384 // Hide the section if we're not dealing with an image or if the load failed. 385 if (resource.type !== WebInspector.Resource.Type.Image || resource.failed) { 386 var imageSectionElement = this._imageSizeSection.element; 387 if (imageSectionElement.parentNode) 388 this.element.removeChild(imageSectionElement); 389 return; 390 } 391 392 // Ensure the section is displayed, right before the "Location" section. 393 this.element.insertBefore(this._imageSizeSection.element, this._locationSection.element); 394 395 // Get the metrics for this resource and fill in the metrics rows with that information. 396 resource.getImageSize(function(size) { 397 this._imageWidthRow.value = WebInspector.UIString("%fpx").format(size.width); 398 this._imageHeightRow.value = WebInspector.UIString("%fpx").format(size.height); 399 }.bind(this)); 400 }, 401 402 _goToRequestDataClicked: function() 403 { 404 WebInspector.resourceSidebarPanel.showResourceRequest(this._resource); 405 }, 406 407 _refreshRequestDataSection: function() 408 { 409 var resource = this._resource; 410 411 if (!resource) 412 return; 413 414 // Hide the section if we're not dealing with a request with data. 415 var requestData = resource.requestData; 416 if (!requestData) { 417 this._requestDataSection.element.remove(); 418 return; 419 } 420 421 // Ensure the section is displayed, right before the "Request Headers" section. 422 this.element.insertBefore(this._requestDataSection.element, this._requestHeadersSection.element); 423 424 var requestDataContentType = resource.requestDataContentType || ""; 425 if (requestDataContentType && requestDataContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i)) { 426 // Simple form data that should be parsable like a query string. 427 var parametersRow = new WebInspector.DetailsSectionDataGridRow(null, WebInspector.UIString("No Parameters")); 428 parametersRow.dataGrid = this._createNameValueDataGrid(parseQueryString(requestData, true)); 429 430 this._requestDataSection.groups = [new WebInspector.DetailsSectionGroup([parametersRow])]; 431 return; 432 } 433 434 // Not simple form data, so we can really only show the size and type here. 435 // FIXME: Add a go-to arrow here to show the data in the content browser. 436 437 var mimeTypeComponents = parseMIMEType(requestDataContentType); 438 439 var mimeType = mimeTypeComponents.type; 440 var boundary = mimeTypeComponents.boundary; 441 var encoding = mimeTypeComponents.encoding; 442 443 var rows = []; 444 445 var mimeTypeRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("MIME Type")); 446 mimeTypeRow.value = mimeType; 447 rows.push(mimeTypeRow); 448 449 if (boundary) { 450 var boundryRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Boundary")); 451 boundryRow.value = boundary; 452 rows.push(boundryRow); 453 } 454 455 if (encoding) { 456 var encodingRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Encoding")); 457 encodingRow.value = encoding; 458 rows.push(encodingRow); 459 } 460 461 var sizeValue = Number.bytesToString(requestData.length); 462 463 var dataValue = document.createDocumentFragment(); 464 465 dataValue.appendChild(document.createTextNode(sizeValue)); 466 467 var goToButton = dataValue.appendChild(WebInspector.createGoToArrowButton()); 468 goToButton.addEventListener("click", this._goToRequestDataClicked.bind(this)); 469 470 var dataRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Data")); 471 dataRow.value = dataValue; 472 rows.push(dataRow); 473 474 this._requestDataSection.groups = [new WebInspector.DetailsSectionGroup(rows)]; 475 } 476}; 477 478WebInspector.ResourceDetailsSidebarPanel.prototype.__proto__ = WebInspector.DetailsSidebarPanel.prototype; 479