// 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);
};