// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * This view displays options for importing/exporting the captured data. Its * primarily usefulness is to allow users to copy-paste their data in an easy * to read format for bug reports. * * - Has a button to generate a text report. * * - Shows how many events have been captured. * @constructor */ function DataView(mainBoxId, outputTextBoxId, exportTextButtonId, securityStrippingCheckboxId, byteLoggingCheckboxId, passivelyCapturedCountId, activelyCapturedCountId, deleteAllId, dumpDataDivId, loadDataDivId, loadLogFileId, capturingTextSpanId, loggingTextSpanId) { DivView.call(this, mainBoxId); this.textPre_ = document.getElementById(outputTextBoxId); var securityStrippingCheckbox = document.getElementById(securityStrippingCheckboxId); securityStrippingCheckbox.onclick = this.onSetSecurityStripping_.bind(this, securityStrippingCheckbox); var byteLoggingCheckbox = document.getElementById(byteLoggingCheckboxId); byteLoggingCheckbox.onclick = this.onSetByteLogging_.bind(this, byteLoggingCheckbox); var exportTextButton = document.getElementById(exportTextButtonId); exportTextButton.onclick = this.onExportToText_.bind(this); this.activelyCapturedCountBox_ = document.getElementById(activelyCapturedCountId); this.passivelyCapturedCountBox_ = document.getElementById(passivelyCapturedCountId); document.getElementById(deleteAllId).onclick = g_browser.deleteAllEvents.bind(g_browser); this.dumpDataDiv_ = document.getElementById(dumpDataDivId); this.loadDataDiv_ = document.getElementById(loadDataDivId); this.capturingTextSpan_ = document.getElementById(capturingTextSpanId); this.loggingTextSpan_ = document.getElementById(loggingTextSpanId); document.getElementById(loadLogFileId).onclick = g_browser.loadLogFile.bind(g_browser); this.updateEventCounts_(); this.waitingForUpdate_ = false; g_browser.addLogObserver(this); } inherits(DataView, DivView); /** * Called whenever a new event is received. */ DataView.prototype.onLogEntryAdded = function(logEntry) { this.updateEventCounts_(); }; /** * Called whenever some log events are deleted. |sourceIds| lists * the source IDs of all deleted log entries. */ DataView.prototype.onLogEntriesDeleted = function(sourceIds) { this.updateEventCounts_(); }; /** * Called whenever all log events are deleted. */ DataView.prototype.onAllLogEntriesDeleted = function() { this.updateEventCounts_(); }; /** * Called when either a log file is loaded or when going back to actively * logging events. In either case, called after clearing the old entries, * but before getting any new ones. */ DataView.prototype.onSetIsViewingLogFile = function(isViewingLogFile) { setNodeDisplay(this.dumpDataDiv_, !isViewingLogFile); setNodeDisplay(this.capturingTextSpan_, !isViewingLogFile); setNodeDisplay(this.loggingTextSpan_, isViewingLogFile); this.setText_(''); }; /** * Updates the counters showing how many events have been captured. */ DataView.prototype.updateEventCounts_ = function() { this.activelyCapturedCountBox_.innerText = g_browser.getNumActivelyCapturedEvents() this.passivelyCapturedCountBox_.innerText = g_browser.getNumPassivelyCapturedEvents(); }; /** * Depending on the value of the checkbox, enables or disables logging of * actual bytes transferred. */ DataView.prototype.onSetByteLogging_ = function(byteLoggingCheckbox) { if (byteLoggingCheckbox.checked) { g_browser.setLogLevel(LogLevelType.LOG_ALL); } else { g_browser.setLogLevel(LogLevelType.LOG_ALL_BUT_BYTES); } }; /** * Depending on the value of the checkbox, enables or disables stripping * cookies and passwords from log dumps and displayed events. */ DataView.prototype.onSetSecurityStripping_ = function(securityStrippingCheckbox) { g_browser.setSecurityStripping(securityStrippingCheckbox.checked); }; /** * Clears displayed text when security stripping is toggled. */ DataView.prototype.onSecurityStrippingChanged = function() { this.setText_(''); } /** * If not already waiting for results from all updates, triggers all * updates and starts waiting for them to complete. */ DataView.prototype.onExportToText_ = function() { if (this.waitingForUpdate_) return; this.waitingForUpdate = true; this.setText_('Generating...'); g_browser.updateAllInfo(this.onUpdateAllCompleted.bind(this)); }; /** * Presents the captured data as formatted text. */ DataView.prototype.onUpdateAllCompleted = function(data) { // It's possible for a log file to be loaded while a dump is being generated. // When that happens, don't display the log dump, to avoid any confusion. if (g_browser.isViewingLogFile()) return; this.waitingForUpdate_ = false; var text = []; // Print some basic information about our environment. text.push('Data exported on: ' + (new Date()).toLocaleString()); text.push(''); text.push('Number of passively captured events: ' + g_browser.getNumPassivelyCapturedEvents()); text.push('Number of actively captured events: ' + g_browser.getNumActivelyCapturedEvents()); text.push(''); text.push('Chrome version: ' + ClientInfo.version + ' (' + ClientInfo.official + ' ' + ClientInfo.cl + ') ' + ClientInfo.version_mod); // Third value in first set of parentheses in user-agent string. var platform = /\(.*?;.*?; (.*?);/.exec(navigator.userAgent); if (platform) text.push('Platform: ' + platform[1]); text.push('Command line: ' + ClientInfo.command_line); text.push(''); var default_address_family = data.hostResolverInfo.default_address_family; text.push('Default address family: ' + getKeyWithValue(AddressFamily, default_address_family)); if (default_address_family == AddressFamily.ADDRESS_FAMILY_IPV4) text.push(' (IPv6 disabled)'); text.push(''); text.push('----------------------------------------------'); text.push(' Proxy settings (effective)'); text.push('----------------------------------------------'); text.push(''); text.push(proxySettingsToString(data.proxySettings.effective)); text.push(''); text.push('----------------------------------------------'); text.push(' Proxy settings (original)'); text.push('----------------------------------------------'); text.push(''); text.push(proxySettingsToString(data.proxySettings.original)); text.push(''); text.push('----------------------------------------------'); text.push(' Bad proxies cache'); text.push('----------------------------------------------'); var badProxiesList = data.badProxies; if (badProxiesList.length == 0) { text.push(''); text.push('None'); } else { for (var i = 0; i < badProxiesList.length; ++i) { var e = badProxiesList[i]; text.push(''); text.push('(' + (i+1) + ')'); text.push('Proxy: ' + e.proxy_uri); text.push('Bad until: ' + this.formatExpirationTime_(e.bad_until)); } } text.push(''); text.push('----------------------------------------------'); text.push(' Host resolver cache'); text.push('----------------------------------------------'); text.push(''); var hostResolverCache = data.hostResolverInfo.cache; text.push('Capacity: ' + hostResolverCache.capacity); text.push('Time to live for successful resolves (ms): ' + hostResolverCache.ttl_success_ms); text.push('Time to live for failed resolves (ms): ' + hostResolverCache.ttl_failure_ms); if (hostResolverCache.entries.length > 0) { for (var i = 0; i < hostResolverCache.entries.length; ++i) { var e = hostResolverCache.entries[i]; text.push(''); text.push('(' + (i+1) + ')'); text.push('Hostname: ' + e.hostname); text.push('Address family: ' + getKeyWithValue(AddressFamily, e.address_family)); if (e.error != undefined) { text.push('Error: ' + e.error); } else { for (var j = 0; j < e.addresses.length; ++j) { text.push('Address ' + (j + 1) + ': ' + e.addresses[j]); } } text.push('Valid until: ' + this.formatExpirationTime_(e.expiration)); var expirationDate = g_browser.convertTimeTicksToDate(e.expiration); text.push(' (' + expirationDate.toLocaleString() + ')'); } } else { text.push(''); text.push('None'); } text.push(''); text.push('----------------------------------------------'); text.push(' Events'); text.push('----------------------------------------------'); text.push(''); this.appendEventsPrintedAsText_(text); text.push(''); text.push('----------------------------------------------'); text.push(' Http cache stats'); text.push('----------------------------------------------'); text.push(''); var httpCacheStats = data.httpCacheInfo.stats; for (var statName in httpCacheStats) text.push(statName + ': ' + httpCacheStats[statName]); text.push(''); text.push('----------------------------------------------'); text.push(' Socket pools'); text.push('----------------------------------------------'); text.push(''); this.appendSocketPoolsAsText_(text, data.socketPoolInfo); text.push(''); text.push('----------------------------------------------'); text.push(' SPDY Status'); text.push('----------------------------------------------'); text.push(''); text.push('SPDY Enabled: ' + data.spdyStatus.spdy_enabled); text.push('Use Alternate Protocol: ' + data.spdyStatus.use_alternate_protocols); text.push('Force SPDY Always: ' + data.spdyStatus.force_spdy_always); text.push('Force SPDY Over SSL: ' + data.spdyStatus.force_spdy_over_ssl); text.push('Next Protocols: ' + data.spdyStatus.next_protos); text.push(''); text.push('----------------------------------------------'); text.push(' SPDY Sessions'); text.push('----------------------------------------------'); text.push(''); if (data.spdySessionInfo == null || data.spdySessionInfo.length == 0) { text.push('None'); } else { var spdyTablePrinter = SpdyView.createSessionTablePrinter(data.spdySessionInfo); text.push(spdyTablePrinter.toText(2)); } text.push(''); text.push('----------------------------------------------'); text.push(' Alternate Protocol Mappings'); text.push('----------------------------------------------'); text.push(''); if (data.spdyAlternateProtocolMappings == null || data.spdyAlternateProtocolMappings.length == 0) { text.push('None'); } else { var spdyTablePrinter = SpdyView.createAlternateProtocolMappingsTablePrinter( data.spdyAlternateProtocolMappings); text.push(spdyTablePrinter.toText(2)); } if (g_browser.isPlatformWindows()) { text.push(''); text.push('----------------------------------------------'); text.push(' Winsock layered service providers'); text.push('----------------------------------------------'); text.push(''); var serviceProviders = data.serviceProviders; var layeredServiceProviders = serviceProviders.service_providers; for (var i = 0; i < layeredServiceProviders.length; ++i) { var provider = layeredServiceProviders[i]; text.push('name: ' + provider.name); text.push('version: ' + provider.version); text.push('type: ' + ServiceProvidersView.getLayeredServiceProviderType(provider)); text.push('socket_type: ' + ServiceProvidersView.getSocketType(provider)); text.push('socket_protocol: ' + ServiceProvidersView.getProtocolType(provider)); text.push('path: ' + provider.path); text.push(''); } text.push(''); text.push('----------------------------------------------'); text.push(' Winsock namespace providers'); text.push('----------------------------------------------'); text.push(''); var namespaceProviders = serviceProviders.namespace_providers; for (var i = 0; i < namespaceProviders.length; ++i) { var provider = namespaceProviders[i]; text.push('name: ' + provider.name); text.push('version: ' + provider.version); text.push('type: ' + ServiceProvidersView.getNamespaceProviderType(provider)); text.push('active: ' + provider.active); text.push(''); } } // Open a new window to display this text. this.setText_(text.join('\n')); this.selectText_(); }; DataView.prototype.appendEventsPrintedAsText_ = function(out) { var allEvents = g_browser.getAllCapturedEvents(); // Group the events into buckets by source ID, and buckets by source type. var sourceIds = []; var sourceIdToEventList = {}; var sourceTypeToSourceIdList = {}; // Lists used for actual output. var eventLists = []; for (var i = 0; i < allEvents.length; ++i) { var e = allEvents[i]; var eventList = sourceIdToEventList[e.source.id]; if (!eventList) { eventList = []; eventLists.push(eventList); if (e.source.type != LogSourceType.NONE) sourceIdToEventList[e.source.id] = eventList; // Update sourceIds sourceIds.push(e.source.id); // Update the sourceTypeToSourceIdList list. var idList = sourceTypeToSourceIdList[e.source.type]; if (!idList) { idList = []; sourceTypeToSourceIdList[e.source.type] = idList; } idList.push(e.source.id); } eventList.push(e); } // For each source or event without a source (ordered by when the first // output event for that source happened). for (var i = 0; i < eventLists.length; ++i) { var eventList = eventLists[i]; var sourceId = eventList[0].source.id; var sourceType = eventList[0].source.type; var startDate = g_browser.convertTimeTicksToDate(eventList[0].time); out.push('------------------------------------------'); out.push(getKeyWithValue(LogSourceType, sourceType) + ' (id=' + sourceId + ')' + ' [start=' + startDate.toLocaleString() + ']'); out.push('------------------------------------------'); out.push(PrintSourceEntriesAsText(eventList)); } }; DataView.prototype.appendSocketPoolsAsText_ = function(text, socketPoolInfo) { var socketPools = SocketPoolWrapper.createArrayFrom(socketPoolInfo); var tablePrinter = SocketPoolWrapper.createTablePrinter(socketPools); text.push(tablePrinter.toText(2)); text.push(''); for (var i = 0; i < socketPools.length; ++i) { if (socketPools[i].origPool.groups == undefined) continue; var groupTablePrinter = socketPools[i].createGroupTablePrinter(); text.push(groupTablePrinter.toText(2)); } }; /** * Helper function to set this view's content to |text|. */ DataView.prototype.setText_ = function(text) { this.textPre_.innerHTML = ''; addTextNode(this.textPre_, text); }; /** * Format a time ticks count as a timestamp. */ DataView.prototype.formatExpirationTime_ = function(timeTicks) { var d = g_browser.convertTimeTicksToDate(timeTicks); var isExpired = d.getTime() < (new Date()).getTime(); return 't=' + d.getTime() + (isExpired ? ' [EXPIRED]' : ''); }; /** * Select all text from log dump. */ DataView.prototype.selectText_ = function() { var selection = window.getSelection(); selection.removeAllRanges(); var range = document.createRange(); range.selectNodeContents(this.textPre_); selection.addRange(range); };