diff options
author | mmenke@chromium.org <mmenke@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-07-11 21:55:11 +0000 |
---|---|---|
committer | mmenke@chromium.org <mmenke@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-07-11 21:55:11 +0000 |
commit | c5afccb09e2b865b20803d44e9fb1b7cb39ca123 (patch) | |
tree | 10bfd7813c4592ecd0844ac0a0447b0851a26ed9 | |
parent | a0057be84fa71801a949079147edb9dd8cdfb5da (diff) | |
download | chromium_src-c5afccb09e2b865b20803d44e9fb1b7cb39ca123.zip chromium_src-c5afccb09e2b865b20803d44e9fb1b7cb39ca123.tar.gz chromium_src-c5afccb09e2b865b20803d44e9fb1b7cb39ca123.tar.bz2 |
Use JSON for net-internals log dumps and make them loadable.
BUG=87320
TEST=manual
Review URL: http://codereview.chromium.org/7215037
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@92061 0039d316-1c4b-4281-b951-d872f2087c98
21 files changed, 995 insertions, 825 deletions
diff --git a/chrome/browser/net/net_log_logger.cc b/chrome/browser/net/net_log_logger.cc index e0f67a3..6c79278 100644 --- a/chrome/browser/net/net_log_logger.cc +++ b/chrome/browser/net/net_log_logger.cc @@ -8,18 +8,30 @@ #include "base/file_util.h" #include "base/json/json_writer.h" +#include "base/memory/scoped_ptr.h" #include "base/threading/thread_restrictions.h" #include "base/values.h" +#include "chrome/browser/ui/webui/net_internals_ui.h" NetLogLogger::NetLogLogger(const FilePath &log_path) : ThreadSafeObserver(net::NetLog::LOG_ALL_BUT_BYTES) { if (!log_path.empty()) { base::ThreadRestrictions::ScopedAllowIO allow_io; file_.Set(file_util::OpenFile(log_path, "w")); + + // Write constants to the output file. This allows loading files that have + // different source and event types, as they may be added and removed + // between Chrome versions. + scoped_ptr<Value> value(NetInternalsUI::GetConstants()); + std::string json; + base::JSONWriter::Write(value.get(), false, &json); + fprintf(file_.get(), "{\"constants\": %s,\n", json.c_str()); + fprintf(file_.get(), "\"events\": [\n"); } } -NetLogLogger::~NetLogLogger() {} +NetLogLogger::~NetLogLogger() { +} void NetLogLogger::OnAddEntry(net::NetLog::EventType type, const base::TimeTicks& time, @@ -28,7 +40,7 @@ void NetLogLogger::OnAddEntry(net::NetLog::EventType type, net::NetLog::EventParameters* params) { scoped_ptr<Value> value( net::NetLog::EntryToDictionaryValue( - type, time, source, phase, params, true)); + type, time, source, phase, params, false)); // Don't pretty print, so each JSON value occupies a single line, with no // breaks (Line breaks in any text field will be escaped). Using strings // instead of integer identifiers allows logs from older versions to be @@ -38,6 +50,6 @@ void NetLogLogger::OnAddEntry(net::NetLog::EventType type, if (!file_.get()) { VLOG(1) << json; } else { - fprintf(file_.get(), "%s\n", json.c_str()); + fprintf(file_.get(), "%s,\n", json.c_str()); } } diff --git a/chrome/browser/net/net_log_logger.h b/chrome/browser/net/net_log_logger.h index 9f3ff75..1ad1244 100644 --- a/chrome/browser/net/net_log_logger.h +++ b/chrome/browser/net/net_log_logger.h @@ -15,6 +15,10 @@ class FilePath; // VLOG(1) or a path specified on creation. This is to debug errors that // prevent getting to the about:net-internals page. // +// When writing directly to a file rather than VLOG(1), the text file will +// contain a single JSON object, with an extra comma on the end and missing +// a terminal "]}". +// // Relies on ChromeNetLog only calling an Observer once at a time for // thread-safety. class NetLogLogger : public ChromeNetLog::ThreadSafeObserver { diff --git a/chrome/browser/resources/net_internals/dataview.js b/chrome/browser/resources/net_internals/dataview.js index afe49cb..dad9903 100644 --- a/chrome/browser/resources/net_internals/dataview.js +++ b/chrome/browser/resources/net_internals/dataview.js @@ -3,26 +3,31 @@ // 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. + * This view displays options for importing/exporting the captured data. This + * view is responsible for the interface and file I/O. The BrowserBridge + * handles creation and loading of the data dumps. * - * - Has a button to generate a text report. + * - Has a button to generate a JSON dump. + * + * - Has a button to import a JSON dump. * * - Shows how many events have been captured. * @constructor */ function DataView(mainBoxId, downloadIframeId, - exportFileButtonId, + saveFileButtonId, + dataViewSaveStatusTextId, securityStrippingCheckboxId, byteLoggingCheckboxId, passivelyCapturedCountId, activelyCapturedCountId, deleteAllId, dumpDataDivId, - loadDataDivId, + loadedDivId, + loadedClientInfoTextId, loadLogFileId, + dataViewLoadStatusTextId, capturingTextSpanId, loggingTextSpanId) { DivView.call(this, mainBoxId); @@ -38,8 +43,9 @@ function DataView(mainBoxId, this.downloadIframe_ = document.getElementById(downloadIframeId); - this.exportFileButton_ = document.getElementById(exportFileButtonId); - this.exportFileButton_.onclick = this.onExportToText_.bind(this); + this.saveFileButton_ = document.getElementById(saveFileButtonId); + this.saveFileButton_.onclick = this.onSaveFile_.bind(this); + this.saveStatusText_ = document.getElementById(dataViewSaveStatusTextId); this.activelyCapturedCountBox_ = document.getElementById(activelyCapturedCountId); @@ -49,13 +55,15 @@ function DataView(mainBoxId, 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); - var loadLogFileElement = document.getElementById(loadLogFileId); - loadLogFileElement.onchange = - this.logFileChanged.bind(this, loadLogFileElement); + this.loadedDiv_ = document.getElementById(loadedDivId); + this.loadedClientInfoText_ = document.getElementById(loadedClientInfoTextId); + + this.loadFileElement_ = document.getElementById(loadLogFileId); + this.loadFileElement_.onchange = this.logFileChanged.bind(this); + this.loadStatusText_ = document.getElementById(dataViewLoadStatusTextId); this.updateEventCounts_(); @@ -87,14 +95,18 @@ DataView.prototype.onAllLogEntriesDeleted = function() { }; /** - * 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. + * Called when a log file is loaded, after clearing the old log entries and + * loading the new ones. Hides parts of the page related to saving data, and + * shows those related to loading. Returns true to indicate the view should + * still be visible. */ -DataView.prototype.onSetIsViewingLogFile = function(isViewingLogFile) { - setNodeDisplay(this.dumpDataDiv_, !isViewingLogFile); - setNodeDisplay(this.capturingTextSpan_, !isViewingLogFile); - setNodeDisplay(this.loggingTextSpan_, isViewingLogFile); +DataView.prototype.onLoadLogFinish = function(data) { + setNodeDisplay(this.dumpDataDiv_, false); + setNodeDisplay(this.capturingTextSpan_, false); + setNodeDisplay(this.loggingTextSpan_, true); + setNodeDisplay(this.loadedDiv_, true); + this.updateLoadedClientInfo(); + return true; }; /** @@ -136,9 +148,10 @@ DataView.prototype.onSecurityStrippingChanged = function() { * * Gets the log file from the input element and tries to read from it. */ -DataView.prototype.logFileChanged = function(loadLogFileElement) { - var logFile = loadLogFileElement.files[0]; +DataView.prototype.logFileChanged = function() { + var logFile = this.loadFileElement_.files[0]; if (logFile) { + this.setLoadFileStatus('Loading log...', true); var fileReader = new FileReader(); fileReader.onload = this.onLoadLogFile.bind(this); @@ -150,269 +163,99 @@ DataView.prototype.logFileChanged = function(loadLogFileElement) { /** * Displays an error message when unable to read the selected log file. + * Also clears the file input control, so the same file can be reloaded. */ DataView.prototype.onLoadLogFileError = function(event) { - alert('Error ' + event.target.error.code + '. Unable to load file.'); + this.loadFileElement_.value = null; + this.setLoadFileStatus( + 'Error ' + getKeyWithValue(FileError, event.target.error.code) + + '. Unable to read file.', + false); }; -/** - * Tries to load the contents of the log file. - */ DataView.prototype.onLoadLogFile = function(event) { - g_browser.loadedLogFile(event.target.result); + var result = loadLogFile(event.target.result); + this.setLoadFileStatus(result, false); }; -DataView.prototype.enableExportFileButton_ = function(enabled) { - this.exportFileButton_.disabled = !enabled; +/** + * Sets the load from file status text, displayed below the load file button, + * to |text|. Also enables or disables the save/load buttons based on the value + * of |isLoading|, which must be true if the load process is still ongoing, and + * false when the operation has stopped, regardless of success of failure. + * Also, when loading is done, replaces the load button so the same file can be + * loaded again. + */ +DataView.prototype.setLoadFileStatus = function(text, isLoading) { + this.enableLoadFileElement_(!isLoading); + this.enableSaveFileButton_(!isLoading); + this.loadStatusText_.innerText = text; + this.saveStatusText_.innerText = ''; + + if (!isLoading) { + // Clear the button, so the same file can be reloaded. Recreating the + // element seems to be the only way to do this. + var loadFileElementId = this.loadFileElement_.id; + var loadFileElementOnChange = this.loadFileElement_.onchange; + this.loadFileElement_.outerHTML = this.loadFileElement_.outerHTML; + this.loadFileElement_ = document.getElementById(loadFileElementId); + this.loadFileElement_.onchange = loadFileElementOnChange; + } }; /** - * If not already waiting for results from all updates, triggers all - * updates and starts waiting for them to complete. + * Sets the save to file status text, displayed below the save to file button, + * to |text|. Also enables or disables the save/load buttons based on the value + * of |isSaving|, which must be true if the save process is still ongoing, and + * false when the operation has stopped, regardless of success of failure. */ -DataView.prototype.onExportToText_ = function() { - if (this.exportFileButton_.disabled) - return; - this.enableExportFileButton_(false); +DataView.prototype.setSaveFileStatus = function(text, isSaving) { + this.enableSaveFileButton_(!isSaving); + this.enableLoadFileElement_(!isSaving); + this.loadStatusText_.innerText = ''; + this.saveStatusText_.innerText = text; +}; + +DataView.prototype.enableSaveFileButton_ = function(enabled) { + this.saveFileButton_.disabled = !enabled; +}; - g_browser.updateAllInfo(this.onUpdateAllCompleted.bind(this)); +DataView.prototype.enableLoadFileElement_ = function(enabled) { + this.loadFileElement_.disabled = !enabled; }; /** - * Presents the captured data as formatted text. + * If not already busy loading or saving a log dump, triggers generation of + * gathering log dump data and starts waiting for the process to complete. */ -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()) +DataView.prototype.onSaveFile_ = function() { + if (this.saveFileButton_.disabled) 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(''); + this.setSaveFileStatus('Preparing data...', true); - 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(''); - } - } + createLogDumpAsync(this.onLogDumpCreated.bind(this)); +}; +/** + * Starts the process of creating a file containing |dumpText| and downloading + * it. + */ +DataView.prototype.onLogDumpCreated = function(dumpText) { var blobBuilder = new WebKitBlobBuilder(); - blobBuilder.append(text.join('\n'), 'native'); + blobBuilder.append(dumpText, 'native'); var textBlob = blobBuilder.getBlob('octet/stream'); + this.setSaveFileStatus('Creating file...', true); + window.webkitRequestFileSystem( window.TEMPORARY, textBlob.size, this.onFileSystemCreate_.bind(this, textBlob), this.onFileError_.bind(this, 'Unable to create file system.')); }; -// Once we have access to the file system, create a log file. +/** + * Once we have access to the file system, create a log file. + */ DataView.prototype.onFileSystemCreate_ = function(textBlob, fileSystem) { fileSystem.root.getFile( 'net_internals.log', {create: true}, @@ -420,16 +263,19 @@ DataView.prototype.onFileSystemCreate_ = function(textBlob, fileSystem) { this.onFileError_.bind(this, 'Unable to create file.')); }; -// Once the file is created, or an existing one has been opened, create a -// writer for it. +/** + * Once the file is created, or an existing one has been opened, create a + * writer for it. + */ DataView.prototype.onFileCreate_ = function(textBlob, fileEntry) { fileEntry.createWriter( this.onFileCreateWriter_.bind(this, textBlob, fileEntry), this.onFileError_.bind(this, 'Unable to create writer.')); }; -// Once the |fileWriter| has been created, truncate the file, in case it already -// existed. +/* Once the |fileWriter| has been created, truncate the file, in case it already + * existed. + */ DataView.prototype.onFileCreateWriter_ = function(textBlob, fileEntry, fileWriter) { fileWriter.onerror = this.onFileError_.bind(this, 'Truncate failed.'); @@ -438,100 +284,57 @@ DataView.prototype.onFileCreateWriter_ = function(textBlob, fileWriter.truncate(0); }; -// Once the file has been truncated, write |textBlob| to the file. +/** + * Once the file has been truncated, write |textBlob| to the file. + */ DataView.prototype.onFileTruncate_ = function(textBlob, fileWriter, fileEntry) { fileWriter.onerror = this.onFileError_.bind(this, 'Write failed.'); fileWriter.onwriteend = this.onFileWriteComplete_.bind(this, fileEntry); fileWriter.write(textBlob); }; -// Once the file has been written to, start the download. +/** + * Once the file has been written to, start the download. + */ DataView.prototype.onFileWriteComplete_ = function(fileEntry) { this.downloadIframe_.src = fileEntry.toURL(); - this.enableExportFileButton_(true); + this.setSaveFileStatus('Dump successful', false); }; -// On any Javascript File API error, enable the export button and display -// |errorText|, followed by the specific error. +/** + * On any Javascript File API error, enable the save button and display + * |errorText|, followed by the specific error. + */ DataView.prototype.onFileError_ = function(errorText, error) { - this.enableExportFileButton_(true); - alert(errorText + ' ' + getKeyWithValue(FileError, error.code)); + this.enableSaveFileButton_(true); + this.setSaveFileStatus( + errorText + ' ' + getKeyWithValue(FileError, error.code), + false); }; -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); +/** + * Prints some basic information about the environment when the log was made. + */ +DataView.prototype.updateLoadedClientInfo = function() { + this.loadedClientInfoText_.innerText = ''; + if (typeof(ClientInfo) != 'object') + return; - out.push('------------------------------------------'); - out.push(getKeyWithValue(LogSourceType, sourceType) + - ' (id=' + sourceId + ')' + - ' [start=' + startDate.toLocaleString() + ']'); - out.push('------------------------------------------'); + var text = []; - out.push(PrintSourceEntriesAsText(eventList)); + // Dumps made with the command line option don't have a date. + if (ClientInfo.date) { + text.push('Data exported on: ' + ClientInfo.date); + text.push(''); } -}; -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)); - } -}; + text.push(ClientInfo.name + + ' ' + ClientInfo.version + + ' (' + ClientInfo.official + + ' ' + ClientInfo.cl + + ') ' + ClientInfo.version_mod); + text.push('OS Type: ' + ClientInfo.os_type); + text.push('Command line: ' + ClientInfo.command_line); -/** - * 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]' : ''); + this.loadedClientInfoText_.innerText = text.join('\n'); }; diff --git a/chrome/browser/resources/net_internals/detailsview.js b/chrome/browser/resources/net_internals/detailsview.js index e3543d4..a68a6b1 100644 --- a/chrome/browser/resources/net_internals/detailsview.js +++ b/chrome/browser/resources/net_internals/detailsview.js @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. @@ -19,8 +19,8 @@ function DetailsView(tabHandlesContainerId, this.logView_ = new DetailsLogView(logBoxId); this.timelineView_ = new DetailsTimelineView(timelineBoxId); - this.addTab(logTabId, this.logView_, true); - this.addTab(timelineTabId, this.timelineView_, true); + this.addTab(logTabId, this.logView_, true, true); + this.addTab(timelineTabId, this.timelineView_, true, true); // Default to the log view. this.switchToTab(logTabId, null); diff --git a/chrome/browser/resources/net_internals/dnsview.js b/chrome/browser/resources/net_internals/dnsview.js index 8c1b6f4..b258ada 100644 --- a/chrome/browser/resources/net_internals/dnsview.js +++ b/chrome/browser/resources/net_internals/dnsview.js @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. @@ -46,6 +46,10 @@ function DnsView(mainBoxId, inherits(DnsView, DivView); +DnsView.prototype.onLoadLogFinish = function(data) { + return this.onHostResolverInfoChanged(data.hostResolverInfo); +}; + DnsView.prototype.onHostResolverInfoChanged = function(hostResolverInfo) { // Clear the existing values. this.defaultFamilySpan_.innerHTML = ''; @@ -55,8 +59,8 @@ DnsView.prototype.onHostResolverInfoChanged = function(hostResolverInfo) { this.cacheTbody_.innerHTML = ''; // No info. - if (!hostResolverInfo) - return; + if (!hostResolverInfo || !hostResolverInfo.cache) + return false; var family = hostResolverInfo.default_address_family; addTextNode(this.defaultFamilySpan_, getKeyWithValue(AddressFamily, family)); @@ -98,4 +102,6 @@ DnsView.prototype.onHostResolverInfoChanged = function(hostResolverInfo) { var expiresCell = addNode(tr, 'td'); addTextNode(expiresCell, expiresDate.toLocaleString()); } + + return true; }; diff --git a/chrome/browser/resources/net_internals/eventsview.js b/chrome/browser/resources/net_internals/eventsview.js index 8f682d5..85ad1a8 100644 --- a/chrome/browser/resources/net_internals/eventsview.js +++ b/chrome/browser/resources/net_internals/eventsview.js @@ -495,15 +495,18 @@ EventsView.prototype.onAllLogEntriesDeleted = function() { }; /** - * 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, + * Called when either a log file is loaded, after clearing the old entries, * but before getting any new ones. */ -EventsView.prototype.onSetIsViewingLogFile = function(isViewingLogFile) { +EventsView.prototype.onLoadLogStart = function() { // Needed to sort new sourceless entries correctly. this.maxReceivedSourceId_ = 0; }; +EventsView.prototype.onLoadLogFinish = function(data) { + return true; +}; + EventsView.prototype.incrementPrefilterCount = function(offset) { this.numPrefilter_ += offset; this.invalidateFilterCounter_(); diff --git a/chrome/browser/resources/net_internals/httpcacheview.js b/chrome/browser/resources/net_internals/httpcacheview.js index dd9346d..2e0e624e 100644 --- a/chrome/browser/resources/net_internals/httpcacheview.js +++ b/chrome/browser/resources/net_internals/httpcacheview.js @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. @@ -17,14 +17,22 @@ function HttpCacheView(mainBoxId, statsDivId) { inherits(HttpCacheView, DivView); +HttpCacheView.prototype.onLoadLogFinish = function(data) { + return this.onHttpCacheInfoChanged(data.httpCacheInfo); +}; + HttpCacheView.prototype.onHttpCacheInfoChanged = function(info) { this.statsDiv_.innerHTML = ''; + if (!info) + return false; + // Print the statistics. var statsUl = addNode(this.statsDiv_, 'ul'); for (var statName in info.stats) { var li = addNode(statsUl, 'li'); addTextNode(li, statName + ': ' + info.stats[statName]); } + return true; }; diff --git a/chrome/browser/resources/net_internals/index.html b/chrome/browser/resources/net_internals/index.html index a0a185f..8ada47b 100644 --- a/chrome/browser/resources/net_internals/index.html +++ b/chrome/browser/resources/net_internals/index.html @@ -15,6 +15,7 @@ found in the LICENSE file. <script src="httpcacheview.js"></script> <script src="testview.js"></script> <script src="hstsview.js"></script> + <script src="logdumputil.js"></script> <script src="main.js"></script> <script src="dnsview.js"></script> <script src="eventsview.js"></script> @@ -59,7 +60,7 @@ found in the LICENSE file. <div id=proxyTabContent> <h4> Current proxy settings - <input type=button value="Re-apply settings" id=proxyReloadSettings /> + <input type=button value="Re-apply settings" id=proxyReloadSettings class="hideOnLoadLog"/> </h4> <table><tr> @@ -92,7 +93,7 @@ found in the LICENSE file. <h4> Proxies which have failed recently, and are marked as bad - <input type=button value="Clear bad proxies" id=clearBadProxies /> + <input type=button value="Clear bad proxies" id=clearBadProxies class="hideOnLoadLog" /> </h4> <table class="styledTable"> <thead> @@ -119,7 +120,7 @@ found in the LICENSE file. <h4> Host resolver cache - <input type=button value="Clear host cache" id=clearHostResolverCache /> + <input type=button value="Clear host cache" id=clearHostResolverCache class="hideOnLoadLog" /> </h4> <ul> <li>Capacity: <span id=hostResolverCacheCapacity></span></li> @@ -145,9 +146,11 @@ found in the LICENSE file. <div id=socketsTabContent> <h4>Socket pools</h4> <ul> - <li><input type=button value="Close idle sockets" id=socketPoolCloseIdleButton /> + <li class="hideOnLoadLog"> + <input type=button value="Close idle sockets" id=socketPoolCloseIdleButton /> </li> - <li><input type=button value="Flush socket pools" id=socketPoolFlushButton /> + <li class="hideOnLoadLog"> + <input type=button value="Flush socket pools" id=socketPoolFlushButton /> <span class=warningText >May break pages with active connections</span> </li> <li><a href='#events&q=type:SOCKET%20is:active'>View live sockets</a> @@ -189,8 +192,10 @@ found in the LICENSE file. </div> </div> <div id=httpCacheTabContent> - <h4>Entries</h4> - <a href="chrome://view-http-cache" target=_blank>Explore cache entries</a> + <div class="hideOnLoadLog"> + <h4>Entries</h4> + <a href="chrome://view-http-cache" target=_blank>Explore cache entries</a> + </div> <h4>Statistics</h4> <div id=httpCacheStats>Nothing loaded yet.</div> @@ -277,19 +282,27 @@ found in the LICENSE file. Help: How to get data for bug reports? </a> </p> - <button id=exportToFile class=bigButton>Dump to file</button> + <button id=dataViewSaveLogFile class=bigButton>Dump to file</button> + <pre id=dataViewSaveStatusText></pre> </div> </div> - <div id=dataViewLoadDataDiv> + <div id=dataViewLoadedDiv style="display: none"> + <h2>Data Loaded</h2> + <p id=dataViewLoadedClientInfoText></p> + </div> + <div> <h2>Load data</h2> + <p><input type=file value="Load log from file" id=dataViewLoadLogFile /></p> + <pre id=dataViewLoadStatusText></pre> <div style="margin: 8px"> - <p><input type=file value="Load log from file" id=dataViewLoadLogFile /></p> - <p>Only works with log files created with "--log-net-log=file_name". - "--net-log-level=#" will set the default log level used. + <p>Works for both log dumps generated from this page with Chrome 14 + and later, and with log files created with + "--log-net-log=file_name". "--net-log-level=#" will set the + default log level used. </p> <p>Once a log is loaded, this page will stop collecting data, and will - only start gathering data again when the page is - <a href="javascript:history.go(0);">reloaded</a>.<BR> + only start gathering data again when + <a href="javascript:history.go(0);">reloaded</a>. </p> </div> </div> @@ -305,7 +318,7 @@ found in the LICENSE file. </span> <table style="margin: 8px"> <tr> - <td>Passively captured:</td> + <td>Passively captured:</td> <td align=right id=passivelyCapturedCount></td> </tr> <tr> @@ -313,8 +326,10 @@ found in the LICENSE file. <td align=right id=activelyCapturedCount></td> </tr> </table> - <p><input type=button value="Delete all" id=dataViewDeleteAll /></p> - <p><input id=byteLoggingCheckbox type=checkbox> + <p class="hideOnLoadLog"> + <input type=button value="Delete all" id=dataViewDeleteAll /> + </p> + <p class="hideOnLoadLog"><input id=byteLoggingCheckbox type=checkbox /> Log actual bytes sent/received. </p> </div> @@ -350,6 +365,8 @@ function displayHelpForBugDump() { <li>Email the log file to the bug investigator, <b>along with an explanation of what went wrong, including any relevant URLs.</b> + You may want to compress the file before sending, as the log files can be + fairly large but compress well. </li> </ol> @@ -360,7 +377,8 @@ function displayHelpForBugDump() { IP addresses, URLs, and cookies.</li> <ul> <li>You can edit the log to obscure information if you like, but sometimes it - is relevant to the bug.</li> + is relevant to the bug. If you choose do this, please make sure you can still + load the log file.</li> <li>If you choose not to have cookies removed from the log, you must toggle the checkbox before clicking the button.</li> </ul> diff --git a/chrome/browser/resources/net_internals/logdumputil.js b/chrome/browser/resources/net_internals/logdumputil.js new file mode 100644 index 0000000..94501eb --- /dev/null +++ b/chrome/browser/resources/net_internals/logdumputil.js @@ -0,0 +1,229 @@ +// 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. + +var createLogDumpAsync; +var loadLogFile; + +// Start of anonymous namespace. +(function() { + +/** + * Creates a new log dump. |events| is a list of all events, |polledData| is an + * object containing the results of each poll, |tabData| is an object containing + * data for individual tabs, and |date| is the time the dump was created, as a + * formatted string. + * Returns the new log dump as an object. |date| may be null. + * + * Log dumps are just JSON objects containing four values: + * |constants| needed to interpret the data. This also includes some browser + * state information + * |events| from the NetLog, + * |polledData| from each PollableDataHelper available on the source OS, + * |tabData| containing any tab-specific state that's not present in + * |polledData|. + * + * |polledData| and |tabData| may be empty objects, or may be missing data for + * tabs not present on the OS the log is from. + */ +function createLogDump(constants, events, polledData, tabData, date) { + if (g_browser.getSecurityStripping()) + events = events.map(stripCookiesAndLoginInfo); + + var logDump = { + 'constants': constants, + 'events': events, + 'polledData': polledData, + 'tabData': tabData + }; + + // Not technically client info, but it's used at the same point in the code. + if (date && constants.clientInfo) + constants.clientInfo.date = date; + + return logDump; +} + +/** + * Creates a full log dump using |polledData| and the return value of each tab's + * saveState function and passes it to |callback|. + */ +function onUpdateAllCompleted(callback, polledData) { + // Gather any tab-specific state information. + var tabData = {}; + var categoryTabSwitcher = g_browser.categoryTabSwitcher(); + var tabIds = categoryTabSwitcher.getAllTabIds(); + for (var i = 0; i < tabIds.length; ++i) { + var view = categoryTabSwitcher.findTabById(tabIds[i]).contentView; + if (view.saveState) + tabData[tabIds[i]] = view.saveState(); + } + + var logDump = createLogDump(Constants, + g_browser.getAllCapturedEvents(), + polledData, + tabData, + (new Date()).toLocaleString()); + callback(JSON.stringify(logDump, null, ' ')); +} + +/** + * Called to create a new log dump. Must not be called once a dump has been + * loaded. Once a log dump has been created, |callback| is passed the dumped + * text as a string. + */ +createLogDumpAsync = function(callback) { + g_browser.updateAllInfo(onUpdateAllCompleted.bind(null, callback)); +}; + +/** + * Loads a full log dump. Returns a string containing a log of the load. + * The process goes like this: + * 1) Load constants. If this fails, or the version number can't be handled, + * abort the load. If this step succeeds, the load cannot be aborted. + * 2) Clear all events. Any event observers are informed of the clear as + * normal. + * 3) Call onLoadLogStart(polledData, tabData) for each view with an + * onLoadLogStart function. This allows tabs to clear any extra state that + * would affect the next step. |polledData| contains the data polled for + * all helpers, but |tabData| contains only the data from that specific tab. + * 4) Add all events from the log file. + * 5) Call onLoadLogFinish(polledData, tabData) for each view with an + * onLoadLogFinish function. The arguments are the same as in step 3. If + * there is no onLoadLogFinish function, it throws an exception, or it + * returns false instead of true, the data dump is assumed to contain no + * valid data for the tab, so the tab is hidden. Otherwise, the tab is + * shown. + */ +function loadLogDump(logDump) { + // Perform minimal validity check, and abort if it fails. + if (typeof(logDump) != 'object') + return 'Load failed. Top level JSON data is not an object.'; + + // String listing text summary of load errors, if any. + var errorString = ''; + + if (!g_browser.areValidConstants(logDump.constants)) + errorString += 'Invalid constants object.\n'; + if (typeof(logDump.events) != 'object') + errorString += 'NetLog events missing.\n'; + if (typeof(logDump.constants.logFormatVersion) != 'number') + errorString += 'Invalid version number.\n'; + + if (errorString.length > 0) + return 'Load failed:\n\n' + errorString; + + if (typeof(logDump.polledData) != 'object') + logDump.polledData = {}; + if (typeof(logDump.tabData) != 'object') + logDump.tabData = {}; + + if (logDump.constants.logFormatVersion != + g_browser.logFormatVersion()) { + return 'Unable to load different log version.' + + ' Found ' + logDump.constants.logFormatVersion + + ', Expected ' + g_browser.logFormatVersion(); + } + if (!g_browser.loadConstants(logDump.constants)) + return 'Expected constants not found in log file.'; + + // Prevent communication with the browser. Once the constants have been + // loaded, it's safer to continue trying to load the log, even in the case of + // bad data. + g_browser.onLoadLogFile(); + + // Delete all events. This will also update all logObservers. + g_browser.deleteAllEvents(); + + // Inform all the views that a log file is being loaded, and pass in + // view-specific saved state, if any. + var categoryTabSwitcher = g_browser.categoryTabSwitcher(); + var tabIds = categoryTabSwitcher.getAllTabIds(); + for (var i = 0; i < tabIds.length; ++i) { + var view = categoryTabSwitcher.findTabById(tabIds[i]).contentView; + view.onLoadLogStart(logDump.polledData, logDump.tabData[tabIds[i]]); + } + + // Check for validity of each log entry, and then add the ones that pass. + // Since the events are kept around, and we can't just hide a single view + // on a bad event, we have more error checking for them than other data. + var validPassiveEvents = []; + var validActiveEvents = []; + for (var eventIndex = 0; eventIndex < logDump.events.length; ++eventIndex) { + var event = logDump.events[eventIndex]; + if (typeof(event) == 'object' && typeof(event.source) == 'object' && + typeof(event.time) == 'string' && + getKeyWithValue(LogEventType, event.type) != '?' && + getKeyWithValue(LogSourceType, event.source.type) != '?' && + getKeyWithValue(LogEventPhase, event.phase) != '?') { + if (event.wasPassivelyCaptured) { + validPassiveEvents.push(event); + } else { + validActiveEvents.push(event); + } + } + } + g_browser.receivedPassiveLogEntries(validPassiveEvents); + g_browser.addLogEntries(validActiveEvents); + + var numInvalidEvents = logDump.events.length + - validPassiveEvents.length + - validActiveEvents.length; + if (numInvalidEvents > 0) { + errorString += 'Unable to load ' + numInvalidEvents + + ' events, due to invalid data.\n\n'; + } + + // Update all views with data from the file. Show only those views which + // successfully load the data. + for (var i = 0; i < tabIds.length; ++i) { + var view = categoryTabSwitcher.findTabById(tabIds[i]).contentView; + var showView = false; + // The try block eliminates the need for checking every single value before + // trying to access it. + try { + if (view.onLoadLogFinish(logDump.polledData, + logDump.tabData[tabIds[i]])) { + showView = true; + } + } catch (error) { + } + categoryTabSwitcher.showTabHandleNode(tabIds[i], showView); + } + + return errorString + 'Log loaded.'; +} + +/** + * Loads a log dump from the string |logFileContents|, which can be either a + * full net-internals dump, or a NetLog dump only. Returns a string containing + * a log of the load. + */ +loadLogFile = function(logFileContents) { + // Try and parse the log dump as a single JSON string. If this succeeds, + // it's most likely a full log dump. Otherwise, it may be a dump created by + // --log-net-log. + var parsedDump = null; + try { + parsedDump = JSON.parse(logFileContents); + } catch (error) { + try { + // We may have a --log-net-log=blah log dump. If so, remove the comma + // after the final good entry, and add the necessary close brackets. + var end = Math.max(logFileContents.lastIndexOf(',\n'), + logFileContents.lastIndexOf(',\r')); + if (end != -1) + parsedDump = JSON.parse(logFileContents.substring(0, end) + ']}'); + } + catch (error) { + } + } + + if (!parsedDump) + return 'Unable to parse log dump as JSON file.'; + return loadLogDump(parsedDump); +}; + +// End of anonymous namespace. +})(); + diff --git a/chrome/browser/resources/net_internals/logviewpainter.js b/chrome/browser/resources/net_internals/logviewpainter.js index 8199a6d..b95cb5d 100644 --- a/chrome/browser/resources/net_internals/logviewpainter.js +++ b/chrome/browser/resources/net_internals/logviewpainter.js @@ -12,6 +12,7 @@ var PaintLogView; var PrintSourceEntriesAsText; var proxySettingsToString; +var stripCookiesAndLoginInfo; // Start of anonymous namespace. (function() { @@ -24,8 +25,6 @@ PaintLogView = function(sourceEntries, node) { } } -const INDENTATION_PX = 20; - function addSourceEntry_(node, sourceEntry) { var div = addNode(node, 'div'); div.className = 'logSourceEntry'; @@ -180,15 +179,19 @@ function formatHexString(hexString, asciiCharsPerLine) { function getTextForExtraParams(entry, enableSecurityStripping) { // Format the extra parameters (use a custom formatter for certain types, // but default to displaying as JSON). + + // If security stripping is enabled, remove data as needed. + if (enableSecurityStripping) + entry = stripCookiesAndLoginInfo(entry); + switch (entry.type) { case LogEventType.HTTP_TRANSACTION_SEND_REQUEST_HEADERS: case LogEventType.HTTP_TRANSACTION_SEND_TUNNEL_HEADERS: - return getTextForRequestHeadersExtraParam(entry, enableSecurityStripping); + return getTextForRequestHeadersExtraParam(entry); case LogEventType.HTTP_TRANSACTION_READ_RESPONSE_HEADERS: case LogEventType.HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS: - return getTextForResponseHeadersExtraParam(entry, - enableSecurityStripping); + return getTextForResponseHeadersExtraParam(entry); case LogEventType.PROXY_CONFIG_CHANGED: return getTextForProxyConfigChangedExtraParam(entry); @@ -197,9 +200,7 @@ function getTextForExtraParams(entry, enableSecurityStripping) { var out = []; for (var k in entry.params) { if (k == 'headers' && entry.params[k] instanceof Array) { - out.push( - getTextForResponseHeadersExtraParam(entry, - enableSecurityStripping)); + out.push(getTextForResponseHeadersExtraParam(entry)); continue; } var value = entry.params[k]; @@ -324,31 +325,37 @@ function stripCookieOrLoginInfo(line) { } /** - * Removes all cookie and unencrypted login text from a list of HTTP - * header lines. + * If |entry| has headers, returns a copy of |entry| with all cookie and + * unencrypted login text removed. Otherwise, returns original |entry| object. + * This is needed so that JSON log dumps can be made without affecting the + * source data. */ -function stripCookiesAndLoginInfo(headers) { - return headers.map(stripCookieOrLoginInfo); +stripCookiesAndLoginInfo = function(entry) { + if (!entry.params || !entry.params.headers || + !(entry.params.headers instanceof Array)) { + return entry; + } + + // Duplicate the top level object, and |entry.params|. All other fields are + // just pointers to the original values, as they won't be modified, other than + // |entry.params.headers|. + entry = shallowCloneObject(entry); + entry.params = shallowCloneObject(entry.params); + + entry.params.headers = entry.params.headers.map(stripCookieOrLoginInfo); + return entry; } -function getTextForRequestHeadersExtraParam(entry, enableSecurityStripping) { +function getTextForRequestHeadersExtraParam(entry) { var params = entry.params; // Strip the trailing CRLF that params.line contains. var lineWithoutCRLF = params.line.replace(/\r\n$/g, ''); - - var headers = params.headers; - if (enableSecurityStripping) - headers = stripCookiesAndLoginInfo(headers); - - return indentLines(' --> ', [lineWithoutCRLF].concat(headers)); + return indentLines(' --> ', [lineWithoutCRLF].concat(params.headers)); } -function getTextForResponseHeadersExtraParam(entry, enableSecurityStripping) { - var headers = entry.params.headers; - if (enableSecurityStripping) - headers = stripCookiesAndLoginInfo(headers); - return indentLines(' --> ', headers); +function getTextForResponseHeadersExtraParam(entry) { + return indentLines(' --> ', entry.params.headers); } function getTextForProxyConfigChangedExtraParam(entry) { diff --git a/chrome/browser/resources/net_internals/main.js b/chrome/browser/resources/net_internals/main.js index 8333761..af44622 100644 --- a/chrome/browser/resources/net_internals/main.js +++ b/chrome/browser/resources/net_internals/main.js @@ -3,7 +3,7 @@ // found in the LICENSE file. /** - * Dictionary of constants (initialized by browser). + * Dictionary of constants (initialized by browser, updated on load log). */ var LogEventType = null; var LogEventPhase = null; @@ -14,6 +14,9 @@ var NetError = null; var LoadFlag = null; var AddressFamily = null; +// Dictionary of all constants, used for saving log files. +var Constants = null; + /** * Object to communicate between the renderer and the browser. * @type {!BrowserBridge} @@ -71,14 +74,17 @@ function onLoaded() { 'hostResolverCacheTTLSuccess', 'hostResolverCacheTTLFailure'); - // Create a view which will display import/export options to control the + // Create a view which will display save/load options to control the // captured data. var dataView = new DataView('dataTabContent', 'dataViewDownloadIframe', - 'exportToFile', 'securityStrippingCheckbox', + 'dataViewSaveLogFile', 'dataViewSaveStatusText', + 'securityStrippingCheckbox', 'byteLoggingCheckbox', 'passivelyCapturedCount', 'activelyCapturedCount', 'dataViewDeleteAll', - 'dataViewDumpDataDiv', 'dataViewLoadDataDiv', - 'dataViewLoadLogFile', + 'dataViewDumpDataDiv', + 'dataViewLoadedDiv', + 'dataViewLoadedClientInfoText', + 'dataViewLoadLogFile', 'dataViewLoadStatusText', 'dataViewCapturingTextSpan', 'dataViewLoggingTextSpan'); @@ -115,25 +121,19 @@ function onLoaded() { 'spdySessionLinkSpan', 'spdySessionDiv'); - var serviceView; - if (g_browser.isPlatformWindows()) { - serviceView = new ServiceProvidersView('serviceProvidersTab', - 'serviceProvidersTabContent', - 'serviceProvidersTbody', - 'namespaceProvidersTbody'); - } + var serviceView = new ServiceProvidersView('serviceProvidersTab', + 'serviceProvidersTabContent', + 'serviceProvidersTbody', + 'namespaceProvidersTbody'); var httpThrottlingView = new HttpThrottlingView( 'httpThrottlingTabContent', 'enableHttpThrottlingCheckbox'); - var logsView; - if (g_browser.isChromeOS()) { - logsView = new LogsView('logsTabContent', - 'logTable', - 'logsGlobalShowBtn', - 'logsGlobalHideBtn', - 'logsRefreshBtn'); - } + var logsView = new LogsView('logsTabContent', + 'logTable', + 'logsGlobalShowBtn', + 'logsGlobalHideBtn', + 'logsRefreshBtn'); var prerenderView = new PrerenderView('prerenderTabContent', 'prerenderEnabledSpan', @@ -144,22 +144,25 @@ function onLoaded() { var categoryTabSwitcher = new TabSwitcherView('categoryTabHandles'); g_browser.setTabSwitcher(categoryTabSwitcher); - // Populate the main tabs. - categoryTabSwitcher.addTab('eventsTab', eventsView, false); - categoryTabSwitcher.addTab('proxyTab', proxyView, false); - categoryTabSwitcher.addTab('dnsTab', dnsView, false); - categoryTabSwitcher.addTab('socketsTab', socketsView, false); - categoryTabSwitcher.addTab('spdyTab', spdyView, false); - categoryTabSwitcher.addTab('httpCacheTab', httpCacheView, false); - categoryTabSwitcher.addTab('dataTab', dataView, false); - if (g_browser.isPlatformWindows()) - categoryTabSwitcher.addTab('serviceProvidersTab', serviceView, false); - categoryTabSwitcher.addTab('testTab', testView, false); - categoryTabSwitcher.addTab('hstsTab', hstsView, false); - categoryTabSwitcher.addTab('httpThrottlingTab', httpThrottlingView, false); - if (g_browser.isChromeOS()) - categoryTabSwitcher.addTab('logsTab', logsView, false); - categoryTabSwitcher.addTab('prerenderTab', prerenderView, false); + // Populate the main tabs. Even tabs that don't contain information for the + // running OS should be created, so they can load log dumps from other + // OSes. + categoryTabSwitcher.addTab('eventsTab', eventsView, false, true); + categoryTabSwitcher.addTab('proxyTab', proxyView, false, true); + categoryTabSwitcher.addTab('dnsTab', dnsView, false, true); + categoryTabSwitcher.addTab('socketsTab', socketsView, false, true); + categoryTabSwitcher.addTab('spdyTab', spdyView, false, true); + categoryTabSwitcher.addTab('httpCacheTab', httpCacheView, false, true); + categoryTabSwitcher.addTab('dataTab', dataView, false, true); + categoryTabSwitcher.addTab('serviceProvidersTab', serviceView, false, + g_browser.isPlatformWindows()); + categoryTabSwitcher.addTab('testTab', testView, false, true); + categoryTabSwitcher.addTab('hstsTab', hstsView, false, true); + categoryTabSwitcher.addTab('httpThrottlingTab', httpThrottlingView, false, + true); + categoryTabSwitcher.addTab('logsTab', logsView, false, + g_browser.isChromeOS()); + categoryTabSwitcher.addTab('prerenderTab', prerenderView, false, true); // Build a map from the anchor name of each tab handle to its "tab ID". // We will consider navigations to the #hash as a switch tab request. @@ -184,9 +187,6 @@ function onLoaded() { // Select the initial view based on the current URL. window.onhashchange(); - // Inform observers a log file is not currently being displayed. - g_browser.setIsViewingLogFile_(false); - // Tell the browser that we are ready to start receiving log events. g_browser.sendReady(); } @@ -204,6 +204,10 @@ function BrowserBridge() { this.hstsObservers_ = []; this.httpThrottlingObservers_ = []; + // This is set to true when a log file is being viewed to block all + // communication with the browser. + this.isViewingLogFile_ = false; + this.pollableDataHelpers_ = {}; this.pollableDataHelpers_.proxySettings = new PollableDataHelper('onProxySettingsChanged', @@ -247,11 +251,6 @@ function BrowserBridge() { // Needed to simplify deletion, identify associated GUI elements, etc. this.nextSourcelessEventId_ = -1; - // True when viewing a log file rather than actively logged events. - // When viewing a log file, all tabs are hidden except the event view, - // and all received events are ignored. - this.isViewingLogFile_ = false; - // True when cookies and authentication information should be removed from // displayed events. When true, such information should be hidden from // all pages. @@ -290,6 +289,47 @@ function onUrlHashChange(anchorMap, categoryTabSwitcher) { } /** + * Returns true if |constants| appears to be a valid constants object. + */ +BrowserBridge.prototype.areValidConstants = function(constants) { + return typeof(constants) == 'object' && + typeof(constants.logEventTypes) == 'object' && + typeof(constants.clientInfo) == 'object' && + typeof(constants.logEventPhase) == 'object' && + typeof(constants.logSourceType) == 'object' && + typeof(constants.logLevelType) == 'object' && + typeof(constants.loadFlag) == 'object' && + typeof(constants.netError) == 'object' && + typeof(constants.addressFamily) == 'object' && + typeof(constants.timeTickOffset) == 'string' && + typeof(constants.logFormatVersion) == 'number'; +}; + +/** + * Attempts to load all constants from |constants|. Returns false if one or + * more entries are missing. On failure, global dictionaries are not modified. + */ +BrowserBridge.prototype.loadConstants = function(constants) { + if (!this.areValidConstants(constants)) + return false; + + LogEventType = constants.logEventTypes; + ClientInfo = constants.clientInfo; + LogEventPhase = constants.logEventPhase; + LogSourceType = constants.logSourceType; + LogLevelType = constants.logLevelType; + LoadFlag = constants.loadFlag; + NetError = constants.netError; + AddressFamily = constants.addressFamily; + this.timeTickOffset_ = constants.timeTickOffset; + + // Used for saving dumps. + Constants = constants; + + return true; +}; + +/** * Delay in milliseconds between updates of certain browser information. */ BrowserBridge.POLL_INTERVAL_MS = 5000; @@ -298,8 +338,16 @@ BrowserBridge.POLL_INTERVAL_MS = 5000; // Messages sent to the browser //------------------------------------------------------------------------------ +/** + * Wraps |chrome.send|. Doesn't send anything when viewing a log file. + */ +BrowserBridge.prototype.send = function(value) { + if (!this.isViewingLogFile_) + chrome.send(value); +}; + BrowserBridge.prototype.sendReady = function() { - chrome.send('notifyReady'); + this.send('notifyReady'); // Some of the data we are interested is not currently exposed as a stream, // so we will poll the browser to find out when it changes and then notify @@ -318,155 +366,124 @@ BrowserBridge.prototype.isChromeOS = function() { BrowserBridge.prototype.sendGetProxySettings = function() { // The browser will call receivedProxySettings on completion. - chrome.send('getProxySettings'); + this.send('getProxySettings'); }; BrowserBridge.prototype.sendReloadProxySettings = function() { - chrome.send('reloadProxySettings'); + this.send('reloadProxySettings'); }; BrowserBridge.prototype.sendGetBadProxies = function() { // The browser will call receivedBadProxies on completion. - chrome.send('getBadProxies'); + this.send('getBadProxies'); }; BrowserBridge.prototype.sendGetHostResolverInfo = function() { // The browser will call receivedHostResolverInfo on completion. - chrome.send('getHostResolverInfo'); + this.send('getHostResolverInfo'); }; BrowserBridge.prototype.sendClearBadProxies = function() { - chrome.send('clearBadProxies'); + this.send('clearBadProxies'); }; BrowserBridge.prototype.sendClearHostResolverCache = function() { - chrome.send('clearHostResolverCache'); + this.send('clearHostResolverCache'); }; BrowserBridge.prototype.sendStartConnectionTests = function(url) { - chrome.send('startConnectionTests', [url]); + this.send('startConnectionTests', [url]); }; BrowserBridge.prototype.sendHSTSQuery = function(domain) { - chrome.send('hstsQuery', [domain]); + this.send('hstsQuery', [domain]); }; BrowserBridge.prototype.sendHSTSAdd = function(domain, include_subdomains, pins) { - chrome.send('hstsAdd', [domain, include_subdomains, pins]); + this.send('hstsAdd', [domain, include_subdomains, pins]); }; BrowserBridge.prototype.sendHSTSDelete = function(domain) { - chrome.send('hstsDelete', [domain]); + this.send('hstsDelete', [domain]); }; BrowserBridge.prototype.sendGetHttpCacheInfo = function() { - chrome.send('getHttpCacheInfo'); + this.send('getHttpCacheInfo'); }; BrowserBridge.prototype.sendGetSocketPoolInfo = function() { - chrome.send('getSocketPoolInfo'); + this.send('getSocketPoolInfo'); }; BrowserBridge.prototype.sendCloseIdleSockets = function() { - chrome.send('closeIdleSockets'); + this.send('closeIdleSockets'); }; BrowserBridge.prototype.sendFlushSocketPools = function() { - chrome.send('flushSocketPools'); + this.send('flushSocketPools'); }; BrowserBridge.prototype.sendGetSpdySessionInfo = function() { - chrome.send('getSpdySessionInfo'); + this.send('getSpdySessionInfo'); }; BrowserBridge.prototype.sendGetSpdyStatus = function() { - chrome.send('getSpdyStatus'); + this.send('getSpdyStatus'); }; BrowserBridge.prototype.sendGetSpdyAlternateProtocolMappings = function() { - chrome.send('getSpdyAlternateProtocolMappings'); + this.send('getSpdyAlternateProtocolMappings'); }; BrowserBridge.prototype.sendGetServiceProviders = function() { - chrome.send('getServiceProviders'); + this.send('getServiceProviders'); }; BrowserBridge.prototype.sendGetPrerenderInfo = function() { - chrome.send('getPrerenderInfo'); -} + this.send('getPrerenderInfo'); +}; BrowserBridge.prototype.enableIPv6 = function() { - chrome.send('enableIPv6'); + this.send('enableIPv6'); }; BrowserBridge.prototype.setLogLevel = function(logLevel) { - chrome.send('setLogLevel', ['' + logLevel]); + this.send('setLogLevel', ['' + logLevel]); }; BrowserBridge.prototype.enableHttpThrottling = function(enable) { - chrome.send('enableHttpThrottling', [enable]); + this.send('enableHttpThrottling', [enable]); }; BrowserBridge.prototype.refreshSystemLogs = function() { - chrome.send('refreshSystemLogs'); + this.send('refreshSystemLogs'); }; BrowserBridge.prototype.getSystemLog = function(log_key, cellId) { - chrome.send('getSystemLog', [log_key, cellId]); + this.send('getSystemLog', [log_key, cellId]); }; //------------------------------------------------------------------------------ -// Messages received from the browser +// Messages received from the browser. //------------------------------------------------------------------------------ -BrowserBridge.prototype.receivedLogEntries = function(logEntries) { +BrowserBridge.prototype.receive = function(command, params) { // Does nothing if viewing a log file. if (this.isViewingLogFile_) return; - this.addLogEntries(logEntries); -}; - -BrowserBridge.prototype.receivedLogEventTypeConstants = function(constantsMap) { - LogEventType = constantsMap; -}; - -BrowserBridge.prototype.receivedClientInfo = -function(info) { - ClientInfo = info; -}; - -BrowserBridge.prototype.receivedLogEventPhaseConstants = -function(constantsMap) { - LogEventPhase = constantsMap; + this[command](params); }; -BrowserBridge.prototype.receivedLogSourceTypeConstants = -function(constantsMap) { - LogSourceType = constantsMap; -}; - -BrowserBridge.prototype.receivedLogLevelConstants = -function(constantsMap) { - LogLevelType = constantsMap; -}; +BrowserBridge.prototype.receivedConstants = function(constants) { + this.logFormatVersion_ = constants.logFormatVersion; -BrowserBridge.prototype.receivedLoadFlagConstants = function(constantsMap) { - LoadFlag = constantsMap; + this.loadConstants(constants); }; -BrowserBridge.prototype.receivedNetErrorConstants = function(constantsMap) { - NetError = constantsMap; -}; - -BrowserBridge.prototype.receivedAddressFamilyConstants = -function(constantsMap) { - AddressFamily = constantsMap; -}; - -BrowserBridge.prototype.receivedTimeTickOffset = function(timeTickOffset) { - this.timeTickOffset_ = timeTickOffset; +BrowserBridge.prototype.receivedLogEntries = function(logEntries) { + this.addLogEntries(logEntries); }; BrowserBridge.prototype.receivedProxySettings = function(proxySettings) { @@ -573,84 +590,33 @@ BrowserBridge.prototype.receivedPrerenderInfo = function(prerenderInfo) { this.pollableDataHelpers_.prerenderInfo.update(prerenderInfo); }; -BrowserBridge.prototype.loadedLogFile = function(logFileContents) { - var match; - // Replace carriage returns with linebreaks and then split around linebreaks. - var lines = logFileContents.replace(/\r/g, '\n').split('\n'); - var entries = []; - var numInvalidLines = 0; - - for (var i = 0; i < lines.length; ++i) { - if (lines[i].trim().length == 0) - continue; - // Parse all valid lines, skipping any others. - try { - var entry = JSON.parse(lines[i]); - if (entry && - typeof(entry) == 'object' && - entry.phase != undefined && - entry.source != undefined && - entry.time != undefined && - entry.type != undefined) { - entries.push(entry); - continue; - } - } catch (err) { - } - ++numInvalidLines; - console.log('Unable to parse log line: ' + lines[i]); - } - - if (entries.length == 0) { - window.alert('Loading log file failed.'); - return; - } +//------------------------------------------------------------------------------ - this.deleteAllEvents(); +BrowserBridge.prototype.categoryTabSwitcher = function() { + return this.categoryTabSwitcher_; +}; - this.setIsViewingLogFile_(true); +BrowserBridge.prototype.logFormatVersion = function() { + return this.logFormatVersion_; +}; - var validEntries = []; - for (var i = 0; i < entries.length; ++i) { - entries[i].wasPassivelyCaptured = true; - if (LogEventType[entries[i].type] != undefined && - LogSourceType[entries[i].source.type] != undefined && - LogEventPhase[entries[i].phase] != undefined) { - entries[i].type = LogEventType[entries[i].type]; - entries[i].source.type = LogSourceType[entries[i].source.type]; - entries[i].phase = LogEventPhase[entries[i].phase]; - validEntries.push(entries[i]); - } else { - // TODO(mmenke): Do something reasonable when the event type isn't - // found, which could happen when event types are - // removed or added between versions. Could also happen - // with source types, but less likely. - console.log( - 'Unrecognized values in log entry: ' + JSON.stringify(entries[i])); - } - } +BrowserBridge.prototype.isViewingLogFile = function() { + return this.isViewingLogFile_; +}; - this.numPassivelyCapturedEvents_ = validEntries.length; - this.addLogEntries(validEntries); - - var numInvalidEntries = entries.length - validEntries.length; - if (numInvalidEntries > 0 || numInvalidLines > 0) { - window.alert( - numInvalidLines.toString() + - ' entries could not be parsed as JSON strings, and ' + - numInvalidEntries.toString() + - ' entries don\'t have valid data.\n\n' + - 'Unparseable lines may indicate log file corruption.\n' + - 'Entries with invalid data may be caused by version differences.\n\n' + - 'See console for more information.'); +/** + * Prevents receiving/sending events to/from the browser, so loaded data will + * not be mixed with current Chrome state. Also hides any interactive HTML + * elements that send messages to the browser. Cannot be undone without + * reloading the page. + */ +BrowserBridge.prototype.onLoadLogFile = function() { + if (!this.isViewingLogFile_) { + this.isViewingLogFile_ = true; + this.setSecurityStripping(false); + document.styleSheets[0].insertRule('.hideOnLoadLog { display: none; }'); } -} - -BrowserBridge.prototype.getSystemLogCallback = function(result) { - document.getElementById(result.cellId).textContent = result.log; -} - -//------------------------------------------------------------------------------ +}; /** * Sets the |categoryTabSwitcher_| of BrowserBridge. Since views depend on @@ -766,9 +732,13 @@ BrowserBridge.prototype.addSpdyAlternateProtocolMappingsObserver = * back when data is received, through: * * observer.onServiceProvidersChanged(serviceProviders) + * + * Will do nothing if on a platform other than Windows, as service providers are + * only present on Windows. */ BrowserBridge.prototype.addServiceProvidersObserver = function(observer) { - this.pollableDataHelpers_.serviceProviders.addObserver(observer); + if (this.pollableDataHelpers_.serviceProviders) + this.pollableDataHelpers_.serviceProviders.addObserver(observer); }; /** @@ -822,7 +792,7 @@ BrowserBridge.prototype.addHttpThrottlingObserver = function(observer) { */ BrowserBridge.prototype.addPrerenderInfoObserver = function(observer) { this.pollableDataHelpers_.prerenderInfo.addObserver(observer); -} +}; /** * The browser gives us times in terms of "time ticks" in milliseconds. @@ -938,32 +908,6 @@ BrowserBridge.prototype.getSecurityStripping = function() { }; /** - * Informs log observers whether or not future events will be from a log file. - * Hides all tabs except the events and data tabs when viewing a log file, shows - * them all otherwise. - */ -BrowserBridge.prototype.setIsViewingLogFile_ = function(isViewingLogFile) { - this.isViewingLogFile_ = isViewingLogFile; - var tabIds = this.categoryTabSwitcher_.getAllTabIds(); - - for (var i = 0; i < this.logObservers_.length; ++i) - this.logObservers_[i].onSetIsViewingLogFile(isViewingLogFile); - - // Shows/hides tabs not used when viewing a log file. - for (var i = 0; i < tabIds.length; ++i) { - if (tabIds[i] == 'eventsTab' || tabIds[i] == 'dataTab') - continue; - this.categoryTabSwitcher_.showTabHandleNode(tabIds[i], !isViewingLogFile); - } - - if (isViewingLogFile) { - var activeTab = this.categoryTabSwitcher_.findActiveTab(); - if (activeTab.id != 'eventsTab') - this.categoryTabSwitcher_.switchToTab('dataTab', null); - } -}; - -/** * Returns true if a log file is currently being viewed. */ BrowserBridge.prototype.isViewingLogFile = function() { @@ -1010,6 +954,14 @@ PollableDataHelper.prototype.getObserverMethodName = function() { return this.observerMethodName_; }; +PollableDataHelper.prototype.isObserver = function(object) { + for (var i = 0; i < this.observerInfos_.length; ++i) { + if (this.observerInfos_[i].observer == object) + return true; + } + return false; +}; + /** * This is a helper class used by PollableDataHelper, to keep track of * each observer and whether or not it has received any data. The @@ -1028,7 +980,7 @@ PollableDataHelper.prototype.addObserver = function(observer) { PollableDataHelper.prototype.removeObserver = function(observer) { for (var i = 0; i < this.observerInfos_.length; ++i) { - if (this.observerInfos_[i].observer == observer) { + if (this.observerInfos_[i].observer === observer) { this.observerInfos_.splice(i, 1); return; } diff --git a/chrome/browser/resources/net_internals/prerenderview.js b/chrome/browser/resources/net_internals/prerenderview.js index 7edcb14..318addf 100644 --- a/chrome/browser/resources/net_internals/prerenderview.js +++ b/chrome/browser/resources/net_internals/prerenderview.js @@ -17,6 +17,10 @@ function PrerenderView(mainBoxId, prerenderEnabledSpanId, prerenderHistoryDivId, inherits(PrerenderView, DivView); +PrerenderView.prototype.onLoadLogFinish = function(data) { + return this.onPrerenderInfoChanged(data.prerenderInfo); +}; + function IsValidPrerenderInfo(prerenderInfo) { if (prerenderInfo == null) { return false; @@ -33,9 +37,9 @@ PrerenderView.prototype.onPrerenderInfoChanged = function(prerenderInfo) { this.prerenderEnabledSpan_.innerText = ''; this.prerenderHistoryDiv_.innerHTML = ''; this.prerenderActiveDiv_.innerHTML = ''; - if (!IsValidPrerenderInfo(prerenderInfo)) { - return; - } + + if (!IsValidPrerenderInfo(prerenderInfo)) + return false; this.prerenderEnabledSpan_.innerText = prerenderInfo.enabled.toString(); @@ -46,6 +50,8 @@ PrerenderView.prototype.onPrerenderInfoChanged = function(prerenderInfo) { var tabPrinter = PrerenderView.createActiveTablePrinter( prerenderInfo.active); tabPrinter.toHTML(this.prerenderActiveDiv_, 'styledTable'); + + return true; }; PrerenderView.createHistoryTablePrinter = function(prerenderHistory) { diff --git a/chrome/browser/resources/net_internals/proxyview.js b/chrome/browser/resources/net_internals/proxyview.js index 48035e8..239229a 100644 --- a/chrome/browser/resources/net_internals/proxyview.js +++ b/chrome/browser/resources/net_internals/proxyview.js @@ -47,21 +47,57 @@ function ProxyView(mainBoxId, inherits(ProxyView, DivView); +ProxyView.prototype.onLoadLogStart = function(data) { + // Need to reset this so the latest proxy source from the dump can be + // identified when the log entries are loaded. + this.latestProxySourceId_ = 0; +}; + +ProxyView.prototype.onLoadLogFinish = function(data, tabData) { + // It's possible that the last INIT_PROXY_RESOLVER source was deleted from the + // log, but earlier sources remain. When that happens, clear the list of + // entries here, to avoid displaying misleading information. + if (tabData != this.latestProxySourceId_) + this.clearLog_(); + return this.onProxySettingsChanged(data.proxySettings) && + this.onBadProxiesChanged(data.badProxies); +}; + +/** + * Save view-specific state. + * + * Save the greatest seen proxy source id, so we will not incorrectly identify + * the log source associated with the current proxy configuration. + */ +ProxyView.prototype.saveState = function() { + return this.latestProxySourceId_; +}; + ProxyView.prototype.onProxySettingsChanged = function(proxySettings) { + // Both |original| and |effective| are dictionaries describing the settings. + this.originalSettingsDiv_.innerHTML = ''; + this.effectiveSettingsDiv_.innerHTML = ''; + + if (!proxySettings) + return false; + var original = proxySettings.original; var effective = proxySettings.effective; - // Both |original| and |effective| are dictionaries describing the settings. - this.originalSettingsDiv_.innerHTML = '' - this.effectiveSettingsDiv_.innerHTML = '' + if (!original || !effective) + return false; addTextNode(this.originalSettingsDiv_, proxySettingsToString(original)); addTextNode(this.effectiveSettingsDiv_, proxySettingsToString(effective)); + return true; }; ProxyView.prototype.onBadProxiesChanged = function(badProxies) { this.badProxiesTbody_.innerHTML = ''; + if (!badProxies) + return false; + // Add a table row for each bad proxy entry. for (var i = 0; i < badProxies.length; ++i) { var entry = badProxies[i]; @@ -75,6 +111,7 @@ ProxyView.prototype.onBadProxiesChanged = function(badProxies) { addTextNode(nameCell, entry.proxy_uri); addTextNode(badUntilCell, badUntilDate.toLocaleString()); } + return true; }; ProxyView.prototype.onLogEntryAdded = function(logEntry) { @@ -114,7 +151,3 @@ ProxyView.prototype.onLogEntriesDeleted = function(sourceIds) { ProxyView.prototype.onAllLogEntriesDeleted = function() { this.clearLog_(); }; - -ProxyView.prototype.onSetIsViewingLogFile = function(isViewingLogFile) { -}; - diff --git a/chrome/browser/resources/net_internals/serviceprovidersview.js b/chrome/browser/resources/net_internals/serviceprovidersview.js index ba5d8e4..48751a7 100644 --- a/chrome/browser/resources/net_internals/serviceprovidersview.js +++ b/chrome/browser/resources/net_internals/serviceprovidersview.js @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. @@ -31,10 +31,15 @@ function ServiceProvidersView(tabId, inherits(ServiceProvidersView, DivView); +ServiceProvidersView.prototype.onLoadLogFinish = function(data) { + return this.onServiceProvidersChanged(data.serviceProviders); +}; + ServiceProvidersView.prototype.onServiceProvidersChanged = function(serviceProviders) { - this.updateServiceProviders_(serviceProviders['service_providers']); - this.updateNamespaceProviders_(serviceProviders['namespace_providers']); + return serviceProviders && + this.updateServiceProviders_(serviceProviders['service_providers']) && + this.updateNamespaceProviders_(serviceProviders['namespace_providers']); }; /** @@ -105,6 +110,9 @@ ServiceProvidersView.prototype.updateServiceProviders_ = function(serviceProviders) { this.serviceProvidersTbody_.innerHTML = ''; + if (!serviceProviders) + return false; + // Add a table row for each service provider. for (var i = 0; i < serviceProviders.length; ++i) { var tr = addNode(this.serviceProvidersTbody_, 'tr'); @@ -118,6 +126,7 @@ function(serviceProviders) { addNodeWithText(tr, 'td', ServiceProvidersView.getProtocolType(entry)); addNodeWithText(tr, 'td', entry.path); } + return true; }; /** @@ -127,6 +136,9 @@ ServiceProvidersView.prototype.updateNamespaceProviders_ = function(namespaceProviders) { this.namespaceProvidersTbody_.innerHTML = ''; + if (!namespaceProviders) + return false; + // Add a table row for each namespace provider. for (var i = 0; i < namespaceProviders.length; ++i) { var tr = addNode(this.namespaceProvidersTbody_, 'tr'); @@ -137,5 +149,5 @@ function(namespaceProviders) { ServiceProvidersView.getNamespaceProviderType(entry)); addNodeWithText(tr, 'td', entry.active); } + return true; }; - diff --git a/chrome/browser/resources/net_internals/socketsview.js b/chrome/browser/resources/net_internals/socketsview.js index 90e7a6b..b99ed2b 100644 --- a/chrome/browser/resources/net_internals/socketsview.js +++ b/chrome/browser/resources/net_internals/socketsview.js @@ -28,12 +28,16 @@ function SocketsView(mainBoxId, socketPoolDivId, socketPoolGroupsDivId, inherits(SocketsView, DivView); +SocketsView.prototype.onLoadLogFinish = function(data) { + return this.onSocketPoolInfoChanged(data.socketPoolInfo); +}; + SocketsView.prototype.onSocketPoolInfoChanged = function(socketPoolInfo) { this.socketPoolDiv_.innerHTML = ''; this.socketPoolGroupsDiv_.innerHTML = ''; if (!socketPoolInfo) - return; + return false; var socketPools = SocketPoolWrapper.createArrayFrom(socketPoolInfo); var tablePrinter = SocketPoolWrapper.createTablePrinter(socketPools); @@ -48,6 +52,7 @@ SocketsView.prototype.onSocketPoolInfoChanged = function(socketPoolInfo) { groupTablePrinter.toHTML(p, 'styledTable'); } } + return true; }; SocketsView.prototype.closeIdleSockets = function() { diff --git a/chrome/browser/resources/net_internals/spdyview.js b/chrome/browser/resources/net_internals/spdyview.js index d7abc5454..2fffaeb 100644 --- a/chrome/browser/resources/net_internals/spdyview.js +++ b/chrome/browser/resources/net_internals/spdyview.js @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. @@ -36,9 +36,16 @@ function SpdyView(mainBoxId, spdyEnabledSpanId, inherits(SpdyView, DivView); +SpdyView.prototype.onLoadLogFinish = function(data) { + return this.onSpdySessionInfoChanged(data.spdySessionInfo) && + this.onSpdyStatusChanged(data.spdyStatus) && + this.onSpdyAlternateProtocolMappingsChanged( + data.spdyAlternateProtocolMappings); +}; + /** - * If |spdySessionInfo| is not null, displays a single table with information - * on each SPDY session. Otherwise, displays "None". + * If |spdySessionInfo| there are any sessions, display a single table with + * information on each SPDY session. Otherwise, displays "None". */ SpdyView.prototype.onSpdySessionInfoChanged = function(spdySessionInfo) { this.spdySessionDiv_.innerHTML = ''; @@ -47,12 +54,17 @@ SpdyView.prototype.onSpdySessionInfoChanged = function(spdySessionInfo) { setNodeDisplay(this.spdySessionNoneSpan_, hasNoSession); setNodeDisplay(this.spdySessionLinkSpan_, !hasNoSession); - if (hasNoSession) - return; + // Only want to be hide the tab if there's no data. In the case of having + // data but no sessions, still show the tab. + if (!spdySessionInfo) + return false; - var tablePrinter = SpdyView.createSessionTablePrinter(spdySessionInfo); - tablePrinter.toHTML(this.spdySessionDiv_, 'styledTable'); + if (!hasNoSession) { + var tablePrinter = SpdyView.createSessionTablePrinter(spdySessionInfo); + tablePrinter.toHTML(this.spdySessionDiv_, 'styledTable'); + } + return true; }; /** @@ -65,7 +77,9 @@ SpdyView.prototype.onSpdyStatusChanged = function(spdyStatus) { this.spdyForceAlwaysSpan_.innerText = spdyStatus.force_spdy_always; this.spdyForceOverSslSpan_.innerText = spdyStatus.force_spdy_over_ssl; this.spdyNextProtocolsSpan_.innerText = spdyStatus.next_protos; -} + + return true; +}; /** * If |spdyAlternateProtocolMappings| is not empty, displays a single table @@ -85,6 +99,7 @@ SpdyView.prototype.onSpdyAlternateProtocolMappingsChanged = } else { this.spdyAlternateProtocolMappingsDiv_.innerHTML = 'None'; } + return true; }; /** diff --git a/chrome/browser/resources/net_internals/tabswitcherview.js b/chrome/browser/resources/net_internals/tabswitcherview.js index 18cfa7a..97eca54 100644 --- a/chrome/browser/resources/net_internals/tabswitcherview.js +++ b/chrome/browser/resources/net_internals/tabswitcherview.js @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. @@ -67,7 +67,8 @@ TabSwitcherView.prototype.show = function(isVisible) { * "tab". * @param {!View} view The tab's actual contents. */ -TabSwitcherView.prototype.addTab = function(id, contentView, switchOnClick) { +TabSwitcherView.prototype.addTab = function(id, contentView, switchOnClick, + visible) { var tab = new TabEntry(id, contentView); this.tabs_.push(tab); @@ -81,6 +82,8 @@ TabSwitcherView.prototype.addTab = function(id, contentView, switchOnClick) { // Start tabs off as hidden. tab.contentView.show(false); + + this.showTabHandleNode(id, visible); }; /** diff --git a/chrome/browser/resources/net_internals/util.js b/chrome/browser/resources/net_internals/util.js index f326a77..f847476 100644 --- a/chrome/browser/resources/net_internals/util.js +++ b/chrome/browser/resources/net_internals/util.js @@ -99,7 +99,7 @@ function changeClassName(node, classNameToAddOrRemove, isAdd) { } function getKeyWithValue(map, value) { - for (key in map) { + for (var key in map) { if (map[key] == value) return key; } @@ -129,6 +129,20 @@ function makeRepeatedString(str, count) { } /** + * Clones a basic POD object. Only a new top level object will be cloned. It + * will continue to reference the same values as the original object. + */ +function shallowCloneObject(object) { + if (!(object instanceof Object)) + return object; + var copy = {}; + for (var key in object) { + copy[key] = object[key]; + } + return copy; +} + +/** * TablePrinter is a helper to format a table as ascii art or an HTML table. * * Usage: call addRow() and addCell() repeatedly to specify the data. diff --git a/chrome/browser/resources/net_internals/view.js b/chrome/browser/resources/net_internals/view.js index 68f485e..b1d229a 100644 --- a/chrome/browser/resources/net_internals/view.js +++ b/chrome/browser/resources/net_internals/view.js @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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. @@ -69,6 +69,23 @@ View.prototype.getBottom = function() { View.prototype.setParameters = function(params) {}; +/** + * Called when loading a log file, after clearing all events, but before + * loading the new ones. |polledData| contains the data from all + * PollableData helpers, and |tabData| contains the data for the particular tab. + */ +View.prototype.onLoadLogStart = function(polledData, tabData) { +}; + +/** + * Called as the final step of loading a log file. Arguments are the same as + * onLoadLogStart. Returns true to indicate the tab should be shown, false + * otherwise. + */ +View.prototype.onLoadLogFinish = function(polledData, tabData) { + return false; +}; + //----------------------------------------------------------------------------- /** diff --git a/chrome/browser/ui/webui/net_internals_ui.cc b/chrome/browser/ui/webui/net_internals_ui.cc index e4d83ec..a4341b2 100644 --- a/chrome/browser/ui/webui/net_internals_ui.cc +++ b/chrome/browser/ui/webui/net_internals_ui.cc @@ -75,6 +75,11 @@ namespace { // sent to the page at once, which reduces context switching and CPU usage. const int kNetLogEventDelayMilliseconds = 100; +// about:net-internals will not even attempt to load a log dump when it +// encounters a new version. This should be incremented when significant +// changes are made that will invalidate the old loading code. +const int kLogFormatVersion = 1; + // Returns the HostCache for |context|'s primary HostResolver, or NULL if // there is none. net::HostCache* GetHostResolverCache(net::URLRequestContext* context) { @@ -161,9 +166,10 @@ class NetInternalsMessageHandler virtual WebUIMessageHandler* Attach(WebUI* web_ui); virtual void RegisterMessages(); - // Executes the javascript function |function_name| in the renderer, passing - // it the argument |arg|. Takes ownership of |arg|. - void CallJavascriptFunction(const std::wstring& function_name, Value* arg); + // Calls g_browser.receive in the renderer, passing in |command| and |arg|. + // Takes ownership of |arg|. If the renderer is displaying a log file, the + // message will be ignored. + void SendJavascriptCommand(const std::wstring& command, Value* arg); // NotificationObserver implementation. virtual void Observe(int type, @@ -265,8 +271,8 @@ class NetInternalsMessageHandler // This class is the "real" message handler. It is allocated and destroyed on // the UI thread. With the exception of OnAddEntry, OnWebUIDeleted, and -// CallJavascriptFunction, its methods are all expected to be called from the IO -// thread. OnAddEntry and CallJavascriptFunction can be called from any thread, +// SendJavascriptCommand, its methods are all expected to be called from the IO +// thread. OnAddEntry and SendJavascriptCommand can be called from any thread, // and OnWebUIDeleted can only be called from the UI thread. class NetInternalsMessageHandler::IOThreadImpl : public base::RefCountedThreadSafe< @@ -351,10 +357,11 @@ class NetInternalsMessageHandler::IOThreadImpl int result); virtual void OnCompletedConnectionTestSuite(); - // Helper that executes |function_name| in the attached renderer. - // The function takes ownership of |arg|. Note that this can be called from - // any thread. - void CallJavascriptFunction(const std::wstring& function_name, Value* arg); + // Helper that calls g_browser.receive in the renderer, passing in |command| + // and |arg|. Takes ownership of |arg|. If the renderer is displaying a log + // file, the message will be ignored. Note that this can be called from any + // thread. + void SendJavascriptCommand(const std::wstring& command, Value* arg); private: friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>; @@ -626,15 +633,20 @@ void NetInternalsMessageHandler::RegisterMessages() { NewCallback(this, &NetInternalsMessageHandler::OnGetPrerenderInfo)); } -void NetInternalsMessageHandler::CallJavascriptFunction( - const std::wstring& function_name, +void NetInternalsMessageHandler::SendJavascriptCommand( + const std::wstring& command, Value* arg) { + scoped_ptr<Value> command_value( + Value::CreateStringValue(WideToASCII(command))); scoped_ptr<Value> value(arg); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (value.get()) { - web_ui_->CallJavascriptFunction(WideToASCII(function_name), *value.get()); + web_ui_->CallJavascriptFunction("g_browser.receive", + *command_value.get(), + *value.get()); } else { - web_ui_->CallJavascriptFunction(WideToASCII(function_name)); + web_ui_->CallJavascriptFunction("g_browser.receive", + *command_value.get()); } } @@ -646,8 +658,8 @@ void NetInternalsMessageHandler::Observe(int type, std::string* pref_name = Details<std::string>(details).ptr(); if (*pref_name == prefs::kHttpThrottlingEnabled) { - CallJavascriptFunction( - L"g_browser.receivedHttpThrottlingEnabledPrefChanged", + SendJavascriptCommand( + L"receivedHttpThrottlingEnabledPrefChanged", Value::CreateBooleanValue(*http_throttling_enabled_)); } } @@ -656,8 +668,8 @@ void NetInternalsMessageHandler::OnRendererReady(const ListValue* list) { CHECK(renderer_ready_io_callback_.get()); renderer_ready_io_callback_->Run(list); - CallJavascriptFunction( - L"g_browser.receivedHttpThrottlingEnabledPrefChanged", + SendJavascriptCommand( + L"receivedHttpThrottlingEnabledPrefChanged", Value::CreateBooleanValue(*http_throttling_enabled_)); } @@ -697,7 +709,7 @@ void NetInternalsMessageHandler::OnGetPrerenderInfo(const ListValue* list) { } else { value = prerender_manager->GetAsValue(); } - CallJavascriptFunction(L"g_browser.receivedPrerenderInfo", value); + SendJavascriptCommand(L"receivedPrerenderInfo", value); } @@ -793,7 +805,7 @@ void NetInternalsMessageHandler::SystemLogsGetter::SendLogs( } result->SetString("cellId", request.cell_id); - handler_->CallJavascriptFunction(L"g_browser.getSystemLogCallback", result); + handler_->SendJavascriptCommand(L"getSystemLogCallback", result); } #endif //////////////////////////////////////////////////////////////////////////////// @@ -850,7 +862,7 @@ void NetInternalsMessageHandler::IOThreadImpl::SendPassiveLogEntries( false)); } - CallJavascriptFunction(L"g_browser.receivedPassiveLogEntries", dict_list); + SendJavascriptCommand(L"receivedPassiveLogEntries", dict_list); } void NetInternalsMessageHandler::IOThreadImpl::OnWebUIDeleted() { @@ -863,152 +875,8 @@ void NetInternalsMessageHandler::IOThreadImpl::OnRendererReady( DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DCHECK(!is_observing_log_) << "notifyReady called twice"; - // Tell the javascript about the relationship between event type enums and - // their symbolic name. - { - std::vector<net::NetLog::EventType> event_types = - net::NetLog::GetAllEventTypes(); - - DictionaryValue* dict = new DictionaryValue(); - - for (size_t i = 0; i < event_types.size(); ++i) { - const char* name = net::NetLog::EventTypeToString(event_types[i]); - dict->SetInteger(name, static_cast<int>(event_types[i])); - } - - CallJavascriptFunction(L"g_browser.receivedLogEventTypeConstants", dict); - } - - // Tell the javascript about the version of the client and its - // command line arguments. - { - DictionaryValue* dict = new DictionaryValue(); - - chrome::VersionInfo version_info; - - if (!version_info.is_valid()) { - DLOG(ERROR) << "Unable to create chrome::VersionInfo"; - } else { - // We have everything we need to send the right values. - dict->SetString("version", version_info.Version()); - dict->SetString("cl", version_info.LastChange()); - dict->SetString("version_mod", - chrome::VersionInfo::GetVersionStringModifier()); - dict->SetString("official", - l10n_util::GetStringUTF16( - version_info.IsOfficialBuild() ? - IDS_ABOUT_VERSION_OFFICIAL - : IDS_ABOUT_VERSION_UNOFFICIAL)); - - dict->SetString("command_line", - CommandLine::ForCurrentProcess()->command_line_string()); - } - - CallJavascriptFunction(L"g_browser.receivedClientInfo", - dict); - } - - // Tell the javascript about the relationship between load flag enums and - // their symbolic name. - { - DictionaryValue* dict = new DictionaryValue(); - -#define LOAD_FLAG(label, value) \ - dict->SetInteger(# label, static_cast<int>(value)); -#include "net/base/load_flags_list.h" -#undef LOAD_FLAG - - CallJavascriptFunction(L"g_browser.receivedLoadFlagConstants", dict); - } - - // Tell the javascript about the relationship between net error codes and - // their symbolic name. - { - DictionaryValue* dict = new DictionaryValue(); - -#define NET_ERROR(label, value) \ - dict->SetInteger(# label, static_cast<int>(value)); -#include "net/base/net_error_list.h" -#undef NET_ERROR - - CallJavascriptFunction(L"g_browser.receivedNetErrorConstants", dict); - } - - // Tell the javascript about the relationship between event phase enums and - // their symbolic name. - { - DictionaryValue* dict = new DictionaryValue(); - - dict->SetInteger("PHASE_BEGIN", net::NetLog::PHASE_BEGIN); - dict->SetInteger("PHASE_END", net::NetLog::PHASE_END); - dict->SetInteger("PHASE_NONE", net::NetLog::PHASE_NONE); - - CallJavascriptFunction(L"g_browser.receivedLogEventPhaseConstants", dict); - } - - // Tell the javascript about the relationship between source type enums and - // their symbolic names. - { - DictionaryValue* dict = new DictionaryValue(); - -#define SOURCE_TYPE(label, value) dict->SetInteger(# label, value); -#include "net/base/net_log_source_type_list.h" -#undef SOURCE_TYPE - - CallJavascriptFunction(L"g_browser.receivedLogSourceTypeConstants", dict); - } - - // Tell the javascript about the relationship between LogLevel enums and their - // symbolic names. - { - DictionaryValue* dict = new DictionaryValue(); - - dict->SetInteger("LOG_ALL", net::NetLog::LOG_ALL); - dict->SetInteger("LOG_ALL_BUT_BYTES", net::NetLog::LOG_ALL_BUT_BYTES); - dict->SetInteger("LOG_BASIC", net::NetLog::LOG_BASIC); - - CallJavascriptFunction(L"g_browser.receivedLogLevelConstants", dict); - } - - // Tell the javascript about the relationship between address family enums and - // their symbolic names. - { - DictionaryValue* dict = new DictionaryValue(); - - dict->SetInteger("ADDRESS_FAMILY_UNSPECIFIED", - net::ADDRESS_FAMILY_UNSPECIFIED); - dict->SetInteger("ADDRESS_FAMILY_IPV4", - net::ADDRESS_FAMILY_IPV4); - dict->SetInteger("ADDRESS_FAMILY_IPV6", - net::ADDRESS_FAMILY_IPV6); - - CallJavascriptFunction(L"g_browser.receivedAddressFamilyConstants", dict); - } - - // Tell the javascript how the "time ticks" values we have given it relate to - // actual system times. (We used time ticks throughout since they are stable - // across system clock changes). - { - int64 cur_time_ms = (base::Time::Now() - base::Time()).InMilliseconds(); - - int64 cur_time_ticks_ms = - (base::TimeTicks::Now() - base::TimeTicks()).InMilliseconds(); - - // If we add this number to a time tick value, it gives the timestamp. - int64 tick_to_time_ms = cur_time_ms - cur_time_ticks_ms; - - // Chrome on all platforms stores times using the Windows epoch - // (Jan 1 1601), but the javascript wants a unix epoch. - // TODO(eroman): Getting the timestamp relative the to unix epoch should - // be part of the time library. - const int64 kUnixEpochMs = 11644473600000LL; - int64 tick_to_unix_time_ms = tick_to_time_ms - kUnixEpochMs; - - // Pass it as a string, since it may be too large to fit in an integer. - CallJavascriptFunction(L"g_browser.receivedTimeTickOffset", - Value::CreateStringValue( - base::Int64ToString(tick_to_unix_time_ms))); - } + SendJavascriptCommand(L"receivedConstants", + NetInternalsUI::GetConstants()); // Register with network stack to observe events. is_observing_log_ = true; @@ -1029,7 +897,7 @@ void NetInternalsMessageHandler::IOThreadImpl::OnGetProxySettings( if (proxy_service->config().is_valid()) dict->Set("effective", proxy_service->config().ToValue()); - CallJavascriptFunction(L"g_browser.receivedProxySettings", dict); + SendJavascriptCommand(L"receivedProxySettings", dict); } void NetInternalsMessageHandler::IOThreadImpl::OnReloadProxySettings( @@ -1063,7 +931,7 @@ void NetInternalsMessageHandler::IOThreadImpl::OnGetBadProxies( dict_list->Append(dict); } - CallJavascriptFunction(L"g_browser.receivedBadProxies", dict_list); + SendJavascriptCommand(L"receivedBadProxies", dict_list); } void NetInternalsMessageHandler::IOThreadImpl::OnClearBadProxies( @@ -1083,7 +951,7 @@ void NetInternalsMessageHandler::IOThreadImpl::OnGetHostResolverInfo( net::HostCache* cache = GetHostResolverCache(context); if (!host_resolver_impl || !cache) { - CallJavascriptFunction(L"g_browser.receivedHostResolverInfo", NULL); + SendJavascriptCommand(L"receivedHostResolverInfo", NULL); return; } @@ -1142,7 +1010,7 @@ void NetInternalsMessageHandler::IOThreadImpl::OnGetHostResolverInfo( cache_info_dict->Set("entries", entry_list); dict->Set("cache", cache_info_dict); - CallJavascriptFunction(L"g_browser.receivedHostResolverInfo", dict); + SendJavascriptCommand(L"receivedHostResolverInfo", dict); } void NetInternalsMessageHandler::IOThreadImpl::OnClearHostResolverCache( @@ -1230,7 +1098,7 @@ void NetInternalsMessageHandler::IOThreadImpl::OnHSTSQuery( } } - CallJavascriptFunction(L"g_browser.receivedHSTSResult", result); + SendJavascriptCommand(L"receivedHSTSResult", result); } void NetInternalsMessageHandler::IOThreadImpl::OnHSTSAdd( @@ -1318,7 +1186,7 @@ void NetInternalsMessageHandler::IOThreadImpl::OnGetHttpCacheInfo( info_dict->Set("stats", stats_dict); - CallJavascriptFunction(L"g_browser.receivedHttpCacheInfo", info_dict); + SendJavascriptCommand(L"receivedHttpCacheInfo", info_dict); } void NetInternalsMessageHandler::IOThreadImpl::OnGetSocketPoolInfo( @@ -1330,7 +1198,7 @@ void NetInternalsMessageHandler::IOThreadImpl::OnGetSocketPoolInfo( if (http_network_session) socket_pool_info = http_network_session->SocketPoolInfoToValue(); - CallJavascriptFunction(L"g_browser.receivedSocketPoolInfo", socket_pool_info); + SendJavascriptCommand(L"receivedSocketPoolInfo", socket_pool_info); } @@ -1362,7 +1230,7 @@ void NetInternalsMessageHandler::IOThreadImpl::OnGetSpdySessionInfo( spdy_info = http_network_session->SpdySessionPoolInfoToValue(); } - CallJavascriptFunction(L"g_browser.receivedSpdySessionInfo", spdy_info); + SendJavascriptCommand(L"receivedSpdySessionInfo", spdy_info); } void NetInternalsMessageHandler::IOThreadImpl::OnGetSpdyStatus( @@ -1385,7 +1253,7 @@ void NetInternalsMessageHandler::IOThreadImpl::OnGetSpdyStatus( Value::CreateStringValue( *net::HttpStreamFactory::next_protos())); - CallJavascriptFunction(L"g_browser.receivedSpdyStatus", status_dict); + SendJavascriptCommand(L"receivedSpdyStatus", status_dict); } void @@ -1412,8 +1280,7 @@ NetInternalsMessageHandler::IOThreadImpl::OnGetSpdyAlternateProtocolMappings( } } - CallJavascriptFunction(L"g_browser.receivedSpdyAlternateProtocolMappings", - dict_list); + SendJavascriptCommand(L"receivedSpdyAlternateProtocolMappings", dict_list); } #ifdef OS_WIN @@ -1453,8 +1320,7 @@ void NetInternalsMessageHandler::IOThreadImpl::OnGetServiceProviders( } service_providers->Set("namespace_providers", namespace_list); - CallJavascriptFunction(L"g_browser.receivedServiceProviders", - service_providers); + SendJavascriptCommand(L"receivedServiceProviders", service_providers); } #endif @@ -1516,19 +1382,17 @@ void NetInternalsMessageHandler::IOThreadImpl::AddEntryToQueue(Value* entry) { void NetInternalsMessageHandler::IOThreadImpl::PostPendingEntries() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - CallJavascriptFunction( - L"g_browser.receivedLogEntries", - pending_entries_.release()); + SendJavascriptCommand(L"receivedLogEntries", pending_entries_.release()); } void NetInternalsMessageHandler::IOThreadImpl::OnStartConnectionTestSuite() { - CallJavascriptFunction(L"g_browser.receivedStartConnectionTestSuite", NULL); + SendJavascriptCommand(L"receivedStartConnectionTestSuite", NULL); } void NetInternalsMessageHandler::IOThreadImpl::OnStartConnectionTestExperiment( const ConnectionTester::Experiment& experiment) { - CallJavascriptFunction( - L"g_browser.receivedStartConnectionTestExperiment", + SendJavascriptCommand( + L"receivedStartConnectionTestExperiment", ExperimentToValue(experiment)); } @@ -1541,15 +1405,15 @@ NetInternalsMessageHandler::IOThreadImpl::OnCompletedConnectionTestExperiment( dict->Set("experiment", ExperimentToValue(experiment)); dict->SetInteger("result", result); - CallJavascriptFunction( - L"g_browser.receivedCompletedConnectionTestExperiment", + SendJavascriptCommand( + L"receivedCompletedConnectionTestExperiment", dict); } void NetInternalsMessageHandler::IOThreadImpl::OnCompletedConnectionTestSuite() { - CallJavascriptFunction( - L"g_browser.receivedCompletedConnectionTestSuite", + SendJavascriptCommand( + L"receivedCompletedConnectionTestSuite", NULL); } @@ -1561,14 +1425,14 @@ void NetInternalsMessageHandler::IOThreadImpl::DispatchToMessageHandler( } // Note that this can be called from ANY THREAD. -void NetInternalsMessageHandler::IOThreadImpl::CallJavascriptFunction( - const std::wstring& function_name, +void NetInternalsMessageHandler::IOThreadImpl::SendJavascriptCommand( + const std::wstring& command, Value* arg) { if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { if (handler_ && !was_webui_deleted_) { // We check |handler_| in case it was deleted on the UI thread earlier // while we were running on the IO thread. - handler_->CallJavascriptFunction(function_name, arg); + handler_->SendJavascriptCommand(command, arg); } else { delete arg; } @@ -1579,8 +1443,9 @@ void NetInternalsMessageHandler::IOThreadImpl::CallJavascriptFunction( BrowserThread::UI, FROM_HERE, NewRunnableMethod( this, - &IOThreadImpl::CallJavascriptFunction, - function_name, arg))) { + &IOThreadImpl::SendJavascriptCommand, + command, + arg))) { // Failed posting the task, avoid leaking. delete arg; } @@ -1595,6 +1460,158 @@ void NetInternalsMessageHandler::IOThreadImpl::CallJavascriptFunction( // //////////////////////////////////////////////////////////////////////////////// +// static +Value* NetInternalsUI::GetConstants() { + DictionaryValue* constants_dict = new DictionaryValue(); + + // Version of the file format. + constants_dict->SetInteger("logFormatVersion", kLogFormatVersion); + + // Add a dictionary with information on the relationship between event type + // enums and their symbolic names. + { + std::vector<net::NetLog::EventType> event_types = + net::NetLog::GetAllEventTypes(); + + DictionaryValue* dict = new DictionaryValue(); + + for (size_t i = 0; i < event_types.size(); ++i) { + const char* name = net::NetLog::EventTypeToString(event_types[i]); + dict->SetInteger(name, static_cast<int>(event_types[i])); + } + constants_dict->Set("logEventTypes", dict); + } + + // Add a dictionary with the version of the client and its command line + // arguments. + { + DictionaryValue* dict = new DictionaryValue(); + + chrome::VersionInfo version_info; + + if (!version_info.is_valid()) { + DLOG(ERROR) << "Unable to create chrome::VersionInfo"; + } else { + // We have everything we need to send the right values. + dict->SetString("name", version_info.Name()); + dict->SetString("version", version_info.Version()); + dict->SetString("cl", version_info.LastChange()); + dict->SetString("version_mod", + chrome::VersionInfo::GetVersionStringModifier()); + dict->SetString("official", + version_info.IsOfficialBuild() ? "official" : + "unofficial"); + dict->SetString("os_type", version_info.OSType()); + dict->SetString("command_line", + CommandLine::ForCurrentProcess()->command_line_string()); + } + + constants_dict->Set("clientInfo", dict); + } + + // Add a dictionary with information about the relationship between load flag + // enums and their symbolic names. + { + DictionaryValue* dict = new DictionaryValue(); + +#define LOAD_FLAG(label, value) \ + dict->SetInteger(# label, static_cast<int>(value)); +#include "net/base/load_flags_list.h" +#undef LOAD_FLAG + + constants_dict->Set("loadFlag", dict); + } + + // Add information on the relationship between net error codes and their + // symbolic names. + { + DictionaryValue* dict = new DictionaryValue(); + +#define NET_ERROR(label, value) \ + dict->SetInteger(# label, static_cast<int>(value)); +#include "net/base/net_error_list.h" +#undef NET_ERROR + + constants_dict->Set("netError", dict); + } + + // Information about the relationship between event phase enums and their + // symbolic names. + { + DictionaryValue* dict = new DictionaryValue(); + + dict->SetInteger("PHASE_BEGIN", net::NetLog::PHASE_BEGIN); + dict->SetInteger("PHASE_END", net::NetLog::PHASE_END); + dict->SetInteger("PHASE_NONE", net::NetLog::PHASE_NONE); + + constants_dict->Set("logEventPhase", dict); + } + + // Information about the relationship between source type enums and + // their symbolic names. + { + DictionaryValue* dict = new DictionaryValue(); + +#define SOURCE_TYPE(label, value) dict->SetInteger(# label, value); +#include "net/base/net_log_source_type_list.h" +#undef SOURCE_TYPE + + constants_dict->Set("logSourceType", dict); + } + + // Information about the relationship between LogLevel enums and their + // symbolic names. + { + DictionaryValue* dict = new DictionaryValue(); + + dict->SetInteger("LOG_ALL", net::NetLog::LOG_ALL); + dict->SetInteger("LOG_ALL_BUT_BYTES", net::NetLog::LOG_ALL_BUT_BYTES); + dict->SetInteger("LOG_BASIC", net::NetLog::LOG_BASIC); + + constants_dict->Set("logLevelType", dict); + } + + // Information about the relationship between address family enums and + // their symbolic names. + { + DictionaryValue* dict = new DictionaryValue(); + + dict->SetInteger("ADDRESS_FAMILY_UNSPECIFIED", + net::ADDRESS_FAMILY_UNSPECIFIED); + dict->SetInteger("ADDRESS_FAMILY_IPV4", + net::ADDRESS_FAMILY_IPV4); + dict->SetInteger("ADDRESS_FAMILY_IPV6", + net::ADDRESS_FAMILY_IPV6); + + constants_dict->Set("addressFamily", dict); + } + + // Information about how the "time ticks" values we have given it relate to + // actual system times. (We used time ticks throughout since they are stable + // across system clock changes). + { + int64 cur_time_ms = (base::Time::Now() - base::Time()).InMilliseconds(); + + int64 cur_time_ticks_ms = + (base::TimeTicks::Now() - base::TimeTicks()).InMilliseconds(); + + // If we add this number to a time tick value, it gives the timestamp. + int64 tick_to_time_ms = cur_time_ms - cur_time_ticks_ms; + + // Chrome on all platforms stores times using the Windows epoch + // (Jan 1 1601), but the javascript wants a unix epoch. + // TODO(eroman): Getting the timestamp relative to the unix epoch should + // be part of the time library. + const int64 kUnixEpochMs = 11644473600000LL; + int64 tick_to_unix_time_ms = tick_to_time_ms - kUnixEpochMs; + + // Pass it as a string, since it may be too large to fit in an integer. + constants_dict->SetString("timeTickOffset", + base::Int64ToString(tick_to_unix_time_ms)); + } + return constants_dict; +} + NetInternalsUI::NetInternalsUI(TabContents* contents) : ChromeWebUI(contents) { AddMessageHandler((new NetInternalsMessageHandler())->Attach(this)); diff --git a/chrome/browser/ui/webui/net_internals_ui.h b/chrome/browser/ui/webui/net_internals_ui.h index 3acf4ce..6a70c4e 100644 --- a/chrome/browser/ui/webui/net_internals_ui.h +++ b/chrome/browser/ui/webui/net_internals_ui.h @@ -8,10 +8,16 @@ #include "chrome/browser/ui/webui/chrome_web_ui.h" +class Value; + class NetInternalsUI : public ChromeWebUI { public: explicit NetInternalsUI(TabContents* contents); + // Returns a Value containing constants NetInternals needs to load a log file. + // Safe to call on any thread. Caller takes ownership of the returned Value. + static Value* GetConstants(); + private: DISALLOW_COPY_AND_ASSIGN(NetInternalsUI); }; |