Commit 44d33b48 authored by mmenke@google.com's avatar mmenke@google.com

Adds the ability to load JSON log files to about:net-internals.

Only works with logs created by the new "--log-net-log=file",
which writes NetLog events to the specified file, regardless
of other logging command line parameters.

Using "--log-net-log" without a file name will just write
NetLog events to VLOG(1), as before.

BUG=63687
TEST=None

Review URL: http://codereview.chromium.org/6025017

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@72567 0039d316-1c4b-4281-b951-d872f2087c98
parent a1be9ffd
...@@ -28,6 +28,9 @@ ...@@ -28,6 +28,9 @@
#include "chrome/browser/net/url_fixer_upper.h" #include "chrome/browser/net/url_fixer_upper.h"
#include "chrome/browser/platform_util.h" #include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/shell_dialogs.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/browser/tab_contents/tab_contents_view.h"
#include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_version_info.h" #include "chrome/common/chrome_version_info.h"
#include "chrome/common/jstemplate_builder.h" #include "chrome/common/jstemplate_builder.h"
...@@ -136,6 +139,7 @@ class NetInternalsHTMLSource : public ChromeURLDataManager::DataSource { ...@@ -136,6 +139,7 @@ class NetInternalsHTMLSource : public ChromeURLDataManager::DataSource {
// TODO(eroman): Can we start on the IO thread to begin with? // TODO(eroman): Can we start on the IO thread to begin with?
class NetInternalsMessageHandler class NetInternalsMessageHandler
: public DOMMessageHandler, : public DOMMessageHandler,
public SelectFileDialog::Listener,
public base::SupportsWeakPtr<NetInternalsMessageHandler> { public base::SupportsWeakPtr<NetInternalsMessageHandler> {
public: public:
NetInternalsMessageHandler(); NetInternalsMessageHandler();
...@@ -150,12 +154,43 @@ class NetInternalsMessageHandler ...@@ -150,12 +154,43 @@ class NetInternalsMessageHandler
void CallJavascriptFunction(const std::wstring& function_name, void CallJavascriptFunction(const std::wstring& function_name,
const Value* value); const Value* value);
// SelectFileDialog::Listener implementation
virtual void FileSelected(const FilePath& path, int index, void* params);
virtual void FileSelectionCanceled(void* params);
// The only callback handled on the UI thread. As it needs to access fields
// from |dom_ui_|, it can't be called on the IO thread.
void OnLoadLogFile(const ListValue* list);
private: private:
class IOThreadImpl; class IOThreadImpl;
// Task run on the FILE thread to read the contents of a log file. The result
// is then passed to IOThreadImpl's CallJavascriptFunction, which sends it
// back to the web page. IOThreadImpl is used instead of the
// NetInternalsMessageHandler directly because it checks if the message
// handler has been destroyed in the meantime.
class ReadLogFileTask : public Task {
public:
ReadLogFileTask(IOThreadImpl* proxy, const FilePath& path);
virtual void Run();
private:
// IOThreadImpl implements existence checks already. Simpler to reused them
// then to reimplement them.
scoped_refptr<IOThreadImpl> proxy_;
// Path of the file to open.
const FilePath path_;
};
// This is the "real" message handler, which lives on the IO thread. // This is the "real" message handler, which lives on the IO thread.
scoped_refptr<IOThreadImpl> proxy_; scoped_refptr<IOThreadImpl> proxy_;
// Used for loading log files.
scoped_refptr<SelectFileDialog> select_log_file_dialog_;
DISALLOW_COPY_AND_ASSIGN(NetInternalsMessageHandler); DISALLOW_COPY_AND_ASSIGN(NetInternalsMessageHandler);
}; };
...@@ -243,17 +278,17 @@ class NetInternalsMessageHandler::IOThreadImpl ...@@ -243,17 +278,17 @@ class NetInternalsMessageHandler::IOThreadImpl
int result); int result);
virtual void OnCompletedConnectionTestSuite(); 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);
private: private:
class CallbackHelper; class CallbackHelper;
// Helper that runs |method| with |arg|, and deletes |arg| on completion. // Helper that runs |method| with |arg|, and deletes |arg| on completion.
void DispatchToMessageHandler(ListValue* arg, MessageHandler method); void DispatchToMessageHandler(ListValue* arg, MessageHandler method);
// 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);
// Adds |entry| to the queue of pending log entries to be sent to the page via // Adds |entry| to the queue of pending log entries to be sent to the page via
// Javascript. Must be called on the IO Thread. Also creates a delayed task // Javascript. Must be called on the IO Thread. Also creates a delayed task
// that will call PostPendingEntries, if there isn't one already. // that will call PostPendingEntries, if there isn't one already.
...@@ -398,6 +433,8 @@ NetInternalsMessageHandler::~NetInternalsMessageHandler() { ...@@ -398,6 +433,8 @@ NetInternalsMessageHandler::~NetInternalsMessageHandler() {
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
NewRunnableMethod(proxy_.get(), &IOThreadImpl::Detach)); NewRunnableMethod(proxy_.get(), &IOThreadImpl::Detach));
} }
if (select_log_file_dialog_)
select_log_file_dialog_->ListenerDestroyed();
} }
DOMMessageHandler* NetInternalsMessageHandler::Attach(DOMUI* dom_ui) { DOMMessageHandler* NetInternalsMessageHandler::Attach(DOMUI* dom_ui) {
...@@ -408,8 +445,35 @@ DOMMessageHandler* NetInternalsMessageHandler::Attach(DOMUI* dom_ui) { ...@@ -408,8 +445,35 @@ DOMMessageHandler* NetInternalsMessageHandler::Attach(DOMUI* dom_ui) {
return result; return result;
} }
void NetInternalsMessageHandler::FileSelected(
const FilePath& path, int index, void* params) {
select_log_file_dialog_.release();
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
new ReadLogFileTask(proxy_.get(), path));
}
void NetInternalsMessageHandler::FileSelectionCanceled(void* params) {
select_log_file_dialog_.release();
}
void NetInternalsMessageHandler::OnLoadLogFile(const ListValue* list) {
// Only allow a single dialog at a time.
if (select_log_file_dialog_.get())
return;
select_log_file_dialog_ = SelectFileDialog::Create(this);
select_log_file_dialog_->SelectFile(
SelectFileDialog::SELECT_OPEN_FILE, string16(), FilePath(), NULL, 0,
FILE_PATH_LITERAL(""),
dom_ui_->tab_contents()->view()->GetTopLevelNativeWindow(), NULL);
}
void NetInternalsMessageHandler::RegisterMessages() { void NetInternalsMessageHandler::RegisterMessages() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Only callback handled on UI thread.
dom_ui_->RegisterMessageCallback(
"loadLogFile",
NewCallback(this, &NetInternalsMessageHandler::OnLoadLogFile));
dom_ui_->RegisterMessageCallback( dom_ui_->RegisterMessageCallback(
"notifyReady", "notifyReady",
...@@ -469,6 +533,25 @@ void NetInternalsMessageHandler::CallJavascriptFunction( ...@@ -469,6 +533,25 @@ void NetInternalsMessageHandler::CallJavascriptFunction(
} }
} }
////////////////////////////////////////////////////////////////////////////////
//
// NetInternalsMessageHandler::ReadLogFileTask
//
////////////////////////////////////////////////////////////////////////////////
NetInternalsMessageHandler::ReadLogFileTask::ReadLogFileTask(
IOThreadImpl* proxy, const FilePath& path)
: proxy_(proxy), path_(path) {
}
void NetInternalsMessageHandler::ReadLogFileTask::Run() {
std::string file_contents;
if (!file_util::ReadFileToString(path_, &file_contents))
return;
proxy_->CallJavascriptFunction(L"g_browser.loadedLogFile",
new StringValue(file_contents));
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// //
// NetInternalsMessageHandler::IOThreadImpl // NetInternalsMessageHandler::IOThreadImpl
......
...@@ -67,7 +67,8 @@ ChromeNetLog::ChromeNetLog() ...@@ -67,7 +67,8 @@ ChromeNetLog::ChromeNetLog()
const CommandLine& command_line = *CommandLine::ForCurrentProcess(); const CommandLine& command_line = *CommandLine::ForCurrentProcess();
if (command_line.HasSwitch(switches::kLogNetLog)) { if (command_line.HasSwitch(switches::kLogNetLog)) {
net_log_logger_.reset(new NetLogLogger()); net_log_logger_.reset(new NetLogLogger(
command_line.GetSwitchValuePath(switches::kLogNetLog)));
AddObserver(net_log_logger_.get()); AddObserver(net_log_logger_.get());
} }
} }
......
...@@ -4,11 +4,19 @@ ...@@ -4,11 +4,19 @@
#include "chrome/browser/net/net_log_logger.h" #include "chrome/browser/net/net_log_logger.h"
#include <stdio.h>
#include "base/file_util.h"
#include "base/json/json_writer.h" #include "base/json/json_writer.h"
#include "base/threading/thread_restrictions.h"
#include "base/values.h" #include "base/values.h"
NetLogLogger::NetLogLogger() NetLogLogger::NetLogLogger(const FilePath &log_path)
: ThreadSafeObserver(net::NetLog::LOG_ALL_BUT_BYTES) { : ThreadSafeObserver(net::NetLog::LOG_ALL_BUT_BYTES) {
if (!log_path.empty()) {
base::ThreadRestrictions::ScopedAllowIO allow_io;
file_.Set(file_util::OpenFile(log_path, "w"));
}
} }
NetLogLogger::~NetLogLogger() {} NetLogLogger::~NetLogLogger() {}
...@@ -21,8 +29,16 @@ void NetLogLogger::OnAddEntry(net::NetLog::EventType type, ...@@ -21,8 +29,16 @@ void NetLogLogger::OnAddEntry(net::NetLog::EventType type,
scoped_ptr<Value> value(net::NetLog::EntryToDictionaryValue(type, time, scoped_ptr<Value> value(net::NetLog::EntryToDictionaryValue(type, time,
source, phase, source, phase,
params, true)); params, true));
// 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
// loaded, though a little extra parsing has to be done when loading a log.
std::string json; std::string json;
base::JSONWriter::Write(value.get(), true, &json); base::JSONWriter::Write(value.get(), false, &json);
if (!file_.get()) {
VLOG(1) << json; VLOG(1) << json;
} else {
fprintf(file_.get(), "%s\n", json.c_str());
}
} }
...@@ -6,14 +6,23 @@ ...@@ -6,14 +6,23 @@
#define CHROME_BROWSER_NET_NET_LOG_LOGGER_H_ #define CHROME_BROWSER_NET_NET_LOG_LOGGER_H_
#pragma once #pragma once
#include "base/scoped_handle.h"
#include "chrome/browser/net/chrome_net_log.h" #include "chrome/browser/net/chrome_net_log.h"
class FilePath;
// NetLogLogger watches the NetLog event stream, and sends all entries to // NetLogLogger watches the NetLog event stream, and sends all entries to
// VLOG(1). This is to debug errors that prevent getting to the // VLOG(1) or a path specified on creation. This is to debug errors that
// about:net-internals page. // prevent getting to the about:net-internals page.
//
// Relies on ChromeNetLog only calling an Observer once at a time for
// thread-safety.
class NetLogLogger : public ChromeNetLog::ThreadSafeObserver { class NetLogLogger : public ChromeNetLog::ThreadSafeObserver {
public: public:
NetLogLogger(); // If |log_path| is empty or file creation fails, writes to VLOG(1).
// Otherwise, writes to |log_path|. Uses one line per entry, for
// easy parsing.
explicit NetLogLogger(const FilePath &log_path);
~NetLogLogger(); ~NetLogLogger();
// ThreadSafeObserver implementation: // ThreadSafeObserver implementation:
...@@ -24,6 +33,8 @@ class NetLogLogger : public ChromeNetLog::ThreadSafeObserver { ...@@ -24,6 +33,8 @@ class NetLogLogger : public ChromeNetLog::ThreadSafeObserver {
net::NetLog::EventParameters* params); net::NetLog::EventParameters* params);
private: private:
ScopedStdioHandle file_;
DISALLOW_COPY_AND_ASSIGN(NetLogLogger); DISALLOW_COPY_AND_ASSIGN(NetLogLogger);
}; };
......
...@@ -19,7 +19,12 @@ function DataView(mainBoxId, ...@@ -19,7 +19,12 @@ function DataView(mainBoxId,
byteLoggingCheckboxId, byteLoggingCheckboxId,
passivelyCapturedCountId, passivelyCapturedCountId,
activelyCapturedCountId, activelyCapturedCountId,
deleteAllId) { deleteAllId,
dumpDataDivId,
loadDataDivId,
loadLogFileId,
capturingTextSpanId,
loggingTextSpanId) {
DivView.call(this, mainBoxId); DivView.call(this, mainBoxId);
this.textPre_ = document.getElementById(outputTextBoxId); this.textPre_ = document.getElementById(outputTextBoxId);
...@@ -40,6 +45,14 @@ function DataView(mainBoxId, ...@@ -40,6 +45,14 @@ function DataView(mainBoxId,
document.getElementById(deleteAllId).onclick = document.getElementById(deleteAllId).onclick =
g_browser.deleteAllEvents.bind(g_browser); g_browser.deleteAllEvents.bind(g_browser);
this.dumpDataDiv_ = document.getElementById(dumpDataDivId);
this.loadDataDiv_ = document.getElementById(loadDataDivId);
this.capturingTextSpan_ = document.getElementById(capturingTextSpanId);
this.loggingTextSpan_ = document.getElementById(loggingTextSpanId);
document.getElementById(loadLogFileId).onclick =
g_browser.loadLogFile.bind(g_browser);
this.updateEventCounts_(); this.updateEventCounts_();
this.waitingForUpdate_ = false; this.waitingForUpdate_ = false;
...@@ -70,6 +83,18 @@ DataView.prototype.onAllLogEntriesDeleted = function() { ...@@ -70,6 +83,18 @@ DataView.prototype.onAllLogEntriesDeleted = function() {
this.updateEventCounts_(); this.updateEventCounts_();
}; };
/**
* Called when either a log file is loaded or when going back to actively
* logging events. In either case, called after clearing the old entries,
* but before getting any new ones.
*/
DataView.prototype.onSetIsViewingLogFile = function(isViewingLogFile) {
setNodeDisplay(this.dumpDataDiv_, !isViewingLogFile);
setNodeDisplay(this.capturingTextSpan_, !isViewingLogFile);
setNodeDisplay(this.loggingTextSpan_, isViewingLogFile);
this.setText_('');
};
/** /**
* Updates the counters showing how many events have been captured. * Updates the counters showing how many events have been captured.
*/ */
...@@ -108,6 +133,10 @@ DataView.prototype.onExportToText_ = function() { ...@@ -108,6 +133,10 @@ DataView.prototype.onExportToText_ = function() {
* Presents the captured data as formatted text. * Presents the captured data as formatted text.
*/ */
DataView.prototype.onUpdateAllCompleted = function(data) { DataView.prototype.onUpdateAllCompleted = function(data) {
// It's possible for a log file to be loaded while a dump is being generated.
// When that happens, don't display the log dump, to avoid any confusion.
if (g_browser.isViewingLogFile())
return;
this.waitingForUpdate_ = false; this.waitingForUpdate_ = false;
var text = []; var text = [];
......
...@@ -475,10 +475,20 @@ EventsView.prototype.onLogEntriesDeleted = function(sourceIds) { ...@@ -475,10 +475,20 @@ EventsView.prototype.onLogEntriesDeleted = function(sourceIds) {
/** /**
* Called whenever all log events are deleted. * Called whenever all log events are deleted.
*/ */
EventsView.prototype.onAllLogEntriesDeleted = function(offset) { EventsView.prototype.onAllLogEntriesDeleted = function() {
this.initializeSourceList_(); this.initializeSourceList_();
}; };
/**
* 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.
*/
EventsView.prototype.onSetIsViewingLogFile = function(isViewingLogFile) {
// Needed to sort new sourceless entries correctly.
this.maxReceivedSourceId_ = 0;
};
EventsView.prototype.incrementPrefilterCount = function(offset) { EventsView.prototype.incrementPrefilterCount = function(offset) {
this.numPrefilter_ += offset; this.numPrefilter_ += offset;
this.invalidateFilterCounter_(); this.invalidateFilterCounter_();
......
...@@ -202,6 +202,7 @@ found in the LICENSE file. ...@@ -202,6 +202,7 @@ found in the LICENSE file.
<table width=100%> <table width=100%>
<tr> <tr>
<td valign=top> <td valign=top>
<div id=dataViewDumpDataDiv>
<h2>Dump data</h2> <h2>Dump data</h2>
<div style="margin: 8px"> <div style="margin: 8px">
<p><input id=securityStrippingCheckbox type=checkbox checked=yes> <p><input id=securityStrippingCheckbox type=checkbox checked=yes>
...@@ -214,11 +215,28 @@ found in the LICENSE file. ...@@ -214,11 +215,28 @@ found in the LICENSE file.
</p> </p>
<button id=exportToText class=bigButton>Dump to text</button> <button id=exportToText class=bigButton>Dump to text</button>
</div> </div>
</div>
<div id=dataViewLoadDataDiv>
<h2>Load data</h2>
<div style="margin: 8px">
<p><input type=button value="Load log from file" id=dataViewLoadLogFile /></p>
<p>Only works with log files created with "--log-net-log=file_name".</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>
</p>
</div>
</div>
</td> </td>
<td align=right valign=top> <td align=right valign=top>
<div class="capturingBox"> <div class="capturingBox" id=dataViewCapturingBox>
<span id=dataViewCapturingTextSpan>
<b>Capturing all events...</b> <b>Capturing all events...</b>
</span>
<span id=dataViewLoggingTextSpan style="display: none;">
<b>Viewing loaded log file.</b>
</span>
<table style="margin: 8px"> <table style="margin: 8px">
<tr> <tr>
<td>Passively captured:</td> <td>Passively captured:</td>
......
...@@ -75,10 +75,12 @@ function onLoaded() { ...@@ -75,10 +75,12 @@ function onLoaded() {
// captured data. // captured data.
var dataView = new DataView('dataTabContent', 'exportedDataText', var dataView = new DataView('dataTabContent', 'exportedDataText',
'exportToText', 'securityStrippingCheckbox', 'exportToText', 'securityStrippingCheckbox',
'byteLoggingCheckbox', 'byteLoggingCheckbox', 'passivelyCapturedCount',
'passivelyCapturedCount', 'activelyCapturedCount', 'dataViewDeleteAll',
'activelyCapturedCount', 'dataViewDumpDataDiv', 'dataViewLoadDataDiv',
'dataViewDeleteAll'); 'dataViewLoadLogFile',
'dataViewCapturingTextSpan',
'dataViewLoggingTextSpan');
// Create a view which will display the results and controls for connection // Create a view which will display the results and controls for connection
// tests. // tests.
...@@ -108,6 +110,7 @@ function onLoaded() { ...@@ -108,6 +110,7 @@ function onLoaded() {
// Create a view which lets you tab between the different sub-views. // Create a view which lets you tab between the different sub-views.
var categoryTabSwitcher = new TabSwitcherView('categoryTabHandles'); var categoryTabSwitcher = new TabSwitcherView('categoryTabHandles');
g_browser.setTabSwitcher(categoryTabSwitcher);
// Populate the main tabs. // Populate the main tabs.
categoryTabSwitcher.addTab('eventsTab', eventsView, false); categoryTabSwitcher.addTab('eventsTab', eventsView, false);
...@@ -144,6 +147,9 @@ function onLoaded() { ...@@ -144,6 +147,9 @@ function onLoaded() {
// Select the initial view based on the current URL. // Select the initial view based on the current URL.
window.onhashchange(); 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. // Tell the browser that we are ready to start receiving log events.
g_browser.sendReady(); g_browser.sendReady();
} }
...@@ -191,6 +197,11 @@ function BrowserBridge() { ...@@ -191,6 +197,11 @@ function BrowserBridge() {
// Next unique id to be assigned to a log entry without a source. // Next unique id to be assigned to a log entry without a source.
// Needed to simplify deletion, identify associated GUI elements, etc. // Needed to simplify deletion, identify associated GUI elements, etc.
this.nextSourcelessEventId_ = -1; 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;
} }
/* /*
...@@ -302,23 +313,19 @@ BrowserBridge.prototype.setLogLevel = function(logLevel) { ...@@ -302,23 +313,19 @@ BrowserBridge.prototype.setLogLevel = function(logLevel) {
chrome.send('setLogLevel', ['' + logLevel]); chrome.send('setLogLevel', ['' + logLevel]);
} }
BrowserBridge.prototype.loadLogFile = function() {
chrome.send('loadLogFile');
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Messages received from the browser // Messages received from the browser
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
BrowserBridge.prototype.receivedLogEntries = function(logEntries) { BrowserBridge.prototype.receivedLogEntries = function(logEntries) {
for (var e = 0; e < logEntries.length; ++e) { // Does nothing if viewing a log file.
var logEntry = logEntries[e]; if (this.isViewingLogFile_)
return;
// Assign unique ID, if needed. this.addLogEntries(logEntries);
if (logEntry.source.id == 0) {
logEntry.source.id = this.nextSourcelessEventId_;
--this.nextSourcelessEventId_;
}
this.capturedEvents_.push(logEntry);
for (var i = 0; i < this.logObservers_.length; ++i)
this.logObservers_[i].onLogEntryAdded(logEntry);
}
}; };
BrowserBridge.prototype.receivedLogEventTypeConstants = function(constantsMap) { BrowserBridge.prototype.receivedLogEventTypeConstants = function(constantsMap) {
...@@ -439,8 +446,90 @@ BrowserBridge.prototype.receivedHttpCacheInfo = function(info) { ...@@ -439,8 +446,90 @@ BrowserBridge.prototype.receivedHttpCacheInfo = function(info) {
this.pollableDataHelpers_.httpCacheInfo.update(info); this.pollableDataHelpers_.httpCacheInfo.update(info);
}; };
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();
this.setIsViewingLogFile_(true);
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(entry));
}
}
this.numPassivelyCapturedEvents_ = validEntries.length;
this.addLogEntries(validEntries);
var numInvalidEntries = entries.length - validEntries.length;
if (numInvalidEntries > 0 || numInvalidLines > 0) {
window.alert(
numInvalidLines.toString() +
' 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.');
}
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
/**
* Sets the |categoryTabSwitcher_| of BrowserBridge. Since views depend on
* g_browser being initialized, have to have a BrowserBridge prior to tab
* construction.
*/
BrowserBridge.prototype.setTabSwitcher = function(categoryTabSwitcher) {
this.categoryTabSwitcher_ = categoryTabSwitcher;
};
/** /**
* Adds a listener of log entries. |observer| will be called back when new log * Adds a listener of log entries. |observer| will be called back when new log
* data arrives, through: * data arrives, through:
...@@ -591,6 +680,25 @@ BrowserBridge.prototype.getNumPassivelyCapturedEvents = function() { ...@@ -591,6 +680,25 @@ BrowserBridge.prototype.getNumPassivelyCapturedEvents = function() {
return this.numPassivelyCapturedEvents_; return this.numPassivelyCapturedEvents_;
}; };
/**
* Sends each entry to all log observers, and updates |capturedEvents_|.
* Also assigns unique ids to log entries without a source.
*/
BrowserBridge.prototype.addLogEntries = function(logEntries) {
for (var e = 0; e < logEntries.length; ++e) {
var logEntry = logEntries[e];
// Assign unique ID, if needed.
if (logEntry.source.id == 0) {
logEntry.source.id = this.nextSourcelessEventId_;
--this.nextSourcelessEventId_;
}
this.capturedEvents_.push(logEntry);
for (var i = 0; i < this.logObservers_.length; ++i)
this.logObservers_[i].onLogEntryAdded(logEntry);
}
};
/** /**
* Deletes captured events with source IDs in |sourceIds|. * Deletes captured events with source IDs in |sourceIds|.
*/ */
...@@ -625,6 +733,39 @@ BrowserBridge.prototype.deleteAllEvents = function() { ...@@ -625,6 +733,39 @@ BrowserBridge.prototype.deleteAllEvents = function() {
this.logObservers_[i].onAllLogEntriesDeleted(); this.logObservers_[i].onAllLogEntriesDeleted();
}; };
/**
* 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() {
return this.isViewingLogFile_;
};
/** /**
* If |force| is true, calls all startUpdate functions. Otherwise, just * If |force| is true, calls all startUpdate functions. Otherwise, just
* runs updates with active observers. * runs updates with active observers.
......
...@@ -114,3 +114,7 @@ ProxyView.prototype.onLogEntriesDeleted = function(sourceIds) { ...@@ -114,3 +114,7 @@ ProxyView.prototype.onLogEntriesDeleted = function(sourceIds) {
ProxyView.prototype.onAllLogEntriesDeleted = function() { ProxyView.prototype.onAllLogEntriesDeleted = function() {
this.clearLog_(); this.clearLog_();
}; };
ProxyView.prototype.onSetIsViewingLogFile = function(isViewingLogFile) {
};
...@@ -136,6 +136,13 @@ TabSwitcherView.prototype.getAllTabIds = function() { ...@@ -136,6 +136,13 @@ TabSwitcherView.prototype.getAllTabIds = function() {
return ids; return ids;
}; };
// Shows/hides the DOM node that is used to select the tab. Will not change
// the active tab.
TabSwitcherView.prototype.showTabHandleNode = function(id, isVisible) {
var tab = this.findTabById(id);
setNodeDisplay(tab.getTabHandleNode(), isVisible);
};
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
/** /**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment