diff options
-rw-r--r-- | chrome/browser/resources/net_internals/dataview.js | 194 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/index.html | 10 | ||||
-rw-r--r-- | chrome/browser/resources/net_internals/main.js | 92 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 1 |
4 files changed, 271 insertions, 26 deletions
diff --git a/chrome/browser/resources/net_internals/dataview.js b/chrome/browser/resources/net_internals/dataview.js new file mode 100644 index 0000000..778ea9f --- /dev/null +++ b/chrome/browser/resources/net_internals/dataview.js @@ -0,0 +1,194 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * This view displays options for importing/exporting the captured data. Its + * primarily usefulness is to allow users to copy-paste their data in an easy + * to read format for bug reports. + * + * - Has a button to generate a text report. + * - Has a button to generate a raw JSON dump (most useful for testing). + * + * @constructor + */ +function DataView(mainBoxId, exportJsonButtonId, exportTextButtonId) { + DivView.call(this, mainBoxId); + + var exportJsonButton = document.getElementById(exportJsonButtonId); + var exportTextButton = document.getElementById(exportTextButtonId); + + exportJsonButton.onclick = this.onExportToJSON_.bind(this); + exportTextButton.onclick = this.onExportToText_.bind(this); +} + +inherits(DataView, DivView); + +/** + * Very simple output format which directly displays the internally captured + * data in its javascript format. + */ +DataView.prototype.onExportToJSON_ = function() { + // Format some text describing the data we have captured. + var text = []; // Lines of text. + + text.push('//----------------------------------------------'); + text.push('// Proxy settings'); + text.push('//----------------------------------------------'); + text.push(''); + + text.push('proxySettings = ' + + JSON.stringify(g_browser.proxySettings_.currentData_)); + + text.push(''); + text.push('//----------------------------------------------'); + text.push('// Bad proxies'); + text.push('//----------------------------------------------'); + text.push(''); + + text.push('badProxies = ' + + JSON.stringify(g_browser.badProxies_.currentData_)); + + text.push(''); + text.push('//----------------------------------------------'); + text.push('// Host resolver cache'); + text.push('//----------------------------------------------'); + text.push(''); + + text.push('hostResolverCache = ' + + JSON.stringify(g_browser.hostResolverCache_.currentData_)); + + text.push(''); + text.push('//----------------------------------------------'); + text.push('// Passively captured events'); + text.push('//----------------------------------------------'); + text.push(''); + + this.appendPrettyPrintedJsonArray_('passive', + g_browser.getAllPassivelyCapturedEvents(), + text); + + text.push(''); + text.push('//----------------------------------------------'); + text.push('// Actively captured events'); + text.push('//----------------------------------------------'); + text.push(''); + + this.appendPrettyPrintedJsonArray_('active', + g_browser.getAllActivelyCapturedEvents(), + text); + + // Open a new window to display this text. + this.displayPopupWindow_(text.join('\n')); +}; + +/** + * Presents the captured data as formatted text. + */ +DataView.prototype.onExportToText_ = function() { + var text = []; + + // Print some basic information about our environment. + text.push('Data exported on: ' + (new Date()).toLocaleString()); + text.push(''); + text.push('Number of passively captured events: ' + + g_browser.getAllPassivelyCapturedEvents().length); + text.push('Number of actively captured events: ' + + g_browser.getAllActivelyCapturedEvents().length); + text.push('Timetick to timestamp offset: ' + g_browser.timeTickOffset_); + text.push(''); + // TODO(eroman): fill this with proper values. + text.push('Chrome version: ' + 'TODO'); + text.push('Command line switches: ' + 'TODO'); + + text.push(''); + text.push('----------------------------------------------'); + text.push(' Proxy settings'); + text.push('----------------------------------------------'); + text.push(''); + + text.push(g_browser.proxySettings_.currentData_); + + text.push(''); + text.push('----------------------------------------------'); + text.push(' Bad proxies cache'); + text.push('----------------------------------------------'); + text.push(''); + + var badProxiesList = g_browser.badProxies_.currentData_; + if (badProxiesList.length == 0) { + text.push('None'); + } else { + this.appendPrettyPrintedTable_(badProxiesList, text); + } + + text.push(''); + text.push('----------------------------------------------'); + text.push(' Host resolver cache'); + text.push('----------------------------------------------'); + text.push(''); + + var hostResolverCache = g_browser.hostResolverCache_.currentData_; + + text.push('Capcity: ' + hostResolverCache.capacity); + text.push('Time to live for successful resolves (ms): ' + + hostResolverCache.ttl_success_ms); + text.push('Time to live for failed resolves (ms): ' + + hostResolverCache.ttl_failure_ms); + + if (hostResolverCache.entries.length > 0) { + text.push(''); + this.appendPrettyPrintedTable_(hostResolverCache.entries, text); + } + + text.push(''); + text.push('----------------------------------------------'); + text.push(' URL requests'); + text.push('----------------------------------------------'); + text.push(''); + + // TODO(eroman): Output the per-request events. + text.push('TODO'); + + // Open a new window to display this text. + this.displayPopupWindow_(text.join('\n')); +}; + +/** + * Helper function to open a new window and display |text| in it. + */ +DataView.prototype.displayPopupWindow_ = function(text) { + // Note that we use a data:URL because the chrome:// URL scheme doesn't + // allow us to mutate any new windows we open. + dataUrl = 'data:text/plain,' + encodeURIComponent(text); + window.open(dataUrl, '_blank'); +}; + +/** + * Stringifies |arrayData| as JSON-like output, and appends it to the + * line buffer |out|. + */ +DataView.prototype.appendPrettyPrintedJsonArray_ = function(name, + arrayData, + out) { + out.push(name + ' = [];'); + for (var i = 0; i < arrayData.length; ++i) { + out.push(name + '[' + i + '] = ' + JSON.stringify(arrayData[i]) + ';'); + } +}; + +/** + * Stringifies |arrayData| to formatted table output, and appends it to the + * line buffer |out|. + */ +DataView.prototype.appendPrettyPrintedTable_ = function(arrayData, out) { + for (var i = 0; i < arrayData.length; ++i) { + var e = arrayData[i]; + var eString = '[' + i + ']: '; + for (var key in e) { + eString += key + "=" + e[key] + "; "; + } + out.push(eString); + } +}; + diff --git a/chrome/browser/resources/net_internals/index.html b/chrome/browser/resources/net_internals/index.html index cd77f62..be62e42 100644 --- a/chrome/browser/resources/net_internals/index.html +++ b/chrome/browser/resources/net_internals/index.html @@ -9,6 +9,7 @@ found in the LICENSE file. <script src="util.js"></script> <script src="view.js"></script> <script src="tabswitcherview.js"></script> + <script src="dataview.js"></script> <script src="main.js"></script> <script src="dnsview.js"></script> <script src="requestsview.js"></script> @@ -30,6 +31,7 @@ found in the LICENSE file. <li><a href="#dns" id=dnsTab>DNS</a></li> <li><a href="#sockets" id=socketsTab>Sockets</a></li> <li><a href="#httpCache" id=httpCacheTab>HTTP Cache</a></li> + <li><a href="#data" id=dataTab>Data</a></li> </ul> <div style="clear: both;"></div> </div> @@ -85,6 +87,14 @@ found in the LICENSE file. <!-- Sections TODO --> <div id=socketsTabContent>TODO: display socket information (outstanding connect jobs)</div> <div id=httpCacheTabContent>TODO: display http cache information (disk cache navigator)</div> + <!-- Import/Export data --> + <div id=dataTabContent> + <br/> + <button id=exportToText><b>Click here to generate bug report data</b><br/>(Copy paste the result and attach to bug)</button> + <br/> + <br/> + <button id=exportToJson>Export data to JSON</button> + </div> <!-- ================= Requests view =================== --> diff --git a/chrome/browser/resources/net_internals/main.js b/chrome/browser/resources/net_internals/main.js index f695c3c..d06e5e9 100644 --- a/chrome/browser/resources/net_internals/main.js +++ b/chrome/browser/resources/net_internals/main.js @@ -57,6 +57,10 @@ function onLoaded() { "hostResolverCacheTTLSuccess", "hostResolverCacheTTLFailure"); + // Create a view which will display import/export options to control the + // captured data. + var dataView = new DataView("dataTabContent", "exportToJson", "exportToText"); + // Create a view which lets you tab between the different sub-views. var categoryTabSwitcher = new TabSwitcherView(new DivView('categoryTabHandles')); @@ -69,6 +73,7 @@ function onLoaded() { false); categoryTabSwitcher.addTab('httpCacheTab', new DivView('httpCacheTabContent'), false); + categoryTabSwitcher.addTab('dataTab', dataView, false); // Build a map from the anchor name of each tab handle to its "tab ID". // We will consider navigations to the #hash as a switch tab request. @@ -109,14 +114,15 @@ function onLoaded() { function BrowserBridge() { // List of observers for various bits of browser state. this.logObservers_ = []; - this.proxySettingsObservers_ = []; - this.badProxiesObservers_ = []; - this.hostResolverCacheObservers_ = []; - - // Map from observer method name (i.e. 'onProxySettingsChanged', - // 'onBadProxiesChanged') to the previously received data for that type. Used - // to tell if the data has actually changed since we last polled it. - this.prevPollData_ = {}; + this.proxySettings_ = new PollableDataHelper('onProxySettingsChanged'); + this.badProxies_ = new PollableDataHelper('onBadProxiesChanged'); + this.hostResolverCache_ = new PollableDataHelper('onHostResolverCacheChanged'); + + // Cache of the data received. + // TODO(eroman): the controls to clear data in the "Requests" tab should be + // affecting this as well. + this.passivelyCapturedEvents_ = []; + this.activelyCapturedEvents_ = []; } /** @@ -169,7 +175,11 @@ BrowserBridge.prototype.sendClearHostResolverCache = function() { // Messages received from the browser //------------------------------------------------------------------------------ -BrowserBridge.prototype.receivedLogEntry = function(logEntry) { +BrowserBridge.prototype.receivedLogEntry = function(logEntry, + wasCapturedPassively) { + if (!wasCapturedPassively) { + this.activelyCapturedEvents_.push(logEntry); + } for (var i = 0; i < this.logObservers_.length; ++i) this.logObservers_[i].onLogEntryAdded(logEntry); }; @@ -193,25 +203,23 @@ BrowserBridge.prototype.receivedTimeTickOffset = function(timeTickOffset) { }; BrowserBridge.prototype.receivedProxySettings = function(proxySettings) { - this.dispatchToObserversFromPoll_( - this.proxySettingsObservers_, 'onProxySettingsChanged', proxySettings); + this.proxySettings_.update(proxySettings); }; BrowserBridge.prototype.receivedBadProxies = function(badProxies) { - this.dispatchToObserversFromPoll_( - this.badProxiesObservers_, 'onBadProxiesChanged', badProxies); + this.badProxies_.update(badProxies); }; BrowserBridge.prototype.receivedHostResolverCache = function(hostResolverCache) { - this.dispatchToObserversFromPoll_( - this.hostResolverCacheObservers_, 'onHostResolverCacheChanged', - hostResolverCache); + this.hostResolverCache_.update(hostResolverCache); }; BrowserBridge.prototype.receivedPassiveLogEntries = function(entries) { + this.passivelyCapturedEvents_ = + this.passivelyCapturedEvents_.concat(entries); for (var i = 0; i < entries.length; ++i) - this.receivedLogEntry(entries[i]); + this.receivedLogEntry(entries[i], true); }; //------------------------------------------------------------------------------ @@ -236,7 +244,7 @@ BrowserBridge.prototype.addLogObserver = function(observer) { * TODO(eroman): send a dictionary instead. */ BrowserBridge.prototype.addProxySettingsObserver = function(observer) { - this.proxySettingsObservers_.push(observer); + this.proxySettings_.addObserver(observer); }; /** @@ -251,7 +259,7 @@ BrowserBridge.prototype.addProxySettingsObserver = function(observer) { * bad. Note the time is in time ticks. */ BrowserBridge.prototype.addBadProxiesObsever = function(observer) { - this.badProxiesObservers_.push(observer); + this.badProxies_.addObserver(observer); }; /** @@ -261,7 +269,7 @@ BrowserBridge.prototype.addBadProxiesObsever = function(observer) { * observer.onHostResolverCacheChanged(hostResolverCache) */ BrowserBridge.prototype.addHostResolverCacheObserver = function(observer) { - this.hostResolverCacheObservers_.push(observer); + this.hostResolverCache_.addObserver(observer); }; /** @@ -280,6 +288,22 @@ BrowserBridge.prototype.convertTimeTicksToDate = function(timeTicks) { return d; }; +/** + * Returns a list of all the events that were captured while we were + * listening for events. + */ +BrowserBridge.prototype.getAllActivelyCapturedEvents = function() { + return this.activelyCapturedEvents_; +}; + +/** + * Returns a list of all the events that were captured passively by the + * browser prior to when the net-internals page was started. + */ +BrowserBridge.prototype.getAllPassivelyCapturedEvents = function() { + return this.passivelyCapturedEvents_; +}; + BrowserBridge.prototype.doPolling_ = function() { // TODO(eroman): Optimize this by using a separate polling frequency for the // data consumed by the currently active view. Everything else can be on a low @@ -290,21 +314,37 @@ BrowserBridge.prototype.doPolling_ = function() { }; /** + * 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. + * @constructor + */ +function PollableDataHelper(observerMethodName) { + this.observerMethodName_ = observerMethodName; + this.observers_ = []; +} + +PollableDataHelper.prototype.addObserver = function(observer) { + this.observers_.push(observer); +}; + +/** * Helper function to handle calling all the observers, but ONLY if the data has * actually changed since last time. This is used for data we received from * browser on a poll loop. */ -BrowserBridge.prototype.dispatchToObserversFromPoll_ = function( - observerList, method, data) { - var prevData = this.prevPollData_[method]; +PollableDataHelper.prototype.update = function(data) { + var prevData = this.currentData_; // If the data hasn't changed since last time, no need to notify observers. if (prevData && JSON.stringify(prevData) == JSON.stringify(data)) return; - this.prevPollData_[method] = data; + this.currentData_ = data; // Ok, notify the observers of the change. - for (var i = 0; i < observerList.length; ++i) - observerList[i][method](data); + for (var i = 0; i < this.observers_.length; ++i) + var observer = this.observers_[i]; + observer[this.observerMethodName_](data); }; diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index a47c61b..3a59ea4 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -3333,6 +3333,7 @@ { 'destination': '<(PRODUCT_DIR)/resources/net_internals', 'files': [ + 'browser/resources/net_internals/dataview.js', 'browser/resources/net_internals/detailsview.js', 'browser/resources/net_internals/dnsview.js', 'browser/resources/net_internals/index.html', |