diff options
Diffstat (limited to 'chrome/browser/resources/net_internals')
-rw-r--r-- | chrome/browser/resources/net_internals/browserbridge.js | 624 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/dataview.js | 18 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/dnsview.js | 2 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/eventsview.js | 161 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/index.html | 1 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/index.js | 4 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/logdumputil.js | 229 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/logviewpainter.js | 8 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/main.js | 1065 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/proxyview.js | 25 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/sourceentry.js | 365 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/sourcerow.js | 284 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/sourcetracker.js | 201 |
13 files changed, 1537 insertions, 1450 deletions
diff --git a/chrome/browser/resources/net_internals/browserbridge.js b/chrome/browser/resources/net_internals/browserbridge.js new file mode 100644 index 0000000..eb362ca --- /dev/null +++ b/chrome/browser/resources/net_internals/browserbridge.js @@ -0,0 +1,624 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * This class provides a "bridge" for communicating between the javascript and + * the browser. + * + * @constructor + */ +function BrowserBridge() { + // List of observers for various bits of browser state. + this.connectionTestsObservers_ = []; + this.hstsObservers_ = []; + this.httpThrottlingObservers_ = []; + this.constantsObservers_ = []; + + this.pollableDataHelpers_ = {}; + this.pollableDataHelpers_.proxySettings = + new PollableDataHelper('onProxySettingsChanged', + this.sendGetProxySettings.bind(this)); + this.pollableDataHelpers_.badProxies = + new PollableDataHelper('onBadProxiesChanged', + this.sendGetBadProxies.bind(this)); + this.pollableDataHelpers_.httpCacheInfo = + new PollableDataHelper('onHttpCacheInfoChanged', + this.sendGetHttpCacheInfo.bind(this)); + this.pollableDataHelpers_.hostResolverInfo = + new PollableDataHelper('onHostResolverInfoChanged', + this.sendGetHostResolverInfo.bind(this)); + this.pollableDataHelpers_.socketPoolInfo = + new PollableDataHelper('onSocketPoolInfoChanged', + this.sendGetSocketPoolInfo.bind(this)); + this.pollableDataHelpers_.spdySessionInfo = + new PollableDataHelper('onSpdySessionInfoChanged', + this.sendGetSpdySessionInfo.bind(this)); + this.pollableDataHelpers_.spdyStatus = + new PollableDataHelper('onSpdyStatusChanged', + this.sendGetSpdyStatus.bind(this)); + this.pollableDataHelpers_.spdyAlternateProtocolMappings = + new PollableDataHelper('onSpdyAlternateProtocolMappingsChanged', + this.sendGetSpdyAlternateProtocolMappings.bind( + this)); + if (cr.isWindows) { + this.pollableDataHelpers_.serviceProviders = + new PollableDataHelper('onServiceProvidersChanged', + this.sendGetServiceProviders.bind(this)); + } + this.pollableDataHelpers_.prerenderInfo = + new PollableDataHelper('onPrerenderInfoChanged', + this.sendGetPrerenderInfo.bind(this)); + + // NetLog entries are all sent to the |SourceTracker|, which both tracks them + // and manages its own observer list. + this.sourceTracker = new SourceTracker(); + + // Setting this to true will cause messages from the browser to be ignored, + // and no messages will be sent to the browser, either. Intended for use when + // viewing log files. + this.disabled_ = false; +} + +/** + * Delay in milliseconds between updates of certain browser information. + */ +BrowserBridge.POLL_INTERVAL_MS = 5000; + +//------------------------------------------------------------------------------ +// Messages sent to the browser +//------------------------------------------------------------------------------ + +/** + * Wraps |chrome.send|. Doesn't send anything when disabled. + */ +BrowserBridge.prototype.send = function(value1, value2) { + if (!this.disabled_) { + if (arguments.length == 1) { + chrome.send(value1); + } else if (arguments.length == 2) { + chrome.send(value1, value2); + } else { + throw 'Unsupported number of arguments.'; + } + } +}; + +BrowserBridge.prototype.sendReady = function() { + 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 + // the observers. + window.setInterval(this.checkForUpdatedInfo.bind(this, false), + BrowserBridge.POLL_INTERVAL_MS); +}; + +BrowserBridge.prototype.sendGetProxySettings = function() { + // The browser will call receivedProxySettings on completion. + this.send('getProxySettings'); +}; + +BrowserBridge.prototype.sendReloadProxySettings = function() { + this.send('reloadProxySettings'); +}; + +BrowserBridge.prototype.sendGetBadProxies = function() { + // The browser will call receivedBadProxies on completion. + this.send('getBadProxies'); +}; + +BrowserBridge.prototype.sendGetHostResolverInfo = function() { + // The browser will call receivedHostResolverInfo on completion. + this.send('getHostResolverInfo'); +}; + +BrowserBridge.prototype.sendClearBadProxies = function() { + this.send('clearBadProxies'); +}; + +BrowserBridge.prototype.sendClearHostResolverCache = function() { + this.send('clearHostResolverCache'); +}; + +BrowserBridge.prototype.sendStartConnectionTests = function(url) { + this.send('startConnectionTests', [url]); +}; + +BrowserBridge.prototype.sendHSTSQuery = function(domain) { + this.send('hstsQuery', [domain]); +}; + +BrowserBridge.prototype.sendHSTSAdd = function(domain, + include_subdomains, + pins) { + this.send('hstsAdd', [domain, include_subdomains, pins]); +}; + +BrowserBridge.prototype.sendHSTSDelete = function(domain) { + this.send('hstsDelete', [domain]); +}; + +BrowserBridge.prototype.sendGetHttpCacheInfo = function() { + this.send('getHttpCacheInfo'); +}; + +BrowserBridge.prototype.sendGetSocketPoolInfo = function() { + this.send('getSocketPoolInfo'); +}; + +BrowserBridge.prototype.sendCloseIdleSockets = function() { + this.send('closeIdleSockets'); +}; + +BrowserBridge.prototype.sendFlushSocketPools = function() { + this.send('flushSocketPools'); +}; + +BrowserBridge.prototype.sendGetSpdySessionInfo = function() { + this.send('getSpdySessionInfo'); +}; + +BrowserBridge.prototype.sendGetSpdyStatus = function() { + this.send('getSpdyStatus'); +}; + +BrowserBridge.prototype.sendGetSpdyAlternateProtocolMappings = function() { + this.send('getSpdyAlternateProtocolMappings'); +}; + +BrowserBridge.prototype.sendGetServiceProviders = function() { + this.send('getServiceProviders'); +}; + +BrowserBridge.prototype.sendGetPrerenderInfo = function() { + this.send('getPrerenderInfo'); +}; + +BrowserBridge.prototype.enableIPv6 = function() { + this.send('enableIPv6'); +}; + +BrowserBridge.prototype.setLogLevel = function(logLevel) { + this.send('setLogLevel', ['' + logLevel]); +}; + +BrowserBridge.prototype.enableHttpThrottling = function(enable) { + this.send('enableHttpThrottling', [enable]); +}; + +BrowserBridge.prototype.refreshSystemLogs = function() { + this.send('refreshSystemLogs'); +}; + +BrowserBridge.prototype.getSystemLog = function(log_key, cellId) { + this.send('getSystemLog', [log_key, cellId]); +}; + +//------------------------------------------------------------------------------ +// Messages received from the browser. +//------------------------------------------------------------------------------ + +BrowserBridge.prototype.receive = function(command, params) { + // Does nothing if disabled. + if (this.disabled_) + return; + this[command](params); +}; + +BrowserBridge.prototype.receivedConstants = function(constants) { + for (var i = 0; i < this.constantsObservers_.length; ++i) + this.constantsObservers_[i].onReceivedConstants(constants); +}; + +BrowserBridge.prototype.receivedLogEntries = function(logEntries) { + this.sourceTracker.onReceivedLogEntries(logEntries); +}; + +BrowserBridge.prototype.receivedProxySettings = function(proxySettings) { + this.pollableDataHelpers_.proxySettings.update(proxySettings); +}; + +BrowserBridge.prototype.receivedBadProxies = function(badProxies) { + this.pollableDataHelpers_.badProxies.update(badProxies); +}; + +BrowserBridge.prototype.receivedHostResolverInfo = +function(hostResolverInfo) { + this.pollableDataHelpers_.hostResolverInfo.update(hostResolverInfo); +}; + +BrowserBridge.prototype.receivedSocketPoolInfo = function(socketPoolInfo) { + this.pollableDataHelpers_.socketPoolInfo.update(socketPoolInfo); +}; + +BrowserBridge.prototype.receivedSpdySessionInfo = function(spdySessionInfo) { + this.pollableDataHelpers_.spdySessionInfo.update(spdySessionInfo); +}; + +BrowserBridge.prototype.receivedSpdyStatus = function(spdyStatus) { + this.pollableDataHelpers_.spdyStatus.update(spdyStatus); +}; + +BrowserBridge.prototype.receivedSpdyAlternateProtocolMappings = + function(spdyAlternateProtocolMappings) { + this.pollableDataHelpers_.spdyAlternateProtocolMappings.update( + spdyAlternateProtocolMappings); +}; + +BrowserBridge.prototype.receivedServiceProviders = function(serviceProviders) { + this.pollableDataHelpers_.serviceProviders.update(serviceProviders); +}; + +BrowserBridge.prototype.receivedPassiveLogEntries = function(entries) { + this.sourceTracker.onReceivedPassiveLogEntries(entries); +}; + +BrowserBridge.prototype.receivedStartConnectionTestSuite = function() { + for (var i = 0; i < this.connectionTestsObservers_.length; ++i) + this.connectionTestsObservers_[i].onStartedConnectionTestSuite(); +}; + +BrowserBridge.prototype.receivedStartConnectionTestExperiment = function( + experiment) { + for (var i = 0; i < this.connectionTestsObservers_.length; ++i) { + this.connectionTestsObservers_[i].onStartedConnectionTestExperiment( + experiment); + } +}; + +BrowserBridge.prototype.receivedCompletedConnectionTestExperiment = +function(info) { + for (var i = 0; i < this.connectionTestsObservers_.length; ++i) { + this.connectionTestsObservers_[i].onCompletedConnectionTestExperiment( + info.experiment, info.result); + } +}; + +BrowserBridge.prototype.receivedCompletedConnectionTestSuite = function() { + for (var i = 0; i < this.connectionTestsObservers_.length; ++i) + this.connectionTestsObservers_[i].onCompletedConnectionTestSuite(); +}; + +BrowserBridge.prototype.receivedHSTSResult = function(info) { + for (var i = 0; i < this.hstsObservers_.length; ++i) + this.hstsObservers_[i].onHSTSQueryResult(info); +}; + +BrowserBridge.prototype.receivedHttpCacheInfo = function(info) { + this.pollableDataHelpers_.httpCacheInfo.update(info); +}; + +BrowserBridge.prototype.receivedHttpThrottlingEnabledPrefChanged = function( + enabled) { + for (var i = 0; i < this.httpThrottlingObservers_.length; ++i) { + this.httpThrottlingObservers_[i].onHttpThrottlingEnabledPrefChanged( + enabled); + } +}; + +BrowserBridge.prototype.receivedPrerenderInfo = function(prerenderInfo) { + this.pollableDataHelpers_.prerenderInfo.update(prerenderInfo); +}; + +//------------------------------------------------------------------------------ + +/** + * Prevents receiving/sending events to/from the browser. + */ +BrowserBridge.prototype.disable = function() { + this.disabled_ = true; +}; + +/** + * Adds a listener of the proxy settings. |observer| will be called back when + * data is received, through: + * + * observer.onProxySettingsChanged(proxySettings) + * + * |proxySettings| is a dictionary with (up to) two properties: + * + * "original" -- The settings that chrome was configured to use + * (i.e. system settings.) + * "effective" -- The "effective" proxy settings that chrome is using. + * (decides between the manual/automatic modes of the + * fetched settings). + * + * Each of these two configurations is formatted as a string, and may be + * omitted if not yet initialized. + * + * TODO(eroman): send a dictionary instead. + */ +BrowserBridge.prototype.addProxySettingsObserver = function(observer) { + this.pollableDataHelpers_.proxySettings.addObserver(observer); +}; + +/** + * Adds a listener of the proxy settings. |observer| will be called back when + * data is received, through: + * + * observer.onBadProxiesChanged(badProxies) + * + * |badProxies| is an array, where each entry has the property: + * badProxies[i].proxy_uri: String identify the proxy. + * badProxies[i].bad_until: The time when the proxy stops being considered + * bad. Note the time is in time ticks. + */ +BrowserBridge.prototype.addBadProxiesObserver = function(observer) { + this.pollableDataHelpers_.badProxies.addObserver(observer); +}; + +/** + * Adds a listener of the host resolver info. |observer| will be called back + * when data is received, through: + * + * observer.onHostResolverInfoChanged(hostResolverInfo) + */ +BrowserBridge.prototype.addHostResolverInfoObserver = function(observer) { + this.pollableDataHelpers_.hostResolverInfo.addObserver(observer); +}; + +/** + * Adds a listener of the socket pool. |observer| will be called back + * when data is received, through: + * + * observer.onSocketPoolInfoChanged(socketPoolInfo) + */ +BrowserBridge.prototype.addSocketPoolInfoObserver = function(observer) { + this.pollableDataHelpers_.socketPoolInfo.addObserver(observer); +}; + +/** + * Adds a listener of the SPDY info. |observer| will be called back + * when data is received, through: + * + * observer.onSpdySessionInfoChanged(spdySessionInfo) + */ +BrowserBridge.prototype.addSpdySessionInfoObserver = function(observer) { + this.pollableDataHelpers_.spdySessionInfo.addObserver(observer); +}; + +/** + * Adds a listener of the SPDY status. |observer| will be called back + * when data is received, through: + * + * observer.onSpdyStatusChanged(spdyStatus) + */ +BrowserBridge.prototype.addSpdyStatusObserver = function(observer) { + this.pollableDataHelpers_.spdyStatus.addObserver(observer); +}; + +/** + * Adds a listener of the AlternateProtocolMappings. |observer| will be called + * back when data is received, through: + * + * observer.onSpdyAlternateProtocolMappingsChanged( + * spdyAlternateProtocolMappings) + */ +BrowserBridge.prototype.addSpdyAlternateProtocolMappingsObserver = + function(observer) { + this.pollableDataHelpers_.spdyAlternateProtocolMappings.addObserver(observer); +}; + +/** + * Adds a listener of the service providers info. |observer| will be called + * 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) { + if (this.pollableDataHelpers_.serviceProviders) + this.pollableDataHelpers_.serviceProviders.addObserver(observer); +}; + +/** + * Adds a listener for the progress of the connection tests. + * The observer will be called back with: + * + * observer.onStartedConnectionTestSuite(); + * observer.onStartedConnectionTestExperiment(experiment); + * observer.onCompletedConnectionTestExperiment(experiment, result); + * observer.onCompletedConnectionTestSuite(); + */ +BrowserBridge.prototype.addConnectionTestsObserver = function(observer) { + this.connectionTestsObservers_.push(observer); +}; + +/** + * Adds a listener for the http cache info results. + * The observer will be called back with: + * + * observer.onHttpCacheInfoChanged(info); + */ +BrowserBridge.prototype.addHttpCacheInfoObserver = function(observer) { + this.pollableDataHelpers_.httpCacheInfo.addObserver(observer); +}; + +/** + * Adds a listener for the results of HSTS (HTTPS Strict Transport Security) + * queries. The observer will be called back with: + * + * observer.onHSTSQueryResult(result); + */ +BrowserBridge.prototype.addHSTSObserver = function(observer) { + this.hstsObservers_.push(observer); +}; + +/** + * Adds a listener for HTTP throttling-related events. |observer| will be called + * back when HTTP throttling is enabled/disabled, through: + * + * observer.onHttpThrottlingEnabledPrefChanged(enabled); + */ +BrowserBridge.prototype.addHttpThrottlingObserver = function(observer) { + this.httpThrottlingObservers_.push(observer); +}; + +/** + * Adds a listener for the received constants event. |observer| will be called + * back when the constants are received, through: + * + * observer.onReceivedConstants(constants); + */ +BrowserBridge.prototype.addConstantsObserver = function(observer) { + this.constantsObservers_.push(observer); +}; + +/** + * Adds a listener for updated prerender info events + * |observer| will be called back with: + * + * observer.onPrerenderInfoChanged(prerenderInfo); + */ +BrowserBridge.prototype.addPrerenderInfoObserver = function(observer) { + this.pollableDataHelpers_.prerenderInfo.addObserver(observer); +}; + +/** + * If |force| is true, calls all startUpdate functions. Otherwise, just + * runs updates with active observers. + */ +BrowserBridge.prototype.checkForUpdatedInfo = function(force) { + for (name in this.pollableDataHelpers_) { + var helper = this.pollableDataHelpers_[name]; + if (force || helper.hasActiveObserver()) + helper.startUpdate(); + } +}; + +/** + * Calls all startUpdate functions and, if |callback| is non-null, + * calls it with the results of all updates. + */ +BrowserBridge.prototype.updateAllInfo = function(callback) { + if (callback) + new UpdateAllObserver(callback, this.pollableDataHelpers_); + this.checkForUpdatedInfo(true); +}; + +/** + * This is a helper class used by BrowserBridge, to keep track of: + * - the list of observers interested in some piece of data. + * - the last known value of that piece of data. + * - the name of the callback method to invoke on observers. + * - the update function. + * @constructor + */ +function PollableDataHelper(observerMethodName, startUpdateFunction) { + this.observerMethodName_ = observerMethodName; + this.startUpdate = startUpdateFunction; + this.observerInfos_ = []; +} + +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 + * latter is used to make sure that new observers get sent data on the + * update following their creation. + * @constructor + */ +function ObserverInfo(observer) { + this.observer = observer; + this.hasReceivedData = false; +} + +PollableDataHelper.prototype.addObserver = function(observer) { + this.observerInfos_.push(new ObserverInfo(observer)); +}; + +PollableDataHelper.prototype.removeObserver = function(observer) { + for (var i = 0; i < this.observerInfos_.length; ++i) { + if (this.observerInfos_[i].observer === observer) { + this.observerInfos_.splice(i, 1); + return; + } + } +}; + +/** + * Helper function to handle calling all the observers, but ONLY if the data has + * actually changed since last time or the observer has yet to receive any data. + * This is used for data we received from browser on an update loop. + */ +PollableDataHelper.prototype.update = function(data) { + var prevData = this.currentData_; + var changed = false; + + // If the data hasn't changed since last time, will only need to notify + // observers that have not yet received any data. + if (!prevData || JSON.stringify(prevData) != JSON.stringify(data)) { + changed = true; + this.currentData_ = data; + } + + // Notify the observers of the change, as needed. + for (var i = 0; i < this.observerInfos_.length; ++i) { + var observerInfo = this.observerInfos_[i]; + if (changed || !observerInfo.hasReceivedData) { + observerInfo.observer[this.observerMethodName_](this.currentData_); + observerInfo.hasReceivedData = true; + } + } +}; + +/** + * Returns true if one of the observers actively wants the data + * (i.e. is visible). + */ +PollableDataHelper.prototype.hasActiveObserver = function() { + for (var i = 0; i < this.observerInfos_.length; ++i) { + if (this.observerInfos_[i].observer.isActive()) + return true; + } + return false; +}; + +/** + * This is a helper class used by BrowserBridge to send data to + * a callback once data from all polls has been received. + * + * It works by keeping track of how many polling functions have + * yet to receive data, and recording the data as it it received. + * + * @constructor + */ +function UpdateAllObserver(callback, pollableDataHelpers) { + this.callback_ = callback; + this.observingCount_ = 0; + this.updatedData_ = {}; + + for (name in pollableDataHelpers) { + ++this.observingCount_; + var helper = pollableDataHelpers[name]; + helper.addObserver(this); + this[helper.getObserverMethodName()] = + this.onDataReceived_.bind(this, helper, name); + } +} + +UpdateAllObserver.prototype.isActive = function() { + return true; +}; + +UpdateAllObserver.prototype.onDataReceived_ = function(helper, name, data) { + helper.removeObserver(this); + --this.observingCount_; + this.updatedData_[name] = data; + if (this.observingCount_ == 0) + this.callback_(this.updatedData_); +}; diff --git a/chrome/browser/resources/net_internals/dataview.js b/chrome/browser/resources/net_internals/dataview.js index cc607fe..aad6ae1 100644 --- a/chrome/browser/resources/net_internals/dataview.js +++ b/chrome/browser/resources/net_internals/dataview.js @@ -51,7 +51,8 @@ function DataView() { this.activelyCapturedCountBox_ = $(activelyCapturedCountId); this.passivelyCapturedCountBox_ = $(passivelyCapturedCountId); - $(deleteAllId).onclick = g_browser.deleteAllEvents.bind(g_browser); + $(deleteAllId).onclick = g_browser.sourceTracker.deleteAllSourceEntries.bind( + g_browser.sourceTracker); this.dumpDataDiv_ = $(dumpDataDivId); this.capturingTextSpan_ = $(capturingTextSpanId); @@ -75,7 +76,7 @@ function DataView() { // saved. this.lastBlobURL_ = null; - g_browser.addLogObserver(this); + g_browser.sourceTracker.addObserver(this); } inherits(DataView, DivView); @@ -83,7 +84,7 @@ inherits(DataView, DivView); /** * Called whenever a new event is received. */ -DataView.prototype.onLogEntryAdded = function(logEntry) { +DataView.prototype.onSourceEntryUpdated = function(sourceEntry) { this.updateEventCounts_(); }; @@ -91,14 +92,14 @@ DataView.prototype.onLogEntryAdded = function(logEntry) { * Called whenever some log events are deleted. |sourceIds| lists * the source IDs of all deleted log entries. */ -DataView.prototype.onLogEntriesDeleted = function(sourceIds) { +DataView.prototype.onSourceEntriesDeleted = function(sourceIds) { this.updateEventCounts_(); }; /** * Called whenever all log events are deleted. */ -DataView.prototype.onAllLogEntriesDeleted = function() { +DataView.prototype.onAllSourceEntriesDeleted = function() { this.updateEventCounts_(); }; @@ -122,9 +123,9 @@ DataView.prototype.onLoadLogFinish = function(data) { */ DataView.prototype.updateEventCounts_ = function() { this.activelyCapturedCountBox_.textContent = - g_browser.getNumActivelyCapturedEvents() + g_browser.sourceTracker.getNumActivelyCapturedEvents(); this.passivelyCapturedCountBox_.textContent = - g_browser.getNumPassivelyCapturedEvents(); + g_browser.sourceTracker.getNumPassivelyCapturedEvents(); }; /** @@ -145,7 +146,8 @@ DataView.prototype.onSetByteLogging_ = function(byteLoggingCheckbox) { */ DataView.prototype.onSetSecurityStripping_ = function(securityStrippingCheckbox) { - g_browser.setSecurityStripping(securityStrippingCheckbox.checked); + g_browser.sourceTracker.setSecurityStripping( + securityStrippingCheckbox.checked); }; DataView.prototype.onSecurityStrippingChanged = function() { diff --git a/chrome/browser/resources/net_internals/dnsview.js b/chrome/browser/resources/net_internals/dnsview.js index 0479215..ebfcde5 100644 --- a/chrome/browser/resources/net_internals/dnsview.js +++ b/chrome/browser/resources/net_internals/dnsview.js @@ -99,7 +99,7 @@ DnsView.prototype.onHostResolverInfoChanged = function(hostResolverInfo) { } } - var expiresDate = g_browser.convertTimeTicksToDate(e.expiration); + var expiresDate = convertTimeTicksToDate(e.expiration); var expiresCell = addNode(tr, 'td'); addTextNode(expiresCell, expiresDate.toLocaleString()); } diff --git a/chrome/browser/resources/net_internals/eventsview.js b/chrome/browser/resources/net_internals/eventsview.js index 14e84c5..674a442 100644 --- a/chrome/browser/resources/net_internals/eventsview.js +++ b/chrome/browser/resources/net_internals/eventsview.js @@ -47,9 +47,6 @@ function EventsView() { View.call(this); - // Used for sorting entries with automatically assigned IDs. - this.maxReceivedSourceId_ = 0; - // Initialize the sub-views. var leftPane = new TopMidBottomView(new DivView(topbarId), new DivView(middleboxId), @@ -64,7 +61,7 @@ function EventsView() { this.splitterView_ = new ResizableVerticalSplitView( leftPane, this.detailsView_, new DivView(sizerId)); - g_browser.addLogObserver(this); + g_browser.sourceTracker.addObserver(this); this.tableBody_ = $(tableBodyId); @@ -76,7 +73,8 @@ function EventsView() { $(deleteSelectedId).onclick = this.deleteSelected_.bind(this); - $(deleteAllId).onclick = g_browser.deleteAllEvents.bind(g_browser); + $(deleteAllId).onclick = g_browser.sourceTracker.deleteAllSourceEntries.bind( + g_browser.sourceTracker); $(selectAllId).addEventListener('click', this.selectAll_.bind(this), true); @@ -101,8 +99,8 @@ inherits(EventsView, View); * being displayed, removes them all in the process. */ EventsView.prototype.initializeSourceList_ = function() { - this.currentSelectedSources_ = []; - this.sourceIdToEntryMap_ = {}; + this.currentSelectedRows_ = []; + this.sourceIdToRowMap_ = {}; this.tableBody_.innerHTML = ''; this.numPrefilter_ = 0; this.numPostfilter_ = 0; @@ -150,11 +148,11 @@ EventsView.prototype.onSecurityStrippingChanged = function() { * duration or time of first event. */ EventsView.compareActive_ = function(source1, source2) { - if (source1.isActive() && !source2.isActive()) + if (!source1.isInactive() && source2.isInactive()) return -1; - if (!source1.isActive() && source2.isActive()) + if (source1.isInactive() && !source2.isInactive()) return 1; - if (!source1.isActive()) { + if (source1.isInactive()) { var deltaEndTime = source1.getEndTime() - source2.getEndTime(); if (deltaEndTime != 0) { // The one that ended most recently (Highest end time) should be sorted @@ -237,17 +235,19 @@ EventsView.comparisonFunctionTable_ = { EventsView.prototype.Sort_ = function() { var sourceEntries = []; - for (var id in this.sourceIdToEntryMap_) { - // Can only sort items with an actual row in the table. - if (this.sourceIdToEntryMap_[id].hasRow()) - sourceEntries.push(this.sourceIdToEntryMap_[id]); + for (var id in this.sourceIdToRowMap_) { + sourceEntries.push(this.sourceIdToRowMap_[id].getSourceEntry()); } sourceEntries.sort(this.comparisonFuncWithReversing_.bind(this)); + // Reposition source rows from back to front. for (var i = sourceEntries.length - 2; i >= 0; --i) { - if (sourceEntries[i].getNextNodeSourceId() != - sourceEntries[i + 1].getSourceId()) - sourceEntries[i].moveBefore(sourceEntries[i + 1]); + var sourceRow = this.sourceIdToRowMap_[sourceEntries[i].getSourceId()]; + var nextSourceId = sourceEntries[i + 1].getSourceId(); + if (sourceRow.getNextNodeSourceId() != nextSourceId) { + var nextSourceRow = this.sourceIdToRowMap_[nextSourceId]; + sourceRow.moveBefore(nextSourceRow); + } } }; @@ -404,94 +404,95 @@ EventsView.prototype.setFilter_ = function(filterText) { this.currentFilter_ = this.createFilter_(filterText); // Iterate through all of the rows and see if they match the filter. - for (var id in this.sourceIdToEntryMap_) { - var entry = this.sourceIdToEntryMap_[id]; + for (var id in this.sourceIdToRowMap_) { + var entry = this.sourceIdToRowMap_[id]; entry.setIsMatchedByFilter(entry.matchesFilter(this.currentFilter_)); } }; /** - * Repositions |sourceEntry|'s row in the table using an insertion sort. + * Repositions |sourceRow|'s in the table using an insertion sort. * Significantly faster than sorting the entire table again, when only * one entry has changed. */ -EventsView.prototype.InsertionSort_ = function(sourceEntry) { - // SourceEntry that should be after |sourceEntry|, if it needs +EventsView.prototype.InsertionSort_ = function(sourceRow) { + // SourceRow that should be after |sourceRow|, if it needs // to be moved earlier in the list. - var sourceEntryAfter = sourceEntry; + var sourceRowAfter = sourceRow; while (true) { - var prevSourceId = sourceEntryAfter.getPreviousNodeSourceId(); + var prevSourceId = sourceRowAfter.getPreviousNodeSourceId(); if (prevSourceId == null) break; - var prevSourceEntry = this.sourceIdToEntryMap_[prevSourceId]; - if (this.comparisonFuncWithReversing_(sourceEntry, prevSourceEntry) >= 0) + var prevSourceRow = this.sourceIdToRowMap_[prevSourceId]; + if (this.comparisonFuncWithReversing_( + sourceRow.getSourceEntry(), + prevSourceRow.getSourceEntry()) >= 0) { break; - sourceEntryAfter = prevSourceEntry; + } + sourceRowAfter = prevSourceRow; } - if (sourceEntryAfter != sourceEntry) { - sourceEntry.moveBefore(sourceEntryAfter); + if (sourceRowAfter != sourceRow) { + sourceRow.moveBefore(sourceRowAfter); return; } - var sourceEntryBefore = sourceEntry; + var sourceRowBefore = sourceRow; while (true) { - var nextSourceId = sourceEntryBefore.getNextNodeSourceId(); + var nextSourceId = sourceRowBefore.getNextNodeSourceId(); if (nextSourceId == null) break; - var nextSourceEntry = this.sourceIdToEntryMap_[nextSourceId]; - if (this.comparisonFuncWithReversing_(sourceEntry, nextSourceEntry) <= 0) + var nextSourceRow = this.sourceIdToRowMap_[nextSourceId]; + if (this.comparisonFuncWithReversing_( + sourceRow.getSourceEntry(), + nextSourceRow.getSourceEntry()) <= 0) { break; - sourceEntryBefore = nextSourceEntry; + } + sourceRowBefore = nextSourceRow; } - if (sourceEntryBefore != sourceEntry) - sourceEntry.moveAfter(sourceEntryBefore); + if (sourceRowBefore != sourceRow) + sourceRow.moveAfter(sourceRowBefore); }; -EventsView.prototype.onLogEntryAdded = function(logEntry) { - var id = logEntry.source.id; +EventsView.prototype.onSourceEntryUpdated = function(sourceEntry) { + // Lookup the row. + var sourceRow = this.sourceIdToRowMap_[sourceEntry.getSourceId()]; - // Lookup the source. - var sourceEntry = this.sourceIdToEntryMap_[id]; - - if (!sourceEntry) { - sourceEntry = new SourceEntry(this, this.maxReceivedSourceId_); - this.sourceIdToEntryMap_[id] = sourceEntry; - this.incrementPrefilterCount(1); - if (id > this.maxReceivedSourceId_) - this.maxReceivedSourceId_ = id; + if (!sourceRow) { + sourceRow = new SourceRow(this, sourceEntry); + this.sourceIdToRowMap_[sourceEntry.getSourceId()] = sourceRow; } - sourceEntry.update(logEntry); + sourceRow.onSourceUpdated(); - if (sourceEntry.isSelected()) + if (sourceRow.isSelected()) this.invalidateDetailsView_(); // TODO(mmenke): Fix sorting when sorting by duration. // Duration continuously increases for all entries that are // still active. This can result in incorrect sorting, until // Sort_ is called. - this.InsertionSort_(sourceEntry); + this.InsertionSort_(sourceRow); }; /** - * Returns the SourceEntry with the specified ID, if there is one. + * Returns the SourceRow with the specified ID, if there is one. * Otherwise, returns undefined. */ -EventsView.prototype.getSourceEntry = function(id) { - return this.sourceIdToEntryMap_[id]; +EventsView.prototype.getSourceRow = function(id) { + return this.sourceIdToRowMap_[id]; }; /** * Called whenever some log events are deleted. |sourceIds| lists * the source IDs of all deleted log entries. */ -EventsView.prototype.onLogEntriesDeleted = function(sourceIds) { +EventsView.prototype.onSourceEntriesDeleted = function(sourceIds) { for (var i = 0; i < sourceIds.length; ++i) { var id = sourceIds[i]; - var entry = this.sourceIdToEntryMap_[id]; - if (entry) { - entry.remove(); - delete this.sourceIdToEntryMap_[id]; + var sourceRow = this.sourceIdToRowMap_[id]; + if (sourceRow) { + sourceRow.remove(); + delete this.sourceIdToRowMap_[id]; this.incrementPrefilterCount(-1); } } @@ -500,7 +501,7 @@ EventsView.prototype.onLogEntriesDeleted = function(sourceIds) { /** * Called whenever all log events are deleted. */ -EventsView.prototype.onAllLogEntriesDeleted = function() { +EventsView.prototype.onAllSourceEntriesDeleted = function() { this.initializeSourceList_(); }; @@ -532,8 +533,8 @@ EventsView.prototype.onSelectionChanged = function() { }; EventsView.prototype.clearSelection = function() { - var prevSelection = this.currentSelectedSources_; - this.currentSelectedSources_ = []; + var prevSelection = this.currentSelectedRows_; + this.currentSelectedRows_ = []; // Unselect everything that is currently selected. for (var i = 0; i < prevSelection.length; ++i) { @@ -545,25 +546,25 @@ EventsView.prototype.clearSelection = function() { EventsView.prototype.deleteSelected_ = function() { var sourceIds = []; - for (var i = 0; i < this.currentSelectedSources_.length; ++i) { - var entry = this.currentSelectedSources_[i]; - sourceIds.push(entry.getSourceId()); + for (var i = 0; i < this.currentSelectedRows_.length; ++i) { + var sourceRow = this.currentSelectedRows_[i]; + sourceIds.push(sourceRow.getSourceEntry().getSourceId()); } - g_browser.deleteEventsBySourceId(sourceIds); + g_browser.sourceTracker.deleteSourceEntries(sourceIds); }; EventsView.prototype.selectAll_ = function(event) { - for (var id in this.sourceIdToEntryMap_) { - var entry = this.sourceIdToEntryMap_[id]; - if (entry.isMatchedByFilter()) { - entry.setSelected(true); + for (var id in this.sourceIdToRowMap_) { + var sourceRow = this.sourceIdToRowMap_[id]; + if (sourceRow.isMatchedByFilter()) { + sourceRow.setSelected(true); } } event.preventDefault(); }; EventsView.prototype.unselectAll_ = function() { - var entries = this.currentSelectedSources_.slice(0); + var entries = this.currentSelectedRows_.slice(0); for (var i = 0; i < entries.length; ++i) { entries[i].setSelected(false); } @@ -611,11 +612,11 @@ EventsView.prototype.sortByDescription_ = function(event) { }; EventsView.prototype.modifySelectionArray = function( - sourceEntry, addToSelection) { + sourceRow, addToSelection) { // Find the index for |sourceEntry| in the current selection list. var index = -1; - for (var i = 0; i < this.currentSelectedSources_.length; ++i) { - if (this.currentSelectedSources_[i] == sourceEntry) { + for (var i = 0; i < this.currentSelectedRows_.length; ++i) { + if (this.currentSelectedRows_[i] == sourceRow) { index = i; break; } @@ -623,16 +624,24 @@ EventsView.prototype.modifySelectionArray = function( if (index != -1 && !addToSelection) { // Remove from the selection. - this.currentSelectedSources_.splice(index, 1); + this.currentSelectedRows_.splice(index, 1); } if (index == -1 && addToSelection) { - this.currentSelectedSources_.push(sourceEntry); + this.currentSelectedRows_.push(sourceRow); + } +}; + +EventsView.prototype.getSelectedSourceEntries_ = function() { + var sourceEntries = []; + for (var id in this.currentSelectedRows_) { + sourceEntries.push(this.currentSelectedRows_[id].getSourceEntry()); } + return sourceEntries; }; EventsView.prototype.invalidateDetailsView_ = function() { - this.detailsView_.setData(this.currentSelectedSources_); + this.detailsView_.setData(this.getSelectedSourceEntries_()); }; EventsView.prototype.invalidateFilterCounter_ = function() { diff --git a/chrome/browser/resources/net_internals/index.html b/chrome/browser/resources/net_internals/index.html index f48a57d..6dd6be23 100644 --- a/chrome/browser/resources/net_internals/index.html +++ b/chrome/browser/resources/net_internals/index.html @@ -10,6 +10,7 @@ found in the LICENSE file. <link rel="stylesheet" href="main.css"> <link rel="stylesheet" href="tabswitcherview.css"> <script src="chrome://resources/js/util.js"></script> + <script src="chrome://resources/js/cr.js"></script> <script src="chrome://net-internals/index.js"></script> <script src="chrome://net-internals/strings.js"></script> </head> diff --git a/chrome/browser/resources/net_internals/index.js b/chrome/browser/resources/net_internals/index.js index 8197867..bc42978 100644 --- a/chrome/browser/resources/net_internals/index.js +++ b/chrome/browser/resources/net_internals/index.js @@ -9,9 +9,11 @@ <include src="httpcacheview.js"/> <include src="testview.js"/> <include src="hstsview.js"/> -<include src="logdumputil.js"/> +<include src="browserbridge.js"/> +<include src="sourcetracker.js"/> <include src="main.js"/> <include src="dnsview.js"/> +<include src="sourcerow.js"/> <include src="eventsview.js"/> <include src="detailsview.js"/> <include src="sourceentry.js"/> diff --git a/chrome/browser/resources/net_internals/logdumputil.js b/chrome/browser/resources/net_internals/logdumputil.js deleted file mode 100644 index cb48c59..0000000 --- a/chrome/browser/resources/net_internals/logdumputil.js +++ /dev/null @@ -1,229 +0,0 @@ -// 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, fileName) { - // 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(fileName); - - // 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, fileName) { - // 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, fileName); -}; - -// End of anonymous namespace. -})(); - diff --git a/chrome/browser/resources/net_internals/logviewpainter.js b/chrome/browser/resources/net_internals/logviewpainter.js index b95cb5d..f37e593 100644 --- a/chrome/browser/resources/net_internals/logviewpainter.js +++ b/chrome/browser/resources/net_internals/logviewpainter.js @@ -38,7 +38,7 @@ function addSourceEntry_(node, sourceEntry) { var nobr2 = addNode(p2, 'nobr'); var logEntries = sourceEntry.getLogEntries(); - var startDate = g_browser.convertTimeTicksToDate(logEntries[0].time); + var startDate = convertTimeTicksToDate(logEntries[0].time); addTextNode(nobr2, 'Start Time: ' + startDate.toLocaleString()); var pre = addNode(div, 'pre'); @@ -60,7 +60,7 @@ PrintSourceEntriesAsText = function(sourceEntries) { if (entries.length == 0) return ''; - var startDate = g_browser.convertTimeTicksToDate(entries[0].orig.time); + var startDate = convertTimeTicksToDate(entries[0].orig.time); var startTime = startDate.getTime(); var tablePrinter = new TablePrinter(); @@ -77,7 +77,7 @@ PrintSourceEntriesAsText = function(sourceEntries) { tablePrinter.addCell(entry.orig.wasPassivelyCaptured ? '(P) ' : ''); tablePrinter.addCell('t='); - var date = g_browser.convertTimeTicksToDate(entry.orig.time) ; + var date = convertTimeTicksToDate(entry.orig.time) ; var tCell = tablePrinter.addCell(date.getTime()); tCell.alignRight = true; tablePrinter.addCell(' [st='); @@ -112,7 +112,7 @@ PrintSourceEntriesAsText = function(sourceEntries) { // Add a continuation row for each line of text from the extra parameters. var extraParamsText = getTextForExtraParams( entry.orig, - g_browser.getSecurityStripping()); + g_browser.sourceTracker.getSecurityStripping()); var extraParamsTextLines = extraParamsText.split('\n'); for (var j = 0; j < extraParamsTextLines.length; ++j) { diff --git a/chrome/browser/resources/net_internals/main.js b/chrome/browser/resources/net_internals/main.js index c53bf2e..4d7c5d9 100644 --- a/chrome/browser/resources/net_internals/main.js +++ b/chrome/browser/resources/net_internals/main.js @@ -14,9 +14,6 @@ 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} @@ -24,14 +21,42 @@ var Constants = null; var g_browser = null; /** + * The browser gives us times in terms of "time ticks" in milliseconds. + * This function converts the tick count to a Date() object. + * + * @param {String} timeTicks. + * @returns {Date} The time that |timeTicks| represents. + */ +var convertTimeTicksToDate; + +/** + * 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. + */ +var createLogDumpAsync; + +/** + * 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. + */ +var loadLogFile; + +// Start of annonymous namespace. +(function() { + +var categoryTabSwitcher; + +/** * Main entry point. called once the page has loaded. */ -function onLoaded() { +onLoaded = function() { g_browser = new BrowserBridge(); // Create a view which lets you tab between the different sub-views. // This view is a left (resizable) navigation bar. - var categoryTabSwitcher = new TabSwitcherView(); + categoryTabSwitcher = new TabSwitcherView(); var tabSwitcherSplitView = new ResizableVerticalSplitView( new DivView('categoryTabHandles'), categoryTabSwitcher, @@ -43,8 +68,6 @@ function onLoaded() { // name rather than using a fixed width. tabSwitcherSplitView.setLeftSplit(150); - g_browser.setTabSwitcher(categoryTabSwitcher); - // 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. @@ -56,13 +79,13 @@ function onLoaded() { categoryTabSwitcher.addTab('httpCacheTab', new HttpCacheView(), false, true); categoryTabSwitcher.addTab('dataTab', new DataView(), false, true); categoryTabSwitcher.addTab('serviceProvidersTab', new ServiceProvidersView(), - false, g_browser.isPlatformWindows()); + false, cr.isWindows); categoryTabSwitcher.addTab('testTab', new TestView(), false, true); categoryTabSwitcher.addTab('hstsTab', new HSTSView(), false, true); categoryTabSwitcher.addTab('httpThrottlingTab', new HttpThrottlingView(), false, true); categoryTabSwitcher.addTab('logsTab', new LogsView(), false, - g_browser.isChromeOS()); + cr.isChromeOS); categoryTabSwitcher.addTab('prerenderTab', new PrerenderView(), false, true); // Build a map from the anchor name of each tab handle to its "tab ID". @@ -76,8 +99,7 @@ function onLoaded() { // Default the empty hash to the data tab. anchorMap['#'] = anchorMap[''] = 'dataTab'; - window.onhashchange = onUrlHashChange.bind(null, anchorMap, - categoryTabSwitcher); + window.onhashchange = onUrlHashChange.bind(null, anchorMap); // Cut out a small vertical strip at the top of the window, to display // a high level status (i.e. if we are capturing events, or displaying a @@ -94,75 +116,11 @@ function onLoaded() { // Select the initial view based on the current URL. window.onhashchange(); + g_browser.addConstantsObserver(new ConstantsObserver()); + // Tell the browser that we are ready to start receiving log events. g_browser.sendReady(); -} - -/** - * This class provides a "bridge" for communicating between the javascript and - * the browser. - * - * @constructor - */ -function BrowserBridge() { - // List of observers for various bits of browser state. - this.logObservers_ = []; - this.connectionTestsObservers_ = []; - 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', - this.sendGetProxySettings.bind(this)); - this.pollableDataHelpers_.badProxies = - new PollableDataHelper('onBadProxiesChanged', - this.sendGetBadProxies.bind(this)); - this.pollableDataHelpers_.httpCacheInfo = - new PollableDataHelper('onHttpCacheInfoChanged', - this.sendGetHttpCacheInfo.bind(this)); - this.pollableDataHelpers_.hostResolverInfo = - new PollableDataHelper('onHostResolverInfoChanged', - this.sendGetHostResolverInfo.bind(this)); - this.pollableDataHelpers_.socketPoolInfo = - new PollableDataHelper('onSocketPoolInfoChanged', - this.sendGetSocketPoolInfo.bind(this)); - this.pollableDataHelpers_.spdySessionInfo = - new PollableDataHelper('onSpdySessionInfoChanged', - this.sendGetSpdySessionInfo.bind(this)); - this.pollableDataHelpers_.spdyStatus = - new PollableDataHelper('onSpdyStatusChanged', - this.sendGetSpdyStatus.bind(this)); - this.pollableDataHelpers_.spdyAlternateProtocolMappings = - new PollableDataHelper('onSpdyAlternateProtocolMappingsChanged', - this.sendGetSpdyAlternateProtocolMappings.bind( - this)); - if (this.isPlatformWindows()) { - this.pollableDataHelpers_.serviceProviders = - new PollableDataHelper('onServiceProvidersChanged', - this.sendGetServiceProviders.bind(this)); - } - this.pollableDataHelpers_.prerenderInfo = - new PollableDataHelper('onPrerenderInfoChanged', - this.sendGetPrerenderInfo.bind(this)); - - // Cache of the data received. - this.numPassivelyCapturedEvents_ = 0; - this.capturedEvents_ = []; - - // Next unique id to be assigned to a log entry without a source. - // Needed to simplify deletion, identify associated GUI elements, etc. - this.nextSourcelessEventId_ = -1; - - // True when cookies and authentication information should be removed from - // displayed events. When true, such information should be hidden from - // all pages. - this.enableSecurityStripping_ = true; -} +}; /* * Takes the current hash in form of "#tab¶m1=value1¶m2=value2&...". @@ -172,7 +130,7 @@ function BrowserBridge() { * * Parameters and values are decoded with decodeURIComponent(). */ -function onUrlHashChange(anchorMap, categoryTabSwitcher) { +function onUrlHashChange(anchorMap) { var parameters = window.location.hash.split('&'); var tabId = anchorMap[parameters[0]]; @@ -196,329 +154,6 @@ 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; - -//------------------------------------------------------------------------------ -// Messages sent to the browser -//------------------------------------------------------------------------------ - -/** - * Wraps |chrome.send|. Doesn't send anything when viewing a log file. - */ -BrowserBridge.prototype.send = function(value1, value2) { - if (!this.isViewingLogFile_) { - if (arguments.length == 1) { - chrome.send(value1); - } else if (arguments.length == 2) { - chrome.send(value1, value2); - } else { - throw 'Unsupported number of arguments.'; - } - } -}; - -BrowserBridge.prototype.sendReady = function() { - 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 - // the observers. - window.setInterval(this.checkForUpdatedInfo.bind(this, false), - BrowserBridge.POLL_INTERVAL_MS); -}; - -BrowserBridge.prototype.isPlatformWindows = function() { - return /Win/.test(navigator.platform); -}; - -BrowserBridge.prototype.isChromeOS = function() { - return /CrOS/.test(navigator.userAgent); -}; - -BrowserBridge.prototype.sendGetProxySettings = function() { - // The browser will call receivedProxySettings on completion. - this.send('getProxySettings'); -}; - -BrowserBridge.prototype.sendReloadProxySettings = function() { - this.send('reloadProxySettings'); -}; - -BrowserBridge.prototype.sendGetBadProxies = function() { - // The browser will call receivedBadProxies on completion. - this.send('getBadProxies'); -}; - -BrowserBridge.prototype.sendGetHostResolverInfo = function() { - // The browser will call receivedHostResolverInfo on completion. - this.send('getHostResolverInfo'); -}; - -BrowserBridge.prototype.sendClearBadProxies = function() { - this.send('clearBadProxies'); -}; - -BrowserBridge.prototype.sendClearHostResolverCache = function() { - this.send('clearHostResolverCache'); -}; - -BrowserBridge.prototype.sendStartConnectionTests = function(url) { - this.send('startConnectionTests', [url]); -}; - -BrowserBridge.prototype.sendHSTSQuery = function(domain) { - this.send('hstsQuery', [domain]); -}; - -BrowserBridge.prototype.sendHSTSAdd = function(domain, - include_subdomains, - pins) { - this.send('hstsAdd', [domain, include_subdomains, pins]); -}; - -BrowserBridge.prototype.sendHSTSDelete = function(domain) { - this.send('hstsDelete', [domain]); -}; - -BrowserBridge.prototype.sendGetHttpCacheInfo = function() { - this.send('getHttpCacheInfo'); -}; - -BrowserBridge.prototype.sendGetSocketPoolInfo = function() { - this.send('getSocketPoolInfo'); -}; - -BrowserBridge.prototype.sendCloseIdleSockets = function() { - this.send('closeIdleSockets'); -}; - -BrowserBridge.prototype.sendFlushSocketPools = function() { - this.send('flushSocketPools'); -}; - -BrowserBridge.prototype.sendGetSpdySessionInfo = function() { - this.send('getSpdySessionInfo'); -}; - -BrowserBridge.prototype.sendGetSpdyStatus = function() { - this.send('getSpdyStatus'); -}; - -BrowserBridge.prototype.sendGetSpdyAlternateProtocolMappings = function() { - this.send('getSpdyAlternateProtocolMappings'); -}; - -BrowserBridge.prototype.sendGetServiceProviders = function() { - this.send('getServiceProviders'); -}; - -BrowserBridge.prototype.sendGetPrerenderInfo = function() { - this.send('getPrerenderInfo'); -}; - -BrowserBridge.prototype.enableIPv6 = function() { - this.send('enableIPv6'); -}; - -BrowserBridge.prototype.setLogLevel = function(logLevel) { - this.send('setLogLevel', ['' + logLevel]); -}; - -BrowserBridge.prototype.enableHttpThrottling = function(enable) { - this.send('enableHttpThrottling', [enable]); -}; - -BrowserBridge.prototype.refreshSystemLogs = function() { - this.send('refreshSystemLogs'); -}; - -BrowserBridge.prototype.getSystemLog = function(log_key, cellId) { - this.send('getSystemLog', [log_key, cellId]); -}; - -//------------------------------------------------------------------------------ -// Messages received from the browser. -//------------------------------------------------------------------------------ - -BrowserBridge.prototype.receive = function(command, params) { - // Does nothing if viewing a log file. - if (this.isViewingLogFile_) - return; - this[command](params); -}; - -BrowserBridge.prototype.receivedConstants = function(constants) { - this.logFormatVersion_ = constants.logFormatVersion; - - this.loadConstants(constants); -}; - -BrowserBridge.prototype.receivedLogEntries = function(logEntries) { - this.addLogEntries(logEntries); -}; - -BrowserBridge.prototype.receivedProxySettings = function(proxySettings) { - this.pollableDataHelpers_.proxySettings.update(proxySettings); -}; - -BrowserBridge.prototype.receivedBadProxies = function(badProxies) { - this.pollableDataHelpers_.badProxies.update(badProxies); -}; - -BrowserBridge.prototype.receivedHostResolverInfo = -function(hostResolverInfo) { - this.pollableDataHelpers_.hostResolverInfo.update(hostResolverInfo); -}; - -BrowserBridge.prototype.receivedSocketPoolInfo = function(socketPoolInfo) { - this.pollableDataHelpers_.socketPoolInfo.update(socketPoolInfo); -}; - -BrowserBridge.prototype.receivedSpdySessionInfo = function(spdySessionInfo) { - this.pollableDataHelpers_.spdySessionInfo.update(spdySessionInfo); -}; - -BrowserBridge.prototype.receivedSpdyStatus = function(spdyStatus) { - this.pollableDataHelpers_.spdyStatus.update(spdyStatus); -}; - -BrowserBridge.prototype.receivedSpdyAlternateProtocolMappings = - function(spdyAlternateProtocolMappings) { - this.pollableDataHelpers_.spdyAlternateProtocolMappings.update( - spdyAlternateProtocolMappings); -}; - -BrowserBridge.prototype.receivedServiceProviders = function(serviceProviders) { - this.pollableDataHelpers_.serviceProviders.update(serviceProviders); -}; - -BrowserBridge.prototype.receivedPassiveLogEntries = function(entries) { - // Due to an expected race condition, it is possible to receive actively - // captured log entries before the passively logged entries are received. - // - // When that happens, we create a copy of the actively logged entries, delete - // all entries, and, after handling all the passively logged entries, add back - // the deleted actively logged entries. - var earlyActivelyCapturedEvents = this.capturedEvents_.slice(0); - if (earlyActivelyCapturedEvents.length > 0) - this.deleteAllEvents(); - - this.numPassivelyCapturedEvents_ = entries.length; - for (var i = 0; i < entries.length; ++i) - entries[i].wasPassivelyCaptured = true; - this.receivedLogEntries(entries); - - // Add back early actively captured events, if any. - if (earlyActivelyCapturedEvents.length) - this.receivedLogEntries(earlyActivelyCapturedEvents); -}; - - -BrowserBridge.prototype.receivedStartConnectionTestSuite = function() { - for (var i = 0; i < this.connectionTestsObservers_.length; ++i) - this.connectionTestsObservers_[i].onStartedConnectionTestSuite(); -}; - -BrowserBridge.prototype.receivedStartConnectionTestExperiment = function( - experiment) { - for (var i = 0; i < this.connectionTestsObservers_.length; ++i) { - this.connectionTestsObservers_[i].onStartedConnectionTestExperiment( - experiment); - } -}; - -BrowserBridge.prototype.receivedCompletedConnectionTestExperiment = -function(info) { - for (var i = 0; i < this.connectionTestsObservers_.length; ++i) { - this.connectionTestsObservers_[i].onCompletedConnectionTestExperiment( - info.experiment, info.result); - } -}; - -BrowserBridge.prototype.receivedCompletedConnectionTestSuite = function() { - for (var i = 0; i < this.connectionTestsObservers_.length; ++i) - this.connectionTestsObservers_[i].onCompletedConnectionTestSuite(); -}; - -BrowserBridge.prototype.receivedHSTSResult = function(info) { - for (var i = 0; i < this.hstsObservers_.length; ++i) - this.hstsObservers_[i].onHSTSQueryResult(info); -}; - -BrowserBridge.prototype.receivedHttpCacheInfo = function(info) { - this.pollableDataHelpers_.httpCacheInfo.update(info); -}; - -BrowserBridge.prototype.receivedHttpThrottlingEnabledPrefChanged = function( - enabled) { - for (var i = 0; i < this.httpThrottlingObservers_.length; ++i) { - this.httpThrottlingObservers_[i].onHttpThrottlingEnabledPrefChanged( - enabled); - } -}; - -BrowserBridge.prototype.receivedPrerenderInfo = function(prerenderInfo) { - this.pollableDataHelpers_.prerenderInfo.update(prerenderInfo); -}; - -//------------------------------------------------------------------------------ - -BrowserBridge.prototype.categoryTabSwitcher = function() { - return this.categoryTabSwitcher_; -}; - -BrowserBridge.prototype.logFormatVersion = function() { - return this.logFormatVersion_; -}; - -BrowserBridge.prototype.isViewingLogFile = function() { - return this.isViewingLogFile_; -}; - -/** * 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 @@ -526,460 +161,284 @@ BrowserBridge.prototype.isViewingLogFile = function() { * * @param {String} fileName The name of the log file that has been loaded. */ -BrowserBridge.prototype.onLoadLogFile = function(fileName) { - if (!this.isViewingLogFile_) { - this.isViewingLogFile_ = true; - this.setSecurityStripping(false); - - // Swap out the status bar to indicate we have loaded from a file. - setNodeDisplay($('statusViewForCapture'), false); - setNodeDisplay($('statusViewForFile'), true); - - // Indicate which file is being displayed. - $('statusViewDumpFileName').innerText = fileName; - - document.styleSheets[0].insertRule('.hideOnLoadLog { display: none; }'); - } -}; - -/** - * 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 - * data arrives, through: - * - * observer.onLogEntryAdded(logEntry) - */ -BrowserBridge.prototype.addLogObserver = function(observer) { - this.logObservers_.push(observer); -}; - -/** - * Adds a listener of the proxy settings. |observer| will be called back when - * data is received, through: - * - * observer.onProxySettingsChanged(proxySettings) - * - * |proxySettings| is a dictionary with (up to) two properties: - * - * "original" -- The settings that chrome was configured to use - * (i.e. system settings.) - * "effective" -- The "effective" proxy settings that chrome is using. - * (decides between the manual/automatic modes of the - * fetched settings). - * - * Each of these two configurations is formatted as a string, and may be - * omitted if not yet initialized. - * - * TODO(eroman): send a dictionary instead. - */ -BrowserBridge.prototype.addProxySettingsObserver = function(observer) { - this.pollableDataHelpers_.proxySettings.addObserver(observer); -}; +function onLoadLogFile(fileName) { + // Swap out the status bar to indicate we have loaded from a file. + setNodeDisplay($('statusViewForCapture'), false); + setNodeDisplay($('statusViewForFile'), true); -/** - * Adds a listener of the proxy settings. |observer| will be called back when - * data is received, through: - * - * observer.onBadProxiesChanged(badProxies) - * - * |badProxies| is an array, where each entry has the property: - * badProxies[i].proxy_uri: String identify the proxy. - * badProxies[i].bad_until: The time when the proxy stops being considered - * bad. Note the time is in time ticks. - */ -BrowserBridge.prototype.addBadProxiesObserver = function(observer) { - this.pollableDataHelpers_.badProxies.addObserver(observer); -}; + // Indicate which file is being displayed. + $('statusViewDumpFileName').innerText = fileName; -/** - * Adds a listener of the host resolver info. |observer| will be called back - * when data is received, through: - * - * observer.onHostResolverInfoChanged(hostResolverInfo) - */ -BrowserBridge.prototype.addHostResolverInfoObserver = function(observer) { - this.pollableDataHelpers_.hostResolverInfo.addObserver(observer); -}; - -/** - * Adds a listener of the socket pool. |observer| will be called back - * when data is received, through: - * - * observer.onSocketPoolInfoChanged(socketPoolInfo) - */ -BrowserBridge.prototype.addSocketPoolInfoObserver = function(observer) { - this.pollableDataHelpers_.socketPoolInfo.addObserver(observer); -}; + document.styleSheets[0].insertRule('.hideOnLoadLog { display: none; }'); -/** - * Adds a listener of the SPDY info. |observer| will be called back - * when data is received, through: - * - * observer.onSpdySessionInfoChanged(spdySessionInfo) - */ -BrowserBridge.prototype.addSpdySessionInfoObserver = function(observer) { - this.pollableDataHelpers_.spdySessionInfo.addObserver(observer); -}; - -/** - * Adds a listener of the SPDY status. |observer| will be called back - * when data is received, through: - * - * observer.onSpdyStatusChanged(spdyStatus) - */ -BrowserBridge.prototype.addSpdyStatusObserver = function(observer) { - this.pollableDataHelpers_.spdyStatus.addObserver(observer); -}; + g_browser.sourceTracker.setSecurityStripping(false); + g_browser.disable(); +} /** - * Adds a listener of the AlternateProtocolMappings. |observer| will be called - * back when data is received, through: - * - * observer.onSpdyAlternateProtocolMappingsChanged( - * spdyAlternateProtocolMappings) + * Returns true if |constants| appears to be a valid constants object. */ -BrowserBridge.prototype.addSpdyAlternateProtocolMappingsObserver = - function(observer) { - this.pollableDataHelpers_.spdyAlternateProtocolMappings.addObserver(observer); -}; +function areValidConstants(receivedConstants) { + return typeof(receivedConstants) == 'object' && + typeof(receivedConstants.logEventTypes) == 'object' && + typeof(receivedConstants.clientInfo) == 'object' && + typeof(receivedConstants.logEventPhase) == 'object' && + typeof(receivedConstants.logSourceType) == 'object' && + typeof(receivedConstants.logLevelType) == 'object' && + typeof(receivedConstants.loadFlag) == 'object' && + typeof(receivedConstants.netError) == 'object' && + typeof(receivedConstants.addressFamily) == 'object' && + typeof(receivedConstants.timeTickOffset) == 'string' && + typeof(receivedConstants.logFormatVersion) == 'number'; +} /** - * Adds a listener of the service providers info. |observer| will be called - * 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. + * Dictionary of all constants, used for saving log files. */ -BrowserBridge.prototype.addServiceProvidersObserver = function(observer) { - if (this.pollableDataHelpers_.serviceProviders) - this.pollableDataHelpers_.serviceProviders.addObserver(observer); -}; +var constants = null; /** - * Adds a listener for the progress of the connection tests. - * The observer will be called back with: - * - * observer.onStartedConnectionTestSuite(); - * observer.onStartedConnectionTestExperiment(experiment); - * observer.onCompletedConnectionTestExperiment(experiment, result); - * observer.onCompletedConnectionTestSuite(); + * Offset needed to convert event times to Date objects. */ -BrowserBridge.prototype.addConnectionTestsObserver = function(observer) { - this.connectionTestsObservers_.push(observer); -}; +var timeTickOffset = 0; -/** - * Adds a listener for the http cache info results. - * The observer will be called back with: - * - * observer.onHttpCacheInfoChanged(info); - */ -BrowserBridge.prototype.addHttpCacheInfoObserver = function(observer) { - this.pollableDataHelpers_.httpCacheInfo.addObserver(observer); -}; +function ConstantsObserver() {} /** - * Adds a listener for the results of HSTS (HTTPS Strict Transport Security) - * queries. The observer will be called back with: - * - * observer.onHSTSQueryResult(result); + * 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.addHSTSObserver = function(observer) { - this.hstsObservers_.push(observer); -}; +ConstantsObserver.prototype.onReceivedConstants = function(receivedConstants) { + if (!areValidConstants(receivedConstants)) + return false; -/** - * Adds a listener for HTTP throttling-related events. |observer| will be called - * back when HTTP throttling is enabled/disabled, through: - * - * observer.onHttpThrottlingEnabledPrefChanged(enabled); - */ -BrowserBridge.prototype.addHttpThrottlingObserver = function(observer) { - this.httpThrottlingObservers_.push(observer); -}; + constants = receivedConstants; -/** - * Adds a listener for updated prerender info events - * |observer| will be called back with: - * - * observer.onPrerenderInfoChanged(prerenderInfo); - */ -BrowserBridge.prototype.addPrerenderInfoObserver = function(observer) { - this.pollableDataHelpers_.prerenderInfo.addObserver(observer); -}; + LogEventType = constants.logEventTypes; + ClientInfo = constants.clientInfo; + LogEventPhase = constants.logEventPhase; + LogSourceType = constants.logSourceType; + LogLevelType = constants.logLevelType; + LoadFlag = constants.loadFlag; + NetError = constants.netError; + AddressFamily = constants.addressFamily; -/** - * The browser gives us times in terms of "time ticks" in milliseconds. - * This function converts the tick count to a Date() object. - * - * @param {String} timeTicks. - * @returns {Date} The time that |timeTicks| represents. - */ -BrowserBridge.prototype.convertTimeTicksToDate = function(timeTicks) { // Note that the subtraction by 0 is to cast to a number (probably a float // since the numbers are big). - var timeStampMs = (this.timeTickOffset_ - 0) + (timeTicks - 0); - var d = new Date(); - d.setTime(timeStampMs); - return d; -}; + timeTickOffset = constants.timeTickOffset - 0; -/** - * Returns a list of all captured events. - */ -BrowserBridge.prototype.getAllCapturedEvents = function() { - return this.capturedEvents_; -}; - -/** - * Returns the number of events that were captured while we were - * listening for events. - */ -BrowserBridge.prototype.getNumActivelyCapturedEvents = function() { - return this.capturedEvents_.length - this.numPassivelyCapturedEvents_; + return true; }; -/** - * Returns the number of events that were captured passively by the - * browser prior to when the net-internals page was started. - */ -BrowserBridge.prototype.getNumPassivelyCapturedEvents = function() { - return this.numPassivelyCapturedEvents_; +convertTimeTicksToDate = function(timeTicks) { + var timeStampMs = timeTickOffset + (timeTicks - 0); + return new Date(timeStampMs); }; /** - * Sends each entry to all log observers, and updates |capturedEvents_|. - * Also assigns unique ids to log entries without a source. + * 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. */ -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); - } -}; +function createLogDump(constants, events, polledData, tabData, date) { + if (g_browser.sourceTracker.getSecurityStripping()) + events = events.map(stripCookiesAndLoginInfo); -/** - * Deletes captured events with source IDs in |sourceIds|. - */ -BrowserBridge.prototype.deleteEventsBySourceId = function(sourceIds) { - var sourceIdDict = {}; - for (var i = 0; i < sourceIds.length; i++) - sourceIdDict[sourceIds[i]] = true; - - var newEventList = []; - for (var i = 0; i < this.capturedEvents_.length; ++i) { - var id = this.capturedEvents_[i].source.id; - if (id in sourceIdDict) { - if (this.capturedEvents_[i].wasPassivelyCaptured) - --this.numPassivelyCapturedEvents_; - continue; - } - newEventList.push(this.capturedEvents_[i]); - } - this.capturedEvents_ = newEventList; + var logDump = { + 'constants': constants, + 'events': events, + 'polledData': polledData, + 'tabData': tabData + }; - for (var i = 0; i < this.logObservers_.length; ++i) - this.logObservers_[i].onLogEntriesDeleted(sourceIds); -}; + // Not technically client info, but it's used at the same point in the code. + if (date && constants.clientInfo) + constants.clientInfo.date = date; -/** - * Deletes all captured events. - */ -BrowserBridge.prototype.deleteAllEvents = function() { - this.capturedEvents_ = []; - this.numPassivelyCapturedEvents_ = 0; - for (var i = 0; i < this.logObservers_.length; ++i) - this.logObservers_[i].onAllLogEntriesDeleted(); -}; + return logDump; +} /** - * Sets the value of |enableSecurityStripping_| and informs log observers - * of the change. + * Creates a full log dump using |polledData| and the return value of each tab's + * saveState function and passes it to |callback|. */ -BrowserBridge.prototype.setSecurityStripping = - function(enableSecurityStripping) { - this.enableSecurityStripping_ = enableSecurityStripping; - for (var i = 0; i < this.logObservers_.length; ++i) { - if (this.logObservers_[i].onSecurityStrippingChanged) - this.logObservers_[i].onSecurityStrippingChanged(); +function onUpdateAllCompleted(callback, polledData) { + // Gather any tab-specific state information. + var tabData = {}; + 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(); } -}; - -/** - * Returns whether or not cookies and authentication information should be - * displayed for events that contain them. - */ -BrowserBridge.prototype.getSecurityStripping = function() { - return this.enableSecurityStripping_; -}; -/** - * Returns true if a log file is currently being viewed. - */ -BrowserBridge.prototype.isViewingLogFile = function() { - return this.isViewingLogFile_; -}; + var logDump = createLogDump(constants, + g_browser.sourceTracker.getAllCapturedEvents(), + polledData, + tabData, + (new Date()).toLocaleString()); + callback(JSON.stringify(logDump, null, ' ')); +} -/** - * If |force| is true, calls all startUpdate functions. Otherwise, just - * runs updates with active observers. - */ -BrowserBridge.prototype.checkForUpdatedInfo = function(force) { - for (name in this.pollableDataHelpers_) { - var helper = this.pollableDataHelpers_[name]; - if (force || helper.hasActiveObserver()) - helper.startUpdate(); +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, fileName) { + // 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 (!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 != constants.logFormatVersion) { + return 'Unable to load different log version.' + + ' Found ' + logDump.constants.logFormatVersion + + ', Expected ' + constants.logFormatVersion; } -}; + if (!areValidConstants(logDump.constants)) + return 'Expected constants not found in log file.'; -/** - * Calls all startUpdate functions and, if |callback| is non-null, - * calls it with the results of all updates. - */ -BrowserBridge.prototype.updateAllInfo = function(callback) { - if (callback) - new UpdateAllObserver(callback, this.pollableDataHelpers_); - this.checkForUpdatedInfo(true); -}; + g_browser.receivedConstants(logDump.constants); -/** - * This is a helper class used by BrowserBridge, to keep track of: - * - the list of observers interested in some piece of data. - * - the last known value of that piece of data. - * - the name of the callback method to invoke on observers. - * - the update function. - * @constructor - */ -function PollableDataHelper(observerMethodName, startUpdateFunction) { - this.observerMethodName_ = observerMethodName; - this.startUpdate = startUpdateFunction; - this.observerInfos_ = []; -} + // 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. + onLoadLogFile(fileName); -PollableDataHelper.prototype.getObserverMethodName = function() { - return this.observerMethodName_; -}; + // Delete all events. This will also update all logObservers. + g_browser.sourceTracker.deleteAllSourceEntries(); -PollableDataHelper.prototype.isObserver = function(object) { - for (var i = 0; i < this.observerInfos_.length; ++i) { - if (this.observerInfos_[i].observer == object) - return true; + // Inform all the views that a log file is being loaded, and pass in + // view-specific saved state, if any. + 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]]); } - 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 - * latter is used to make sure that new observers get sent data on the - * update following their creation. - * @constructor - */ -function ObserverInfo(observer) { - this.observer = observer; - this.hasReceivedData = false; -} - -PollableDataHelper.prototype.addObserver = function(observer) { - this.observerInfos_.push(new ObserverInfo(observer)); -}; -PollableDataHelper.prototype.removeObserver = function(observer) { - for (var i = 0; i < this.observerInfos_.length; ++i) { - if (this.observerInfos_[i].observer === observer) { - this.observerInfos_.splice(i, 1); - return; + // 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); + } } } -}; - -/** - * Helper function to handle calling all the observers, but ONLY if the data has - * actually changed since last time or the observer has yet to receive any data. - * This is used for data we received from browser on an update loop. - */ -PollableDataHelper.prototype.update = function(data) { - var prevData = this.currentData_; - var changed = false; - - // If the data hasn't changed since last time, will only need to notify - // observers that have not yet received any data. - if (!prevData || JSON.stringify(prevData) != JSON.stringify(data)) { - changed = true; - this.currentData_ = data; + g_browser.sourceTracker.onReceivedPassiveLogEntries(validPassiveEvents); + g_browser.sourceTracker.onReceivedLogEntries(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'; } - // Notify the observers of the change, as needed. - for (var i = 0; i < this.observerInfos_.length; ++i) { - var observerInfo = this.observerInfos_[i]; - if (changed || !observerInfo.hasReceivedData) { - observerInfo.observer[this.observerMethodName_](this.currentData_); - observerInfo.hasReceivedData = true; + // 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); } -}; -/** - * Returns true if one of the observers actively wants the data - * (i.e. is visible). - */ -PollableDataHelper.prototype.hasActiveObserver = function() { - for (var i = 0; i < this.observerInfos_.length; ++i) { - if (this.observerInfos_[i].observer.isActive()) - return true; - } - return false; -}; + return errorString + 'Log loaded.'; +} -/** - * This is a helper class used by BrowserBridge to send data to - * a callback once data from all polls has been received. - * - * It works by keeping track of how many polling functions have - * yet to receive data, and recording the data as it it received. - * - * @constructor - */ -function UpdateAllObserver(callback, pollableDataHelpers) { - this.callback_ = callback; - this.observingCount_ = 0; - this.updatedData_ = {}; - - for (name in pollableDataHelpers) { - ++this.observingCount_; - var helper = pollableDataHelpers[name]; - helper.addObserver(this); - this[helper.getObserverMethodName()] = - this.onDataReceived_.bind(this, helper, name); +loadLogFile = function(logFileContents, fileName) { + // 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) { + } } -} -UpdateAllObserver.prototype.isActive = function() { - return true; + if (!parsedDump) + return 'Unable to parse log dump as JSON file.'; + return loadLogDump(parsedDump, fileName); }; -UpdateAllObserver.prototype.onDataReceived_ = function(helper, name, data) { - helper.removeObserver(this); - --this.observingCount_; - this.updatedData_[name] = data; - if (this.observingCount_ == 0) - this.callback_(this.updatedData_); -}; +// End of anonymous namespace. +})(); diff --git a/chrome/browser/resources/net_internals/proxyview.js b/chrome/browser/resources/net_internals/proxyview.js index e50ab54..bece0c4 100644 --- a/chrome/browser/resources/net_internals/proxyview.js +++ b/chrome/browser/resources/net_internals/proxyview.js @@ -24,7 +24,6 @@ function ProxyView() { DivView.call(this, mainBoxId); - this.latestProxySourceEntries_ = null; this.latestProxySourceId_ = 0; // Hook up the UI components. @@ -43,7 +42,7 @@ function ProxyView() { // Register to receive proxy information as it changes. g_browser.addProxySettingsObserver(this); g_browser.addBadProxiesObserver(this); - g_browser.addLogObserver(this); + g_browser.sourceTracker.addObserver(this); } inherits(ProxyView, DivView); @@ -102,7 +101,7 @@ ProxyView.prototype.onBadProxiesChanged = function(badProxies) { // Add a table row for each bad proxy entry. for (var i = 0; i < badProxies.length; ++i) { var entry = badProxies[i]; - var badUntilDate = g_browser.convertTimeTicksToDate(entry.bad_until); + var badUntilDate = convertTimeTicksToDate(entry.bad_until); var tr = addNode(this.badProxiesTbody_, 'tr'); @@ -115,28 +114,22 @@ ProxyView.prototype.onBadProxiesChanged = function(badProxies) { return true; }; -ProxyView.prototype.onLogEntryAdded = function(logEntry) { - if (logEntry.source.type != LogSourceType.INIT_PROXY_RESOLVER || - this.latestProxySourceId_ > logEntry.source.id) { +ProxyView.prototype.onSourceEntryUpdated = function(sourceEntry) { + if (sourceEntry.getSourceType() != LogSourceType.INIT_PROXY_RESOLVER || + this.latestProxySourceId_ > sourceEntry.getSourceId()) { return; } - if (logEntry.source.id > this.latestProxySourceId_) { - this.latestProxySourceId_ = logEntry.source.id; - this.latestProxySourceEntries_ = []; - } + this.latestProxySourceId_ = sourceEntry.getSourceId(); - this.latestProxySourceEntries_.push(logEntry); this.proxyResolverLogPre_.innerHTML = ''; - addTextNode(this.proxyResolverLogPre_, - PrintSourceEntriesAsText(this.latestProxySourceEntries_)); + addTextNode(this.proxyResolverLogPre_, sourceEntry.printAsText()); }; /** * Clears the display of and log entries for the last proxy lookup. */ ProxyView.prototype.clearLog_ = function() { - this.latestProxySourceEntries_ = []; // Prevents display of partial logs. ++this.latestProxySourceId_; @@ -144,11 +137,11 @@ ProxyView.prototype.clearLog_ = function() { addTextNode(this.proxyResolverLogPre_, 'Deleted.'); }; -ProxyView.prototype.onLogEntriesDeleted = function(sourceIds) { +ProxyView.prototype.onSourceEntriesDeleted = function(sourceIds) { if (sourceIds.indexOf(this.latestProxySourceId_) != -1) this.clearLog_(); }; -ProxyView.prototype.onAllLogEntriesDeleted = function() { +ProxyView.prototype.onAllSourceEntriesDeleted = function() { this.clearLog_(); }; diff --git a/chrome/browser/resources/net_internals/sourceentry.js b/chrome/browser/resources/net_internals/sourceentry.js index 1a901ca..50f5321 100644 --- a/chrome/browser/resources/net_internals/sourceentry.js +++ b/chrome/browser/resources/net_internals/sourceentry.js @@ -3,128 +3,34 @@ // found in the LICENSE file. /** - * Each row in the filtered items list is backed by a SourceEntry. This - * instance contains all of the data pertaining to that row, and notifies - * its parent view (the EventsView) whenever its data changes. + * A SourceEntry gathers all log entries with the same source. * * @constructor */ -function SourceEntry(parentView, maxPreviousSourceId) { +function SourceEntry(logEntry, maxPreviousSourceId) { this.maxPreviousSourceId_ = maxPreviousSourceId; this.entries_ = []; - this.parentView_ = parentView; - this.isSelected_ = false; - this.isMatchedByFilter_ = false; + this.description_ = ''; - // Used to set CSS class for display. Must only be modified by calling - // corresponding set functions. - this.isSelected_ = false; - this.isMouseOver_ = false; // Set to true on most net errors. this.isError_ = false; + // If the first entry is a BEGIN_PHASE, set to false. // Set to true when an END_PHASE matching the first entry is encountered. this.isInactive_ = true; -} - -SourceEntry.prototype.isSelected = function() { - return this.isSelected_; -}; - -/** - * Changes |row_|'s class based on currently set flags. Clears any previous - * class set by this method. This method is needed so that some styles - * override others. - */ -SourceEntry.prototype.updateClass_ = function() { - if (!this.row_) - return; - // Each element of this list contains a property of |this| and the - // corresponding class name to set if that property is true. Entries earlier - // in the list take precedence. - var propertyNames = [ - ['isSelected_', 'selected'], - ['isMouseOver_', 'mouseover'], - ['isError_', 'error'], - ['isInactive_', 'inactive']]; - - // Loop through |propertyNames| in order, checking if each property - // is true. For the first such property found, if any, add the - // corresponding class to the SourceEntry's row. Remove classes - // that correspond to any other property. - var noStyleSet = true; - for (var i = 0; i < propertyNames.length; ++i) { - var setStyle = noStyleSet && this[propertyNames[i][0]]; - changeClassName(this.row_, propertyNames[i][1], setStyle); - if (setStyle) - noStyleSet = false; - } -}; - -SourceEntry.prototype.setInactive_ = function(isInactive) { - this.isInactive_ = isInactive; - this.updateClass_(); -}; - -SourceEntry.prototype.setError_ = function(isError) { - this.isError_ = isError; - this.updateClass_(); -}; + if (logEntry.phase == LogEventPhase.PHASE_BEGIN) + this.isInactive_ = false; -SourceEntry.prototype.setSelectedStyles = function(isSelected) { - this.isSelected_ = isSelected; - this.getSelectionCheckbox().checked = isSelected; - this.updateClass_(); -}; - -SourceEntry.prototype.setMouseoverStyle = function(isMouseOver) { - this.isMouseOver_ = isMouseOver; - this.updateClass_(); -}; - -SourceEntry.prototype.setIsMatchedByFilter = function(isMatchedByFilter) { - if (this.isMatchedByFilter() == isMatchedByFilter) - return; // No change. - - this.isMatchedByFilter_ = isMatchedByFilter; - - this.setFilterStyles(isMatchedByFilter); - - if (isMatchedByFilter) { - this.parentView_.incrementPostfilterCount(1); - } else { - this.parentView_.incrementPostfilterCount(-1); - // If we are filtering an entry away, make sure it is no longer - // part of the selection. - this.setSelected(false); - } -}; - -SourceEntry.prototype.isMatchedByFilter = function() { - return this.isMatchedByFilter_; -}; - -SourceEntry.prototype.setFilterStyles = function(isMatchedByFilter) { - // Hide rows which have been filtered away. - if (isMatchedByFilter) { - this.row_.style.display = ''; - } else { - this.row_.style.display = 'none'; - } -}; + this.update(logEntry); +} SourceEntry.prototype.update = function(logEntry) { - if (logEntry.phase == LogEventPhase.PHASE_BEGIN && - this.entries_.length == 0) { - this.setInactive_(false); - } - // Only the last event should have the same type first event, if (!this.isInactive_ && logEntry.phase == LogEventPhase.PHASE_END && logEntry.type == this.entries_[0].type) { - this.setInactive_(true); + this.isInactive_ = true; } // If we have a net error code, update |this.isError_| if apporpriate. @@ -136,7 +42,7 @@ SourceEntry.prototype.update = function(logEntry) { // Ignore error code caused by not finding an entry in the cache. if (logEntry.type != LogEventType.HTTP_CACHE_OPEN_ENTRY || netErrorCode != NetError.FAILED) { - this.setError_(true); + this.isError_ = true; } } } @@ -146,190 +52,73 @@ SourceEntry.prototype.update = function(logEntry) { var curStartEntry = this.getStartEntry_(); // If we just got the first entry for this source. - if (prevStartEntry != curStartEntry) { - if (!prevStartEntry) - this.createRow_(); - else - this.updateDescription_(); - } - - // Update filters. - var matchesFilter = this.matchesFilter(this.parentView_.currentFilter_); - this.setIsMatchedByFilter(matchesFilter); -}; - -SourceEntry.prototype.onCheckboxToggled_ = function() { - this.setSelected(this.getSelectionCheckbox().checked); -}; - -SourceEntry.prototype.matchesFilter = function(filter) { - // Safety check. - if (this.row_ == null) - return false; - - if (filter.isActive && this.isInactive_) - return false; - if (filter.isInactive && !this.isInactive_) - return false; - if (filter.isError && !this.isError_) - return false; - if (filter.isNotError && this.isError_) - return false; - - // Check source type, if needed. - if (filter.type) { - var sourceType = this.getSourceTypeString().toLowerCase(); - if (filter.type.indexOf(sourceType) == -1) - return false; - } - - // Check source ID, if needed. - if (filter.id) { - if (filter.id.indexOf(this.getSourceId() + '') == -1) - return false; - } - - if (filter.text == '') - return true; - - var filterText = filter.text; - var entryText = PrintSourceEntriesAsText(this.entries_).toLowerCase(); - - return entryText.indexOf(filterText) != -1; -}; - -SourceEntry.prototype.setSelected = function(isSelected) { - if (isSelected == this.isSelected()) - return; - - this.isSelected_ = isSelected; - - this.setSelectedStyles(isSelected); - this.parentView_.modifySelectionArray(this, isSelected); - this.parentView_.onSelectionChanged(); -}; - -SourceEntry.prototype.onClicked_ = function() { - this.parentView_.clearSelection(); - this.setSelected(true); -}; - -SourceEntry.prototype.onMouseover_ = function() { - this.setMouseoverStyle(true); -}; - -SourceEntry.prototype.onMouseout_ = function() { - this.setMouseoverStyle(false); + if (prevStartEntry != curStartEntry) + this.updateDescription_(); }; SourceEntry.prototype.updateDescription_ = function() { - this.descriptionCell_.innerHTML = ''; - addTextNode(this.descriptionCell_, this.getDescription()); -}; - -SourceEntry.prototype.createRow_ = function() { - // Create a row. - var tr = addNode(this.parentView_.tableBody_, 'tr'); - tr._id = this.getSourceId(); - tr.style.display = 'none'; - this.row_ = tr; - - var selectionCol = addNode(tr, 'td'); - var checkbox = addNode(selectionCol, 'input'); - checkbox.type = 'checkbox'; - - var idCell = addNode(tr, 'td'); - idCell.style.textAlign = 'right'; - - var typeCell = addNode(tr, 'td'); - var descriptionCell = addNode(tr, 'td'); - this.descriptionCell_ = descriptionCell; - - // Connect listeners. - checkbox.onchange = this.onCheckboxToggled_.bind(this); - - var onclick = this.onClicked_.bind(this); - idCell.onclick = onclick; - typeCell.onclick = onclick; - descriptionCell.onclick = onclick; - - tr.onmouseover = this.onMouseover_.bind(this); - tr.onmouseout = this.onMouseout_.bind(this); - - // Set the cell values to match this source's data. - if (this.getSourceId() >= 0) - addTextNode(idCell, this.getSourceId()); - else - addTextNode(idCell, '-'); - var sourceTypeString = this.getSourceTypeString(); - addTextNode(typeCell, sourceTypeString); - this.updateDescription_(); - - // Add a CSS classname specific to this source type (so CSS can specify - // different stylings for different types). - changeClassName(this.row_, 'source_' + sourceTypeString, true); - - this.updateClass_(); -}; - -/** - * Returns a description for this source log stream, which will be displayed - * in the list view. Most often this is a URL that identifies the request, - * or a hostname for a connect job, etc... - */ -SourceEntry.prototype.getDescription = function() { var e = this.getStartEntry_(); + this.description_ = ''; if (!e) - return ''; + return; if (e.source.type == LogSourceType.NONE) { // NONE is what we use for global events that aren't actually grouped // by a "source ID", so we will just stringize the event's type. - return getKeyWithValue(LogEventType, e.type); + this.description_ = getKeyWithValue(LogEventType, e.type); + return; } - if (e.params == undefined) - return ''; + if (e.params == undefined) { + return; + } - var description = ''; switch (e.source.type) { case LogSourceType.URL_REQUEST: case LogSourceType.SOCKET_STREAM: case LogSourceType.HTTP_STREAM_JOB: - description = e.params.url; + this.description_ = e.params.url; break; case LogSourceType.CONNECT_JOB: - description = e.params.group_name; + this.description_ = e.params.group_name; break; case LogSourceType.HOST_RESOLVER_IMPL_REQUEST: case LogSourceType.HOST_RESOLVER_IMPL_JOB: - description = e.params.host; + this.description_ = e.params.host; break; case LogSourceType.DISK_CACHE_ENTRY: case LogSourceType.MEMORY_CACHE_ENTRY: - description = e.params.key; + this.description_ = e.params.key; break; case LogSourceType.SPDY_SESSION: if (e.params.host) - description = e.params.host + ' (' + e.params.proxy + ')'; + this.description_ = e.params.host + ' (' + e.params.proxy + ')'; break; case LogSourceType.SOCKET: if (e.params.source_dependency != undefined) { - var connectJobSourceEntry = - this.parentView_.getSourceEntry(e.params.source_dependency.id); - if (connectJobSourceEntry) - description = connectJobSourceEntry.getDescription(); + var connectJobId = e.params.source_dependency.id; + var connectJob = g_browser.sourceTracker.getSourceEntry(connectJobId); + if (connectJob) + this.description_ = connectJob.getDescription(); } break; case LogSourceType.ASYNC_HOST_RESOLVER_REQUEST: case LogSourceType.DNS_TRANSACTION: - description = e.params.hostname; + this.description_ = e.params.hostname; break; } - if (description == undefined) - return ''; - return description; + if (this.description_ == undefined) + this.description_ = ''; +}; + +/** + * Returns a description for this source log stream, which will be displayed + * in the list view. Most often this is a URL that identifies the request, + * or a hostname for a connect job, etc... + */ +SourceEntry.prototype.getDescription = function() { + return this.description_; }; /** @@ -357,8 +146,8 @@ SourceEntry.prototype.getSourceTypeString = function() { return getKeyWithValue(LogSourceType, this.entries_[0].source.type); }; -SourceEntry.prototype.getSelectionCheckbox = function() { - return this.row_.childNodes[0].firstChild; +SourceEntry.prototype.getSourceType = function() { + return this.entries_[0].source.type; }; SourceEntry.prototype.getSourceId = function() { @@ -373,8 +162,12 @@ SourceEntry.prototype.getMaxPreviousEntrySourceId = function() { return this.maxPreviousSourceId_; }; -SourceEntry.prototype.isActive = function() { - return !this.isInactive_; +SourceEntry.prototype.isInactive = function() { + return this.isInactive_; +}; + +SourceEntry.prototype.isError = function() { + return this.isError_; }; /** @@ -383,10 +176,9 @@ SourceEntry.prototype.isActive = function() { SourceEntry.prototype.getEndTime = function() { if (!this.isInactive_) { return (new Date()).getTime(); - } - else { + } else { var endTicks = this.entries_[this.entries_.length - 1].time; - return g_browser.convertTimeTicksToDate(endTicks).getTime(); + return convertTimeTicksToDate(endTicks).getTime(); } }; @@ -397,62 +189,11 @@ SourceEntry.prototype.getEndTime = function() { */ SourceEntry.prototype.getDuration = function() { var startTicks = this.entries_[0].time; - var startTime = g_browser.convertTimeTicksToDate(startTicks).getTime(); + var startTime = convertTimeTicksToDate(startTicks).getTime(); var endTime = this.getEndTime(); return endTime - startTime; }; -/** - * Returns source ID of the entry whose row is currently above this one's. - * Returns null if no such node exists. - */ -SourceEntry.prototype.getPreviousNodeSourceId = function() { - if (!this.hasRow()) - return null; - var prevNode = this.row_.previousSibling; - if (prevNode == null) - return null; - return prevNode._id; -}; - -/** - * Returns source ID of the entry whose row is currently below this one's. - * Returns null if no such node exists. - */ -SourceEntry.prototype.getNextNodeSourceId = function() { - if (!this.hasRow()) - return null; - var nextNode = this.row_.nextSibling; - if (nextNode == null) - return null; - return nextNode._id; -}; - -SourceEntry.prototype.hasRow = function() { - return this.row_ != null; -}; - -/** - * Moves current object's row before |entry|'s row. - */ -SourceEntry.prototype.moveBefore = function(entry) { - if (this.hasRow() && entry.hasRow()) { - this.row_.parentNode.insertBefore(this.row_, entry.row_); - } -}; - -/** - * Moves current object's row after |entry|'s row. - */ -SourceEntry.prototype.moveAfter = function(entry) { - if (this.hasRow() && entry.hasRow()) { - this.row_.parentNode.insertBefore(this.row_, entry.row_.nextSibling); - } +SourceEntry.prototype.printAsText = function() { + return PrintSourceEntriesAsText(this.entries_); }; - -SourceEntry.prototype.remove = function() { - this.setSelected(false); - this.setIsMatchedByFilter(false); - this.row_.parentNode.removeChild(this.row_); -}; - diff --git a/chrome/browser/resources/net_internals/sourcerow.js b/chrome/browser/resources/net_internals/sourcerow.js new file mode 100644 index 0000000..96cdcf7 --- /dev/null +++ b/chrome/browser/resources/net_internals/sourcerow.js @@ -0,0 +1,284 @@ +// 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. + +/** + * A SourceRow represents the row corresponding to a single SourceEntry + * displayed by the EventsView. + * + * @constructor + */ +function SourceRow(parentView, sourceEntry) { + this.parentView_ = parentView; + + this.sourceEntry_ = sourceEntry; + this.isSelected_ = false; + this.isMatchedByFilter_ = false; + + // Used to set CSS class for display. Must only be modified by calling + // corresponding set functions. + this.isSelected_ = false; + this.isMouseOver_ = false; + + // Mirror sourceEntry's values, so we only update the DOM when necessary. + this.isError_ = sourceEntry.isError(); + this.isInactive_ = sourceEntry.isInactive(); + this.description_ = sourceEntry.getDescription(); + + this.createRow_(); + this.onSourceUpdated(); +} + +SourceRow.prototype.createRow_ = function() { + // Create a row. + var tr = addNode(this.parentView_.tableBody_, 'tr'); + tr._id = this.sourceEntry_.getSourceId(); + tr.style.display = 'none'; + this.row_ = tr; + + var selectionCol = addNode(tr, 'td'); + var checkbox = addNode(selectionCol, 'input'); + checkbox.type = 'checkbox'; + + var idCell = addNode(tr, 'td'); + idCell.style.textAlign = 'right'; + + var typeCell = addNode(tr, 'td'); + var descriptionCell = addNode(tr, 'td'); + this.descriptionCell_ = descriptionCell; + + // Connect listeners. + checkbox.onchange = this.onCheckboxToggled_.bind(this); + + var onclick = this.onClicked_.bind(this); + idCell.onclick = onclick; + typeCell.onclick = onclick; + descriptionCell.onclick = onclick; + + tr.onmouseover = this.onMouseover_.bind(this); + tr.onmouseout = this.onMouseout_.bind(this); + + // Set the cell values to match this source's data. + if (this.sourceEntry_.getSourceId() >= 0) { + addTextNode(idCell, this.sourceEntry_.getSourceId()); + } else { + addTextNode(idCell, '-'); + } + var sourceTypeString = this.sourceEntry_.getSourceTypeString(); + addTextNode(typeCell, sourceTypeString); + this.updateDescription_(); + + // Add a CSS classname specific to this source type (so CSS can specify + // different stylings for different types). + changeClassName(this.row_, 'source_' + sourceTypeString, true); + + this.updateClass_(); +}; + +SourceRow.prototype.onSourceUpdated = function() { + if (this.sourceEntry_.isInactive() != this.isInactive_ || + this.sourceEntry_.isError() != this.isError_) { + this.updateClass_(); + } + + if (this.description_ != this.sourceEntry_.getDescription()) + this.updateDescription_(); + + // Update filters. + var matchesFilter = this.matchesFilter(this.parentView_.currentFilter_); + this.setIsMatchedByFilter(matchesFilter); +}; + +/** + * Changes |row_|'s class based on currently set flags. Clears any previous + * class set by this method. This method is needed so that some styles + * override others. + */ +SourceRow.prototype.updateClass_ = function() { + this.isInactive_ = this.sourceEntry_.isInactive(); + this.isError_ = this.sourceEntry_.isError(); + + // Each element of this list contains a property of |this| and the + // corresponding class name to set if that property is true. Entries earlier + // in the list take precedence. + var propertyNames = [ + ['isSelected_', 'selected'], + ['isMouseOver_', 'mouseover'], + ['isError_', 'error'], + ['isInactive_', 'inactive'], + ]; + + // Loop through |propertyNames| in order, checking if each property + // is true. For the first such property found, if any, add the + // corresponding class to the SourceEntry's row. Remove classes + // that correspond to any other property. + var noStyleSet = true; + for (var i = 0; i < propertyNames.length; ++i) { + var setStyle = noStyleSet && this[propertyNames[i][0]]; + changeClassName(this.row_, propertyNames[i][1], setStyle); + if (setStyle) + noStyleSet = false; + } +}; + +SourceRow.prototype.getSourceEntry = function() { + return this.sourceEntry_; +}; + +SourceRow.prototype.setIsMatchedByFilter = function(isMatchedByFilter) { + if (this.isMatchedByFilter() == isMatchedByFilter) + return; // No change. + + this.isMatchedByFilter_ = isMatchedByFilter; + + this.setFilterStyles(isMatchedByFilter); + + if (isMatchedByFilter) { + this.parentView_.incrementPostfilterCount(1); + } else { + this.parentView_.incrementPostfilterCount(-1); + // If we are filtering an entry away, make sure it is no longer + // part of the selection. + this.setSelected(false); + } +}; + +SourceRow.prototype.isMatchedByFilter = function() { + return this.isMatchedByFilter_; +}; + +SourceRow.prototype.setFilterStyles = function(isMatchedByFilter) { + // Hide rows which have been filtered away. + if (isMatchedByFilter) { + this.row_.style.display = ''; + } else { + this.row_.style.display = 'none'; + } +}; + +SourceRow.prototype.matchesFilter = function(filter) { + if (filter.isActive && this.isInactive_) + return false; + if (filter.isInactive && !this.isInactive_) + return false; + if (filter.isError && !this.isError_) + return false; + if (filter.isNotError && this.isError_) + return false; + + // Check source type, if needed. + if (filter.type) { + var sourceType = this.sourceEntry_.getSourceTypeString().toLowerCase(); + if (filter.type.indexOf(sourceType) == -1) + return false; + } + + // Check source ID, if needed. + if (filter.id) { + if (filter.id.indexOf(this.sourceEntry_.getSourceId() + '') == -1) + return false; + } + + if (filter.text == '') + return true; + + var filterText = filter.text; + var entryText = this.sourceEntry_.PrintEntriesAsText().toLowerCase(); + + return entryText.indexOf(filterText) != -1; +}; + +SourceRow.prototype.isSelected = function() { + return this.isSelected_; +}; + +SourceRow.prototype.setSelected = function(isSelected) { + if (isSelected == this.isSelected()) + return; + + this.isSelected_ = isSelected; + + this.setSelectedStyles(isSelected); + this.parentView_.modifySelectionArray(this, isSelected); + this.parentView_.onSelectionChanged(); +}; + +SourceRow.prototype.setSelectedStyles = function(isSelected) { + this.isSelected_ = isSelected; + this.getSelectionCheckbox().checked = isSelected; + this.updateClass_(); +}; + +SourceRow.prototype.setMouseoverStyle = function(isMouseOver) { + this.isMouseOver_ = isMouseOver; + this.updateClass_(); +}; + +SourceRow.prototype.onClicked_ = function() { + this.parentView_.clearSelection(); + this.setSelected(true); +}; + +SourceRow.prototype.onMouseover_ = function() { + this.setMouseoverStyle(true); +}; + +SourceRow.prototype.onMouseout_ = function() { + this.setMouseoverStyle(false); +}; + +SourceRow.prototype.updateDescription_ = function() { + this.description_ = this.sourceEntry_.getDescription(); + this.descriptionCell_.innerHTML = ''; + addTextNode(this.descriptionCell_, this.description_); +}; + +SourceRow.prototype.onCheckboxToggled_ = function() { + this.setSelected(this.getSelectionCheckbox().checked); +}; + +SourceRow.prototype.getSelectionCheckbox = function() { + return this.row_.childNodes[0].firstChild; +}; + +/** + * Returns source ID of the entry whose row is currently above this one's. + * Returns null if no such node exists. + */ +SourceRow.prototype.getPreviousNodeSourceId = function() { + var prevNode = this.row_.previousSibling; + if (prevNode == null) + return null; + return prevNode._id; +}; + +/** + * Returns source ID of the entry whose row is currently below this one's. + * Returns null if no such node exists. + */ +SourceRow.prototype.getNextNodeSourceId = function() { + var nextNode = this.row_.nextSibling; + if (nextNode == null) + return null; + return nextNode._id; +}; + +/** + * Moves current object's row before |entry|'s row. + */ +SourceRow.prototype.moveBefore = function(entry) { + this.row_.parentNode.insertBefore(this.row_, entry.row_); +}; + +/** + * Moves current object's row after |entry|'s row. + */ +SourceRow.prototype.moveAfter = function(entry) { + this.row_.parentNode.insertBefore(this.row_, entry.row_.nextSibling); +}; + +SourceRow.prototype.remove = function() { + this.setSelected(false); + this.setIsMatchedByFilter(false); + this.row_.parentNode.removeChild(this.row_); +}; diff --git a/chrome/browser/resources/net_internals/sourcetracker.js b/chrome/browser/resources/net_internals/sourcetracker.js new file mode 100644 index 0000000..239ffd5 --- /dev/null +++ b/chrome/browser/resources/net_internals/sourcetracker.js @@ -0,0 +1,201 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * This class keeps track of all NetLog events. + * It receives events from the browser and when loading a log file, and passes + * them on to all its observers. + * + * @constructor + */ +function SourceTracker() { + this.observers_ = []; + + // True when cookies and authentication information should be removed from + // displayed events. When true, such information should be hidden from + // all pages. + this.enableSecurityStripping_ = true; + + this.clearEntries_(); +} + +/** + * Clears all log entries and SourceEntries and related state. + */ +SourceTracker.prototype.clearEntries_ = function() { + // Used for sorting entries with automatically assigned IDs. + this.maxReceivedSourceId_ = 0; + + // Next unique id to be assigned to a log entry without a source. + // Needed to simplify deletion, identify associated GUI elements, etc. + this.nextSourcelessEventId_ = -1; + + this.numPassivelyCapturedEvents_ = 0; + + // Ordered list of log entries. Needed to maintain original order when + // generating log dumps + this.capturedEvents_ = []; + + this.sourceEntries_ = {}; +}; + +/** + * Returns a list of all captured events. + */ +SourceTracker.prototype.getAllCapturedEvents = function() { + return this.capturedEvents_; +}; + +/** + * Returns a list of all SourceEntries. + */ +SourceTracker.prototype.getAllSourceEntries = function() { + return this.sourceEntries_; +}; + +/** + * Returns the number of events that were captured while we were + * listening for events. + */ +SourceTracker.prototype.getNumActivelyCapturedEvents = function() { + return this.capturedEvents_.length - this.numPassivelyCapturedEvents_; +}; + +/** + * Returns the number of events that were captured passively by the + * browser prior to when the net-internals page was started. + */ +SourceTracker.prototype.getNumPassivelyCapturedEvents = function() { + return this.numPassivelyCapturedEvents_; +}; + +/** + * Returns the specified SourceEntry. + */ +SourceTracker.prototype.getSourceEntry = function(id) { + return this.sourceEntries_[id]; +}; + +SourceTracker.prototype.onReceivedPassiveLogEntries = function(entries) { + // Due to an expected race condition, it is possible to receive actively + // captured log entries before the passively logged entries are received. + // + // When that happens, we create a copy of the actively logged entries, delete + // all entries, and, after handling all the passively logged entries, add back + // the deleted actively logged entries. + var earlyActivelyCapturedEvents = this.capturedEvents_.slice(0); + if (earlyActivelyCapturedEvents.length > 0) + this.deleteAllSourceEntries(); + + this.numPassivelyCapturedEvents_ = entries.length; + for (var i = 0; i < entries.length; ++i) + entries[i].wasPassivelyCaptured = true; + this.onReceivedLogEntries(entries); + + // Add back early actively captured events, if any. + if (earlyActivelyCapturedEvents.length) + this.onReceivedLogEntries(earlyActivelyCapturedEvents); +}; + +/** + * Sends each entry to all log observers, and updates |capturedEvents_|. + * Also assigns unique ids to log entries without a source. + */ +SourceTracker.prototype.onReceivedLogEntries = 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_; + } else if (this.maxReceivedSourceId_ < logEntry.source.id) { + this.maxReceivedSourceId_ = logEntry.source.id; + } + + var sourceEntry = this.sourceEntries_[logEntry.source.id]; + if (!sourceEntry) { + sourceEntry = new SourceEntry(logEntry, this.maxReceivedSourceId_); + this.sourceEntries_[logEntry.source.id] = sourceEntry; + } else { + sourceEntry.update(logEntry); + } + this.capturedEvents_.push(logEntry); + + // TODO(mmenke): Send a list of all updated source entries instead, + // eliminating duplicates, to reduce CPU usage. + for (var i = 0; i < this.observers_.length; ++i) + this.observers_[i].onSourceEntryUpdated(sourceEntry); + } +}; + +/** + * Deletes captured events with source IDs in |sourceEntryIds|. + */ +SourceTracker.prototype.deleteSourceEntries = function(sourceEntryIds) { + var sourceIdDict = {}; + for (var i = 0; i < sourceEntryIds.length; i++) { + sourceIdDict[sourceEntryIds[i]] = true; + delete this.sourceEntries_[sourceEntryIds[i]]; + } + + var newEventList = []; + for (var i = 0; i < this.capturedEvents_.length; ++i) { + var id = this.capturedEvents_[i].source.id; + if (id in sourceIdDict) { + if (this.capturedEvents_[i].wasPassivelyCaptured) + --this.numPassivelyCapturedEvents_; + continue; + } + newEventList.push(this.capturedEvents_[i]); + } + this.capturedEvents_ = newEventList; + + for (var i = 0; i < this.observers_.length; ++i) + this.observers_[i].onSourceEntriesDeleted(sourceEntryIds); +}; + +/** + * Deletes all captured events. + */ +SourceTracker.prototype.deleteAllSourceEntries = function() { + this.clearEntries_(); + for (var i = 0; i < this.observers_.length; ++i) + this.observers_[i].onAllSourceEntriesDeleted(); +}; + +/** + * Sets the value of |enableSecurityStripping_| and informs log observers + * of the change. + */ +SourceTracker.prototype.setSecurityStripping = + function(enableSecurityStripping) { + this.enableSecurityStripping_ = enableSecurityStripping; + for (var i = 0; i < this.observers_.length; ++i) { + if (this.observers_[i].onSecurityStrippingChanged) + this.observers_[i].onSecurityStrippingChanged(); + } +}; + +/** + * Returns whether or not cookies and authentication information should be + * displayed for events that contain them. + */ +SourceTracker.prototype.getSecurityStripping = function() { + return this.enableSecurityStripping_; +}; + +/** + * Adds a listener of log entries. |observer| will be called back when new log + * data arrives, source entries are deleted, or security stripping changes + * through: + * + * observer.onSourceEntryUpdated(sourceEntry) + * observer.deleteSourceEntries(sourceEntryIds) + * ovserver.deleteAllSourceEntries() + * observer.onSecurityStrippingChanged() + */ +SourceTracker.prototype.addObserver = function(observer) { + this.observers_.push(observer); +}; |