1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 /** 6 * This view displays options for importing/exporting the captured data. Its 7 * primarily usefulness is to allow users to copy-paste their data in an easy 8 * to read format for bug reports. 9 * 10 * - Has a button to generate a text report. 11 * 12 * - Shows how many events have been captured. 13 * @constructor 14 */ 15 function DataView(mainBoxId, 16 outputTextBoxId, 17 exportTextButtonId, 18 securityStrippingCheckboxId, 19 byteLoggingCheckboxId, 20 passivelyCapturedCountId, 21 activelyCapturedCountId, 22 deleteAllId, 23 dumpDataDivId, 24 loadDataDivId, 25 loadLogFileId, 26 capturingTextSpanId, 27 loggingTextSpanId) { 28 DivView.call(this, mainBoxId); 29 30 this.textPre_ = document.getElementById(outputTextBoxId); 31 32 var securityStrippingCheckbox = 33 document.getElementById(securityStrippingCheckboxId); 34 securityStrippingCheckbox.onclick = 35 this.onSetSecurityStripping_.bind(this, securityStrippingCheckbox); 36 37 var byteLoggingCheckbox = document.getElementById(byteLoggingCheckboxId); 38 byteLoggingCheckbox.onclick = 39 this.onSetByteLogging_.bind(this, byteLoggingCheckbox); 40 41 var exportTextButton = document.getElementById(exportTextButtonId); 42 exportTextButton.onclick = this.onExportToText_.bind(this); 43 44 this.activelyCapturedCountBox_ = 45 document.getElementById(activelyCapturedCountId); 46 this.passivelyCapturedCountBox_ = 47 document.getElementById(passivelyCapturedCountId); 48 document.getElementById(deleteAllId).onclick = 49 g_browser.deleteAllEvents.bind(g_browser); 50 51 this.dumpDataDiv_ = document.getElementById(dumpDataDivId); 52 this.loadDataDiv_ = document.getElementById(loadDataDivId); 53 this.capturingTextSpan_ = document.getElementById(capturingTextSpanId); 54 this.loggingTextSpan_ = document.getElementById(loggingTextSpanId); 55 56 document.getElementById(loadLogFileId).onclick = 57 g_browser.loadLogFile.bind(g_browser); 58 59 this.updateEventCounts_(); 60 this.waitingForUpdate_ = false; 61 62 g_browser.addLogObserver(this); 63 } 64 65 inherits(DataView, DivView); 66 67 /** 68 * Called whenever a new event is received. 69 */ 70 DataView.prototype.onLogEntryAdded = function(logEntry) { 71 this.updateEventCounts_(); 72 }; 73 74 /** 75 * Called whenever some log events are deleted. |sourceIds| lists 76 * the source IDs of all deleted log entries. 77 */ 78 DataView.prototype.onLogEntriesDeleted = function(sourceIds) { 79 this.updateEventCounts_(); 80 }; 81 82 /** 83 * Called whenever all log events are deleted. 84 */ 85 DataView.prototype.onAllLogEntriesDeleted = function() { 86 this.updateEventCounts_(); 87 }; 88 89 /** 90 * Called when either a log file is loaded or when going back to actively 91 * logging events. In either case, called after clearing the old entries, 92 * but before getting any new ones. 93 */ 94 DataView.prototype.onSetIsViewingLogFile = function(isViewingLogFile) { 95 setNodeDisplay(this.dumpDataDiv_, !isViewingLogFile); 96 setNodeDisplay(this.capturingTextSpan_, !isViewingLogFile); 97 setNodeDisplay(this.loggingTextSpan_, isViewingLogFile); 98 this.setText_(''); 99 }; 100 101 /** 102 * Updates the counters showing how many events have been captured. 103 */ 104 DataView.prototype.updateEventCounts_ = function() { 105 this.activelyCapturedCountBox_.innerText = 106 g_browser.getNumActivelyCapturedEvents() 107 this.passivelyCapturedCountBox_.innerText = 108 g_browser.getNumPassivelyCapturedEvents(); 109 }; 110 111 /** 112 * Depending on the value of the checkbox, enables or disables logging of 113 * actual bytes transferred. 114 */ 115 DataView.prototype.onSetByteLogging_ = function(byteLoggingCheckbox) { 116 if (byteLoggingCheckbox.checked) { 117 g_browser.setLogLevel(LogLevelType.LOG_ALL); 118 } else { 119 g_browser.setLogLevel(LogLevelType.LOG_ALL_BUT_BYTES); 120 } 121 }; 122 123 /** 124 * Depending on the value of the checkbox, enables or disables stripping 125 * cookies and passwords from log dumps and displayed events. 126 */ 127 DataView.prototype.onSetSecurityStripping_ = 128 function(securityStrippingCheckbox) { 129 g_browser.setSecurityStripping(securityStrippingCheckbox.checked); 130 }; 131 132 /** 133 * Clears displayed text when security stripping is toggled. 134 */ 135 DataView.prototype.onSecurityStrippingChanged = function() { 136 this.setText_(''); 137 } 138 139 /** 140 * If not already waiting for results from all updates, triggers all 141 * updates and starts waiting for them to complete. 142 */ 143 DataView.prototype.onExportToText_ = function() { 144 if (this.waitingForUpdate_) 145 return; 146 this.waitingForUpdate = true; 147 this.setText_('Generating...'); 148 g_browser.updateAllInfo(this.onUpdateAllCompleted.bind(this)); 149 }; 150 151 /** 152 * Presents the captured data as formatted text. 153 */ 154 DataView.prototype.onUpdateAllCompleted = function(data) { 155 // It's possible for a log file to be loaded while a dump is being generated. 156 // When that happens, don't display the log dump, to avoid any confusion. 157 if (g_browser.isViewingLogFile()) 158 return; 159 this.waitingForUpdate_ = false; 160 var text = []; 161 162 // Print some basic information about our environment. 163 text.push('Data exported on: ' + (new Date()).toLocaleString()); 164 text.push(''); 165 text.push('Number of passively captured events: ' + 166 g_browser.getNumPassivelyCapturedEvents()); 167 text.push('Number of actively captured events: ' + 168 g_browser.getNumActivelyCapturedEvents()); 169 text.push(''); 170 171 text.push('Chrome version: ' + ClientInfo.version + 172 ' (' + ClientInfo.official + 173 ' ' + ClientInfo.cl + 174 ') ' + ClientInfo.version_mod); 175 // Third value in first set of parentheses in user-agent string. 176 var platform = /\(.*?;.*?; (.*?);/.exec(navigator.userAgent); 177 if (platform) 178 text.push('Platform: ' + platform[1]); 179 text.push('Command line: ' + ClientInfo.command_line); 180 181 text.push(''); 182 var default_address_family = data.hostResolverInfo.default_address_family; 183 text.push('Default address family: ' + 184 getKeyWithValue(AddressFamily, default_address_family)); 185 if (default_address_family == AddressFamily.ADDRESS_FAMILY_IPV4) 186 text.push(' (IPv6 disabled)'); 187 188 text.push(''); 189 text.push('----------------------------------------------'); 190 text.push(' Proxy settings (effective)'); 191 text.push('----------------------------------------------'); 192 text.push(''); 193 194 text.push(proxySettingsToString(data.proxySettings.effective)); 195 196 text.push(''); 197 text.push('----------------------------------------------'); 198 text.push(' Proxy settings (original)'); 199 text.push('----------------------------------------------'); 200 text.push(''); 201 202 text.push(proxySettingsToString(data.proxySettings.original)); 203 204 text.push(''); 205 text.push('----------------------------------------------'); 206 text.push(' Bad proxies cache'); 207 text.push('----------------------------------------------'); 208 209 var badProxiesList = data.badProxies; 210 if (badProxiesList.length == 0) { 211 text.push(''); 212 text.push('None'); 213 } else { 214 for (var i = 0; i < badProxiesList.length; ++i) { 215 var e = badProxiesList[i]; 216 text.push(''); 217 text.push('(' + (i+1) + ')'); 218 text.push('Proxy: ' + e.proxy_uri); 219 text.push('Bad until: ' + this.formatExpirationTime_(e.bad_until)); 220 } 221 } 222 223 text.push(''); 224 text.push('----------------------------------------------'); 225 text.push(' Host resolver cache'); 226 text.push('----------------------------------------------'); 227 text.push(''); 228 229 var hostResolverCache = data.hostResolverInfo.cache; 230 231 text.push('Capacity: ' + hostResolverCache.capacity); 232 text.push('Time to live for successful resolves (ms): ' + 233 hostResolverCache.ttl_success_ms); 234 text.push('Time to live for failed resolves (ms): ' + 235 hostResolverCache.ttl_failure_ms); 236 237 if (hostResolverCache.entries.length > 0) { 238 for (var i = 0; i < hostResolverCache.entries.length; ++i) { 239 var e = hostResolverCache.entries[i]; 240 241 text.push(''); 242 text.push('(' + (i+1) + ')'); 243 text.push('Hostname: ' + e.hostname); 244 text.push('Address family: ' + 245 getKeyWithValue(AddressFamily, e.address_family)); 246 247 if (e.error != undefined) { 248 text.push('Error: ' + e.error); 249 } else { 250 for (var j = 0; j < e.addresses.length; ++j) { 251 text.push('Address ' + (j + 1) + ': ' + e.addresses[j]); 252 } 253 } 254 255 text.push('Valid until: ' + this.formatExpirationTime_(e.expiration)); 256 var expirationDate = g_browser.convertTimeTicksToDate(e.expiration); 257 text.push(' (' + expirationDate.toLocaleString() + ')'); 258 } 259 } else { 260 text.push(''); 261 text.push('None'); 262 } 263 264 text.push(''); 265 text.push('----------------------------------------------'); 266 text.push(' Events'); 267 text.push('----------------------------------------------'); 268 text.push(''); 269 270 this.appendEventsPrintedAsText_(text); 271 272 text.push(''); 273 text.push('----------------------------------------------'); 274 text.push(' Http cache stats'); 275 text.push('----------------------------------------------'); 276 text.push(''); 277 278 var httpCacheStats = data.httpCacheInfo.stats; 279 for (var statName in httpCacheStats) 280 text.push(statName + ': ' + httpCacheStats[statName]); 281 282 text.push(''); 283 text.push('----------------------------------------------'); 284 text.push(' Socket pools'); 285 text.push('----------------------------------------------'); 286 text.push(''); 287 288 this.appendSocketPoolsAsText_(text, data.socketPoolInfo); 289 290 text.push(''); 291 text.push('----------------------------------------------'); 292 text.push(' SPDY Status'); 293 text.push('----------------------------------------------'); 294 text.push(''); 295 296 text.push('SPDY Enabled: ' + data.spdyStatus.spdy_enabled); 297 text.push('Use Alternate Protocol: ' + 298 data.spdyStatus.use_alternate_protocols); 299 text.push('Force SPDY Always: ' + data.spdyStatus.force_spdy_always); 300 text.push('Force SPDY Over SSL: ' + data.spdyStatus.force_spdy_over_ssl); 301 text.push('Next Protocols: ' + data.spdyStatus.next_protos); 302 303 304 text.push(''); 305 text.push('----------------------------------------------'); 306 text.push(' SPDY Sessions'); 307 text.push('----------------------------------------------'); 308 text.push(''); 309 310 if (data.spdySessionInfo == null || data.spdySessionInfo.length == 0) { 311 text.push('None'); 312 } else { 313 var spdyTablePrinter = 314 SpdyView.createSessionTablePrinter(data.spdySessionInfo); 315 text.push(spdyTablePrinter.toText(2)); 316 } 317 318 text.push(''); 319 text.push('----------------------------------------------'); 320 text.push(' Alternate Protocol Mappings'); 321 text.push('----------------------------------------------'); 322 text.push(''); 323 324 if (data.spdyAlternateProtocolMappings == null || 325 data.spdyAlternateProtocolMappings.length == 0) { 326 text.push('None'); 327 } else { 328 var spdyTablePrinter = 329 SpdyView.createAlternateProtocolMappingsTablePrinter( 330 data.spdyAlternateProtocolMappings); 331 text.push(spdyTablePrinter.toText(2)); 332 } 333 334 if (g_browser.isPlatformWindows()) { 335 text.push(''); 336 text.push('----------------------------------------------'); 337 text.push(' Winsock layered service providers'); 338 text.push('----------------------------------------------'); 339 text.push(''); 340 341 var serviceProviders = data.serviceProviders; 342 var layeredServiceProviders = serviceProviders.service_providers; 343 for (var i = 0; i < layeredServiceProviders.length; ++i) { 344 var provider = layeredServiceProviders[i]; 345 text.push('name: ' + provider.name); 346 text.push('version: ' + provider.version); 347 text.push('type: ' + 348 ServiceProvidersView.getLayeredServiceProviderType(provider)); 349 text.push('socket_type: ' + 350 ServiceProvidersView.getSocketType(provider)); 351 text.push('socket_protocol: ' + 352 ServiceProvidersView.getProtocolType(provider)); 353 text.push('path: ' + provider.path); 354 text.push(''); 355 } 356 357 text.push(''); 358 text.push('----------------------------------------------'); 359 text.push(' Winsock namespace providers'); 360 text.push('----------------------------------------------'); 361 text.push(''); 362 363 var namespaceProviders = serviceProviders.namespace_providers; 364 for (var i = 0; i < namespaceProviders.length; ++i) { 365 var provider = namespaceProviders[i]; 366 text.push('name: ' + provider.name); 367 text.push('version: ' + provider.version); 368 text.push('type: ' + 369 ServiceProvidersView.getNamespaceProviderType(provider)); 370 text.push('active: ' + provider.active); 371 text.push(''); 372 } 373 } 374 375 // Open a new window to display this text. 376 this.setText_(text.join('\n')); 377 378 this.selectText_(); 379 }; 380 381 DataView.prototype.appendEventsPrintedAsText_ = function(out) { 382 var allEvents = g_browser.getAllCapturedEvents(); 383 384 // Group the events into buckets by source ID, and buckets by source type. 385 var sourceIds = []; 386 var sourceIdToEventList = {}; 387 var sourceTypeToSourceIdList = {}; 388 389 // Lists used for actual output. 390 var eventLists = []; 391 392 for (var i = 0; i < allEvents.length; ++i) { 393 var e = allEvents[i]; 394 var eventList = sourceIdToEventList[e.source.id]; 395 if (!eventList) { 396 eventList = []; 397 eventLists.push(eventList); 398 if (e.source.type != LogSourceType.NONE) 399 sourceIdToEventList[e.source.id] = eventList; 400 401 // Update sourceIds 402 sourceIds.push(e.source.id); 403 404 // Update the sourceTypeToSourceIdList list. 405 var idList = sourceTypeToSourceIdList[e.source.type]; 406 if (!idList) { 407 idList = []; 408 sourceTypeToSourceIdList[e.source.type] = idList; 409 } 410 idList.push(e.source.id); 411 } 412 eventList.push(e); 413 } 414 415 416 // For each source or event without a source (ordered by when the first 417 // output event for that source happened). 418 for (var i = 0; i < eventLists.length; ++i) { 419 var eventList = eventLists[i]; 420 var sourceId = eventList[0].source.id; 421 var sourceType = eventList[0].source.type; 422 423 var startDate = g_browser.convertTimeTicksToDate(eventList[0].time); 424 425 out.push('------------------------------------------'); 426 out.push(getKeyWithValue(LogSourceType, sourceType) + 427 ' (id=' + sourceId + ')' + 428 ' [start=' + startDate.toLocaleString() + ']'); 429 out.push('------------------------------------------'); 430 431 out.push(PrintSourceEntriesAsText(eventList)); 432 } 433 }; 434 435 DataView.prototype.appendSocketPoolsAsText_ = function(text, socketPoolInfo) { 436 var socketPools = SocketPoolWrapper.createArrayFrom(socketPoolInfo); 437 var tablePrinter = SocketPoolWrapper.createTablePrinter(socketPools); 438 text.push(tablePrinter.toText(2)); 439 440 text.push(''); 441 442 for (var i = 0; i < socketPools.length; ++i) { 443 if (socketPools[i].origPool.groups == undefined) 444 continue; 445 var groupTablePrinter = socketPools[i].createGroupTablePrinter(); 446 text.push(groupTablePrinter.toText(2)); 447 } 448 }; 449 450 /** 451 * Helper function to set this view's content to |text|. 452 */ 453 DataView.prototype.setText_ = function(text) { 454 this.textPre_.innerHTML = ''; 455 addTextNode(this.textPre_, text); 456 }; 457 458 /** 459 * Format a time ticks count as a timestamp. 460 */ 461 DataView.prototype.formatExpirationTime_ = function(timeTicks) { 462 var d = g_browser.convertTimeTicksToDate(timeTicks); 463 var isExpired = d.getTime() < (new Date()).getTime(); 464 return 't=' + d.getTime() + (isExpired ? ' [EXPIRED]' : ''); 465 }; 466 467 /** 468 * Select all text from log dump. 469 */ 470 DataView.prototype.selectText_ = function() { 471 var selection = window.getSelection(); 472 selection.removeAllRanges(); 473 474 var range = document.createRange(); 475 range.selectNodeContents(this.textPre_); 476 selection.addRange(range); 477 }; 478