diff options
Diffstat (limited to 'chrome/browser/resources/net_internals')
23 files changed, 532 insertions, 436 deletions
diff --git a/chrome/browser/resources/net_internals/capture_view.js b/chrome/browser/resources/net_internals/capture_view.js index 6e127d3..29d7b7b 100644 --- a/chrome/browser/resources/net_internals/capture_view.js +++ b/chrome/browser/resources/net_internals/capture_view.js @@ -47,6 +47,9 @@ var CaptureView = (function() { g_browser.sourceTracker.addObserver(this); } + // ID for special HTML element in category_tabs.html + CaptureView.TAB_HANDLE_ID = 'tab-handle-capture'; + cr.addSingletonGetter(CaptureView); CaptureView.prototype = { diff --git a/chrome/browser/resources/net_internals/dns_view.js b/chrome/browser/resources/net_internals/dns_view.js index a80b201..092926b 100644 --- a/chrome/browser/resources/net_internals/dns_view.js +++ b/chrome/browser/resources/net_internals/dns_view.js @@ -46,6 +46,9 @@ var DnsView = (function() { g_browser.addHostResolverInfoObserver(this); } + // ID for special HTML element in category_tabs.html + DnsView.TAB_HANDLE_ID = 'tab-handle-dns'; + cr.addSingletonGetter(DnsView); DnsView.prototype = { @@ -106,7 +109,7 @@ var DnsView = (function() { } } - var expiresDate = convertTimeTicksToDate(e.expiration); + var expiresDate = timeutil.convertTimeTicksToDate(e.expiration); var expiresCell = addNode(tr, 'td'); addTextNode(expiresCell, expiresDate.toLocaleString()); } diff --git a/chrome/browser/resources/net_internals/events_view.js b/chrome/browser/resources/net_internals/events_view.js index f024fdd..a94a309 100644 --- a/chrome/browser/resources/net_internals/events_view.js +++ b/chrome/browser/resources/net_internals/events_view.js @@ -108,6 +108,9 @@ var EventsView = (function() { this.initializeSourceList_(); } + // ID for special HTML element in category_tabs.html + EventsView.TAB_HANDLE_ID = 'tab-handle-events'; + cr.addSingletonGetter(EventsView); EventsView.prototype = { diff --git a/chrome/browser/resources/net_internals/export_view.js b/chrome/browser/resources/net_internals/export_view.js index e12b662..eec148c 100644 --- a/chrome/browser/resources/net_internals/export_view.js +++ b/chrome/browser/resources/net_internals/export_view.js @@ -46,6 +46,9 @@ var ExportView = (function() { this.lastBlobURL_ = null; } + // ID for special HTML element in category_tabs.html + ExportView.TAB_HANDLE_ID = 'tab-handle-export'; + cr.addSingletonGetter(ExportView); ExportView.prototype = { @@ -110,7 +113,8 @@ var ExportView = (function() { } this.setSaveFileStatus('Preparing data...', true); - createLogDumpAsync(userComments, this.onLogDumpCreated_.bind(this)); + logutil.createLogDumpAsync(userComments, + this.onLogDumpCreated_.bind(this)); }, /** diff --git a/chrome/browser/resources/net_internals/hsts_view.js b/chrome/browser/resources/net_internals/hsts_view.js index b2bc94d0..add363f 100644 --- a/chrome/browser/resources/net_internals/hsts_view.js +++ b/chrome/browser/resources/net_internals/hsts_view.js @@ -53,6 +53,9 @@ var HSTSView = (function() { g_browser.addHSTSObserver(this); } + // ID for special HTML element in category_tabs.html + HSTSView.TAB_HANDLE_ID = 'tab-handle-hsts'; + cr.addSingletonGetter(HSTSView); HSTSView.prototype = { diff --git a/chrome/browser/resources/net_internals/http_cache_view.js b/chrome/browser/resources/net_internals/http_cache_view.js index 0bcc847..9f77473 100644 --- a/chrome/browser/resources/net_internals/http_cache_view.js +++ b/chrome/browser/resources/net_internals/http_cache_view.js @@ -30,6 +30,9 @@ var HttpCacheView = (function() { g_browser.addHttpCacheInfoObserver(this); } + // ID for special HTML element in category_tabs.html + HttpCacheView.TAB_HANDLE_ID = 'tab-handle-http-cache'; + cr.addSingletonGetter(HttpCacheView); HttpCacheView.prototype = { diff --git a/chrome/browser/resources/net_internals/http_throttling_view.js b/chrome/browser/resources/net_internals/http_throttling_view.js index e5d45f8..fae7489 100644 --- a/chrome/browser/resources/net_internals/http_throttling_view.js +++ b/chrome/browser/resources/net_internals/http_throttling_view.js @@ -30,6 +30,9 @@ var HttpThrottlingView = (function() { g_browser.addHttpThrottlingObserver(this); } + // ID for special HTML element in category_tabs.html + HttpThrottlingView.TAB_HANDLE_ID = 'tab-handle-http-throttling'; + cr.addSingletonGetter(HttpThrottlingView); HttpThrottlingView.prototype = { diff --git a/chrome/browser/resources/net_internals/import_view.js b/chrome/browser/resources/net_internals/import_view.js index 6504bd7..f2bf03c 100644 --- a/chrome/browser/resources/net_internals/import_view.js +++ b/chrome/browser/resources/net_internals/import_view.js @@ -56,6 +56,8 @@ var ImportView = (function() { this.loadedInfoUserComments_ = $(LOADED_INFO_USER_COMMENTS_ID); } + ImportView.TAB_HANDLE_ID = 'tab-handle-import'; + cr.addSingletonGetter(ImportView); ImportView.prototype = { @@ -148,7 +150,7 @@ var ImportView = (function() { }, onLoadLogFile: function(logFile, event) { - var result = loadLogFile(event.target.result, logFile.fileName); + var result = logutil.loadLogFile(event.target.result, logFile.fileName); this.setLoadFileStatus(result, false); }, diff --git a/chrome/browser/resources/net_internals/index.html b/chrome/browser/resources/net_internals/index.html index 25cb063..5037ac8 100644 --- a/chrome/browser/resources/net_internals/index.html +++ b/chrome/browser/resources/net_internals/index.html @@ -18,14 +18,14 @@ found in the LICENSE file. </head> <body id=import-view-drop-target> - <div id=statusViewId> + <div id=status-view> <!-- We use a different status bar when in capture mode vs file mode --> - <div id=statusViewForCapture> + <div id=status-view-for-capture> Recording network events... </div> - <div id=statusViewForFile style="display:none"> - Displaying log file (<span id='statusViewDumpFileName'></span>) + <div id=status-view-for-file style="display:none"> + Displaying log file (<span id=status-view-dump-file-name></span>) </div> </div> diff --git a/chrome/browser/resources/net_internals/index.js b/chrome/browser/resources/net_internals/index.js index 2a5ef5c..48e168c 100644 --- a/chrome/browser/resources/net_internals/index.js +++ b/chrome/browser/resources/net_internals/index.js @@ -13,13 +13,15 @@ <include src="hsts_view.js"/> <include src="browser_bridge.js"/> <include src="source_tracker.js"/> +<include src="resizable_vertical_split_view.js"/> <include src="main.js"/> +<include src="time_util.js"/> +<include src="log_util.js"/> <include src="dns_view.js"/> <include src="source_row.js"/> <include src="events_view.js"/> <include src="details_view.js"/> <include src="source_entry.js"/> -<include src="resizable_vertical_split_view.js"/> <include src="top_mid_bottom_view.js"/> <include src="timeline_view_painter.js"/> <include src="log_view_painter.js"/> @@ -34,5 +36,5 @@ <include src="prerender_view.js"/> document.addEventListener('DOMContentLoaded', function () { - onLoaded(); // from main.js + MainView.getInstance(); // from main.js }); diff --git a/chrome/browser/resources/net_internals/log_util.js b/chrome/browser/resources/net_internals/log_util.js new file mode 100644 index 0000000..aafffec --- /dev/null +++ b/chrome/browser/resources/net_internals/log_util.js @@ -0,0 +1,237 @@ +// 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. + +logutil = (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. + * + * TODO(eroman): Use javadoc notation for these parameters. + * + * Log dumps are just JSON objects containing five values: + * + * |userComments| User-provided notes describing what this dump file is + * about. + * |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(userComments, constants, events, polledData, tabData, + date) { + if (g_browser.sourceTracker.getSecurityStripping()) + events = events.map(stripCookiesAndLoginInfo); + + var logDump = { + 'userComments': userComments, + '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(userComments, callback, polledData) { + // Gather any tab-specific state information. + var tabData = {}; + var categoryTabSwitcher = MainView.getInstance().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(userComments, + Constants, + g_browser.sourceTracker.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. + */ + function createLogDumpAsync(userComments, callback) { + g_browser.updateAllInfo( + onUpdateAllCompleted.bind(null, userComments, 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; + } + + g_browser.receivedConstants(logDump.constants); + + // 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. + MainView.getInstance().onLoadLogFile(fileName); + + // Delete all events. This will also update all logObservers. + g_browser.sourceTracker.deleteAllSourceEntries(); + + // Inform all the views that a log file is being loaded, and pass in + // view-specific saved state, if any. + var categoryTabSwitcher = MainView.getInstance().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.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'; + } + + // 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]], + logDump.userComments)) { + 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. + */ + function loadLogFile(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); + } + + // Exports. + return { + createLogDumpAsync : createLogDumpAsync, + loadLogFile : loadLogFile + }; +})(); diff --git a/chrome/browser/resources/net_internals/log_view_painter.js b/chrome/browser/resources/net_internals/log_view_painter.js index f37e593..e0abbc1 100644 --- a/chrome/browser/resources/net_internals/log_view_painter.js +++ b/chrome/browser/resources/net_internals/log_view_painter.js @@ -38,7 +38,7 @@ function addSourceEntry_(node, sourceEntry) { var nobr2 = addNode(p2, 'nobr'); var logEntries = sourceEntry.getLogEntries(); - var startDate = convertTimeTicksToDate(logEntries[0].time); + var startDate = timeutil.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 = convertTimeTicksToDate(entries[0].orig.time); + var startDate = timeutil.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 = convertTimeTicksToDate(entry.orig.time) ; + var date = timeutil.convertTimeTicksToDate(entry.orig.time) ; var tCell = tablePrinter.addCell(date.getTime()); tCell.alignRight = true; tablePrinter.addCell(' [st='); diff --git a/chrome/browser/resources/net_internals/logs_view.js b/chrome/browser/resources/net_internals/logs_view.js index 5ce29f4..c5a58a5 100644 --- a/chrome/browser/resources/net_internals/logs_view.js +++ b/chrome/browser/resources/net_internals/logs_view.js @@ -48,6 +48,9 @@ var LogsView = (function() { this.onLogsRefresh_.bind(this)); } + // ID for special HTML element in category_tabs.html + LogsView.TAB_HANDLE_ID = 'tab-handle-logs'; + cr.addSingletonGetter(LogsView); /** diff --git a/chrome/browser/resources/net_internals/main.css b/chrome/browser/resources/net_internals/main.css index 23e9013..96971cf 100644 --- a/chrome/browser/resources/net_internals/main.css +++ b/chrome/browser/resources/net_internals/main.css @@ -101,13 +101,13 @@ table.styledTable, color: red; } -#statusViewForCapture { +#status-view-for-capture { background: red; color: #eee; padding: 4px; } -#statusViewForFile { +#status-view-for-file { background: #88c; color: #eee; padding: 4px; diff --git a/chrome/browser/resources/net_internals/main.js b/chrome/browser/resources/net_internals/main.js index a123e4c..5a242a3 100644 --- a/chrome/browser/resources/net_internals/main.js +++ b/chrome/browser/resources/net_internals/main.js @@ -3,7 +3,8 @@ // found in the LICENSE file. /** - * Dictionary of constants (initialized by browser, updated on load log). + * Dictionary of constants (Initialized soon after loading by data from browser, + * updated on load log). */ var LogEventType = null; var LogEventPhase = null; @@ -15,183 +16,225 @@ 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} */ 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. + * This class is the root view object of the page. It owns all the other + * views, and manages switching between them. It is also responsible for + * initializing the views and the BrowserBridge. */ -var convertTimeTicksToDate; +var MainView = (function() { + 'use strict'; + + // IDs for special HTML elements in index.html + var CATEGORY_TAB_HANDLES_ID = 'category-tab-handles'; + var SPLITTER_BOX_FOR_MAIN_TABS_ID = 'splitter-box-for-main-tabs'; + var STATUS_VIEW_ID = 'status-view'; + var STATUS_VIEW_FOR_CAPTURE_ID = 'status-view-for-capture'; + var STATUS_VIEW_FOR_FILE_ID = 'status-view-for-file'; + var STATUS_VIEW_DUMP_FILE_NAME_ID = 'status-view-dump-file-name'; + + // We inherit from ResizableVerticalSplitView. + var superClass = ResizableVerticalSplitView; + + /** + * Main entry point. Called once the page has loaded. + * @constructor + */ + function MainView() { + assertFirstConstructorCall(MainView); + + // This must be initialized before the tabs, so they can register as + // observers. + g_browser = BrowserBridge.getInstance(); + + // This view is a left (resizable) navigation bar. + this.categoryTabSwitcher_ = new TabSwitcherView(); + var tabs = this.categoryTabSwitcher_; + + // Call superclass's constructor, initializing the view which lets you tab + // between the different sub-views. + superClass.call(this, + new DivView(CATEGORY_TAB_HANDLES_ID), + tabs, + new DivView(SPLITTER_BOX_FOR_MAIN_TABS_ID)); + + // By default the split for the left navbar will be at 50% of the entire + // width. This is not aesthetically pleasing, so we will shrink it. + // TODO(eroman): Should set this dynamically based on the largest tab + // name rather than using a fixed width. + this.setLeftSplit(150); + + // 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. + tabs.addTab(CaptureView.TAB_HANDLE_ID, CaptureView.getInstance(), + false, true); + tabs.addTab(ExportView.TAB_HANDLE_ID, ExportView.getInstance(), + false, true); + tabs.addTab(ImportView.TAB_HANDLE_ID, ImportView.getInstance(), + false, true); + tabs.addTab(ProxyView.TAB_HANDLE_ID, ProxyView.getInstance(), + false, true); + tabs.addTab(EventsView.TAB_HANDLE_ID, EventsView.getInstance(), + false, true); + tabs.addTab(DnsView.TAB_HANDLE_ID, DnsView.getInstance(), + false, true); + tabs.addTab(SocketsView.TAB_HANDLE_ID, SocketsView.getInstance(), + false, true); + tabs.addTab(SpdyView.TAB_HANDLE_ID, SpdyView.getInstance(), false, true); + tabs.addTab(HttpCacheView.TAB_HANDLE_ID, HttpCacheView.getInstance(), + false, true); + tabs.addTab(HttpThrottlingView.TAB_HANDLE_ID, + HttpThrottlingView.getInstance(), false, true); + tabs.addTab(ServiceProvidersView.TAB_HANDLE_ID, + ServiceProvidersView.getInstance(), false, cr.isWindows); + tabs.addTab(TestView.TAB_HANDLE_ID, TestView.getInstance(), false, true); + tabs.addTab(HSTSView.TAB_HANDLE_ID, HSTSView.getInstance(), false, true); + tabs.addTab(LogsView.TAB_HANDLE_ID, LogsView.getInstance(), + false, cr.isChromeOS); + tabs.addTab(PrerenderView.TAB_HANDLE_ID, PrerenderView.getInstance(), + false, true); + + // Build a map from the anchor name of each tab handle to its "tab ID". + // We will consider navigations to the #hash as a switch tab request. + var anchorMap = {}; + var tabIds = tabs.getAllTabIds(); + for (var i = 0; i < tabIds.length; ++i) { + var aNode = $(tabIds[i]); + anchorMap[aNode.hash] = tabIds[i]; + } + // Default the empty hash to the data tab. + anchorMap['#'] = anchorMap[''] = ExportView.TAB_HANDLE_ID; -/** - * 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; + window.onhashchange = onUrlHashChange.bind(null, tabs, anchorMap); -/** - * 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; + // 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 + // log file). Below it we will position the main tabs and their content + // area. + var statusView = new DivView(STATUS_VIEW_ID); + var verticalSplitView = new VerticalSplitView(statusView, this); + var windowView = new WindowView(verticalSplitView); -// Start of annonymous namespace. -(function() { + // Trigger initial layout. + windowView.resetGeometry(); -var categoryTabSwitcher; + // Select the initial view based on the current URL. + window.onhashchange(); -/** - * Main entry point. called once the page has loaded. - */ -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. - categoryTabSwitcher = new TabSwitcherView(); - var tabSwitcherSplitView = new ResizableVerticalSplitView( - new DivView('category-tab-handles'), - categoryTabSwitcher, - new DivView('splitter-box-for-main-tabs')); - - // By default the split for the left navbar will be at 50% of the entire - // width. This is not aesthetically pleasing, so we will shrink it. - // TODO(eroman): Should set this dynamically based on the largest tab - // name rather than using a fixed width. - tabSwitcherSplitView.setLeftSplit(150); - - // Populate the main tabs. Even tabs that don't contain information for the - // running OS should be created, so they can load log dumps from other - // OSes. - categoryTabSwitcher.addTab('tab-handle-events', EventsView.getInstance(), - false, true); - categoryTabSwitcher.addTab('tab-handle-proxy', ProxyView.getInstance(), - false, true); - categoryTabSwitcher.addTab('tab-handle-dns', DnsView.getInstance(), - false, true); - categoryTabSwitcher.addTab('tab-handle-sockets', SocketsView.getInstance(), - false, true); - categoryTabSwitcher.addTab('tab-handle-spdy', SpdyView.getInstance(), - false, true); - categoryTabSwitcher.addTab('tab-handle-http-cache', - HttpCacheView.getInstance(), false, true); - categoryTabSwitcher.addTab('tab-handle-import', ImportView.getInstance(), - false, true); - categoryTabSwitcher.addTab('tab-handle-export', ExportView.getInstance(), - false, true); - categoryTabSwitcher.addTab('tab-handle-capture', CaptureView.getInstance(), - false, true); - categoryTabSwitcher.addTab('tab-handle-service-providers', - ServiceProvidersView.getInstance(), - false, cr.isWindows); - categoryTabSwitcher.addTab('tab-handle-tests', TestView.getInstance(), - false, true); - categoryTabSwitcher.addTab('tab-handle-hsts', HSTSView.getInstance(), - false, true); - categoryTabSwitcher.addTab('tab-handle-http-throttling', - HttpThrottlingView.getInstance(), false, true); - categoryTabSwitcher.addTab('tab-handle-logs', LogsView.getInstance(), false, - cr.isChromeOS); - categoryTabSwitcher.addTab('tab-handle-prerender', - PrerenderView.getInstance(), false, true); - - // Build a map from the anchor name of each tab handle to its "tab ID". - // We will consider navigations to the #hash as a switch tab request. - var anchorMap = {}; - var tabIds = categoryTabSwitcher.getAllTabIds(); - for (var i = 0; i < tabIds.length; ++i) { - var aNode = $(tabIds[i]); - anchorMap[aNode.hash] = tabIds[i]; - } - // Default the empty hash to the data tab. - anchorMap['#'] = anchorMap[''] = 'tab-handle-export'; + g_browser.addConstantsObserver(new ConstantsObserver()); - 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 - // log file). Below it we will position the main tabs and their content - // area. - var statusView = new DivView('statusViewId'); - var verticalSplitView = new VerticalSplitView(statusView, - tabSwitcherSplitView); - var windowView = new WindowView(verticalSplitView); - - // Trigger initial layout. - windowView.resetGeometry(); + // Tell the browser that we are ready to start receiving log events. + g_browser.sendReady(); + } - // Select the initial view based on the current URL. - window.onhashchange(); + cr.addSingletonGetter(MainView); + + MainView.prototype = { + // Inherit the superclass's methods. + __proto__: superClass.prototype, + + // This is exposed both so the log import/export code can enumerate all the + // tabs, and for testing. + categoryTabSwitcher: function() { + return this.categoryTabSwitcher_; + }, + + /** + * Prevents receiving/sending events to/from the browser, so loaded data + * will not be mixed with current Chrome state. Also hides any interactive + * HTML elements that send messages to the browser. Cannot be undone + * without reloading the page. + * + * @param {String} fileName The name of the log file that has been loaded. + */ + onLoadLogFile: function(fileName) { + // Swap out the status bar to indicate we have loaded from a file. + setNodeDisplay($(STATUS_VIEW_FOR_CAPTURE_ID), false); + setNodeDisplay($(STATUS_VIEW_FOR_FILE_ID), true); + + // Indicate which file is being displayed. + $(STATUS_VIEW_DUMP_FILE_NAME_ID).innerText = fileName; + + document.styleSheets[0].insertRule('.hideOnLoadLog { display: none; }'); + + g_browser.sourceTracker.setSecurityStripping(false); + g_browser.disable(); + } + }; - g_browser.addConstantsObserver(new ConstantsObserver()); - // Tell the browser that we are ready to start receiving log events. - g_browser.sendReady(); -}; + /* + * Takes the current hash in form of "#tab¶m1=value1¶m2=value2&...". + * Puts the parameters in an object, and passes the resulting object to + * |categoryTabSwitcher|. Uses tab and |anchorMap| to find a tab ID, + * which it also passes to the tab switcher. + * + * Parameters and values are decoded with decodeURIComponent(). + */ + function onUrlHashChange(categoryTabSwitcher, anchorMap) { + var parameters = window.location.hash.split('&'); + + var tabId = anchorMap[parameters[0]]; + if (!tabId) + return; + + // Split each string except the first around the '='. + var paramDict = null; + for (var i = 1; i < parameters.length; i++) { + var paramStrings = parameters[i].split('='); + if (paramStrings.length != 2) + continue; + if (paramDict == null) + paramDict = {}; + var key = decodeURIComponent(paramStrings[0]); + var value = decodeURIComponent(paramStrings[1]); + paramDict[key] = value; + } -/* - * Takes the current hash in form of "#tab¶m1=value1¶m2=value2&...". - * Puts the parameters in an object, and passes the resulting object to - * |categoryTabSwitcher|. Uses tab and |anchorMap| to find a tab ID, - * which it also passes to the tab switcher. - * - * Parameters and values are decoded with decodeURIComponent(). - */ -function onUrlHashChange(anchorMap) { - var parameters = window.location.hash.split('&'); - - var tabId = anchorMap[parameters[0]]; - if (!tabId) - return; - - // Split each string except the first around the '='. - var paramDict = null; - for (var i = 1; i < parameters.length; i++) { - var paramStrings = parameters[i].split('='); - if (paramStrings.length != 2) - continue; - if (paramDict == null) - paramDict = {}; - var key = decodeURIComponent(paramStrings[0]); - var value = decodeURIComponent(paramStrings[1]); - paramDict[key] = value; + categoryTabSwitcher.switchToTab(tabId, paramDict); } - categoryTabSwitcher.switchToTab(tabId, paramDict); -} + return MainView; +})(); + +function ConstantsObserver() {} /** - * Prevents receiving/sending events to/from the browser, so loaded data will - * not be mixed with current Chrome state. Also hides any interactive HTML - * elements that send messages to the browser. Cannot be undone without - * reloading the page. - * - * @param {String} fileName The name of the log file that has been loaded. + * Attempts to load all constants from |constants|. Returns false if one or + * more entries are missing. On failure, global dictionaries are not + * modified. */ -function onLoadLogFile(fileName) { - // Swap out the status bar to indicate we have loaded from a file. - setNodeDisplay($('statusViewForCapture'), false); - setNodeDisplay($('statusViewForFile'), true); +ConstantsObserver.prototype.onReceivedConstants = + function(receivedConstants) { + if (!areValidConstants(receivedConstants)) + return false; - // Indicate which file is being displayed. - $('statusViewDumpFileName').innerText = fileName; + Constants = receivedConstants; - document.styleSheets[0].insertRule('.hideOnLoadLog { display: none; }'); + LogEventType = Constants.logEventTypes; + ClientInfo = Constants.clientInfo; + LogEventPhase = Constants.logEventPhase; + LogSourceType = Constants.logSourceType; + LogLevelType = Constants.logLevelType; + LoadFlag = Constants.loadFlag; + NetError = Constants.netError; + AddressFamily = Constants.addressFamily; - g_browser.sourceTracker.setSecurityStripping(false); - g_browser.disable(); -} + timeutil.setTimeTickOffset(Constants.timeTickOffset); +}; /** - * Returns true if |constants| appears to be a valid constants object. + * Returns true if it's given a valid-looking constants object. */ function areValidConstants(receivedConstants) { return typeof(receivedConstants) == 'object' && @@ -206,263 +249,3 @@ function areValidConstants(receivedConstants) { typeof(receivedConstants.timeTickOffset) == 'string' && typeof(receivedConstants.logFormatVersion) == 'number'; } - -/** - * Dictionary of all constants, used for saving log files. - */ -var constants = null; - -/** - * Offset needed to convert event times to Date objects. - */ -var timeTickOffset = 0; - -function ConstantsObserver() {} - -/** - * Attempts to load all constants from |constants|. Returns false if one or - * more entries are missing. On failure, global dictionaries are not modified. - */ -ConstantsObserver.prototype.onReceivedConstants = function(receivedConstants) { - if (!areValidConstants(receivedConstants)) - return false; - - constants = receivedConstants; - - LogEventType = constants.logEventTypes; - ClientInfo = constants.clientInfo; - LogEventPhase = constants.logEventPhase; - LogSourceType = constants.logSourceType; - LogLevelType = constants.logLevelType; - LoadFlag = constants.loadFlag; - NetError = constants.netError; - AddressFamily = constants.addressFamily; - - // Note that the subtraction by 0 is to cast to a number (probably a float - // since the numbers are big). - timeTickOffset = constants.timeTickOffset - 0; - - return true; -}; - -convertTimeTicksToDate = function(timeTicks) { - var timeStampMs = timeTickOffset + (timeTicks - 0); - return new Date(timeStampMs); -}; - -/** - * 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. - * - * TODO(eroman): Use javadoc notation for these parameters. - * - * Log dumps are just JSON objects containing four values: - * - * |userComments| User-provided notes describing what this dump file is about. - * |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(userComments, constants, events, polledData, tabData, - date) { - if (g_browser.sourceTracker.getSecurityStripping()) - events = events.map(stripCookiesAndLoginInfo); - - var logDump = { - 'userComments': userComments, - '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(userComments, 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(); - } - - var logDump = createLogDump(userComments, - constants, - g_browser.sourceTracker.getAllCapturedEvents(), - polledData, - tabData, - (new Date()).toLocaleString()); - callback(JSON.stringify(logDump, null, ' ')); -} - -createLogDumpAsync = function(userComments, callback) { - g_browser.updateAllInfo( - onUpdateAllCompleted.bind(null, userComments, 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.'; - - g_browser.receivedConstants(logDump.constants); - - // 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); - - // Delete all events. This will also update all logObservers. - g_browser.sourceTracker.deleteAllSourceEntries(); - - // 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]]); - } - - // 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.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'; - } - - // 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]], - logDump.userComments)) { - showView = true; - } - } catch (error) { - } - categoryTabSwitcher.showTabHandleNode(tabIds[i], showView); - } - - return errorString + 'Log loaded.'; -} - -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/prerender_view.js b/chrome/browser/resources/net_internals/prerender_view.js index 44eafd8..be9d793 100644 --- a/chrome/browser/resources/net_internals/prerender_view.js +++ b/chrome/browser/resources/net_internals/prerender_view.js @@ -32,6 +32,9 @@ var PrerenderView = (function() { this.prerenderActiveDiv_ = $(ACTIVE_DIV_ID); } + // ID for special HTML element in category_tabs.html + PrerenderView.TAB_HANDLE_ID = 'tab-handle-prerender'; + cr.addSingletonGetter(PrerenderView); PrerenderView.prototype = { diff --git a/chrome/browser/resources/net_internals/proxy_view.js b/chrome/browser/resources/net_internals/proxy_view.js index 2d21b70..de657bb7 100644 --- a/chrome/browser/resources/net_internals/proxy_view.js +++ b/chrome/browser/resources/net_internals/proxy_view.js @@ -49,6 +49,9 @@ var ProxyView = (function() { g_browser.sourceTracker.addObserver(this); } + // ID for special HTML element in category_tabs.html + ProxyView.TAB_HANDLE_ID = 'tab-handle-proxy'; + cr.addSingletonGetter(ProxyView); ProxyView.prototype = { @@ -93,9 +96,6 @@ var ProxyView = (function() { var original = proxySettings.original; var effective = proxySettings.effective; - if (!original || !effective) - return false; - $(ORIGINAL_SETTINGS_DIV_ID).innerText = proxySettingsToString(original); $(EFFECTIVE_SETTINGS_DIV_ID).innerText = proxySettingsToString(effective); return true; @@ -110,7 +110,7 @@ var ProxyView = (function() { // Add a table row for each bad proxy entry. for (var i = 0; i < badProxies.length; ++i) { var entry = badProxies[i]; - var badUntilDate = convertTimeTicksToDate(entry.bad_until); + var badUntilDate = timeutil.convertTimeTicksToDate(entry.bad_until); var tr = addNode($(BAD_PROXIES_TBODY_ID), 'tr'); diff --git a/chrome/browser/resources/net_internals/service_providers_view.js b/chrome/browser/resources/net_internals/service_providers_view.js index d7c561d..9336a89 100644 --- a/chrome/browser/resources/net_internals/service_providers_view.js +++ b/chrome/browser/resources/net_internals/service_providers_view.js @@ -13,8 +13,6 @@ var ServiceProvidersView = (function() { 'use strict'; - var TAB_ID = 'tab-handle-service-providers'; - // IDs for special HTML elements in service_providers_view.html var MAIN_BOX_ID = 'service-providers-view-tab-content'; var SERVICE_PROVIDERS_TBODY_ID = 'service-providers-view-tbody'; @@ -33,15 +31,15 @@ var ServiceProvidersView = (function() { // Call superclass's constructor. superClass.call(this, MAIN_BOX_ID); - var tab = $(TAB_ID); - setNodeDisplay(tab, true); - this.serviceProvidersTbody_ = $(SERVICE_PROVIDERS_TBODY_ID); this.namespaceProvidersTbody_ = $(NAMESPACE_PROVIDERS_TBODY_ID); g_browser.addServiceProvidersObserver(this); } + // ID for special HTML element in category_tabs.html + ServiceProvidersView.TAB_HANDLE_ID = 'tab-handle-service-providers'; + cr.addSingletonGetter(ServiceProvidersView); ServiceProvidersView.prototype = { diff --git a/chrome/browser/resources/net_internals/sockets_view.js b/chrome/browser/resources/net_internals/sockets_view.js index 386cadd..53c52ad 100644 --- a/chrome/browser/resources/net_internals/sockets_view.js +++ b/chrome/browser/resources/net_internals/sockets_view.js @@ -42,6 +42,9 @@ var SocketsView = (function() { flushSocketsButton.onclick = this.flushSocketPools.bind(this); } + // ID for special HTML element in category_tabs.html + SocketsView.TAB_HANDLE_ID = 'tab-handle-sockets'; + cr.addSingletonGetter(SocketsView); SocketsView.prototype = { diff --git a/chrome/browser/resources/net_internals/source_entry.js b/chrome/browser/resources/net_internals/source_entry.js index 50f5321..9a151e6 100644 --- a/chrome/browser/resources/net_internals/source_entry.js +++ b/chrome/browser/resources/net_internals/source_entry.js @@ -178,7 +178,7 @@ SourceEntry.prototype.getEndTime = function() { return (new Date()).getTime(); } else { var endTicks = this.entries_[this.entries_.length - 1].time; - return convertTimeTicksToDate(endTicks).getTime(); + return timeutil.convertTimeTicksToDate(endTicks).getTime(); } }; @@ -189,7 +189,7 @@ SourceEntry.prototype.getEndTime = function() { */ SourceEntry.prototype.getDuration = function() { var startTicks = this.entries_[0].time; - var startTime = convertTimeTicksToDate(startTicks).getTime(); + var startTime = timeutil.convertTimeTicksToDate(startTicks).getTime(); var endTime = this.getEndTime(); return endTime - startTime; }; diff --git a/chrome/browser/resources/net_internals/spdy_view.js b/chrome/browser/resources/net_internals/spdy_view.js index b9267ab..d4f2fa0 100644 --- a/chrome/browser/resources/net_internals/spdy_view.js +++ b/chrome/browser/resources/net_internals/spdy_view.js @@ -51,6 +51,9 @@ var SpdyView = (function() { this.spdySessionDiv_ = $(SESSION_DIV_ID); } + // ID for special HTML element in category_tabs.html + SpdyView.TAB_HANDLE_ID = 'tab-handle-spdy'; + cr.addSingletonGetter(SpdyView); SpdyView.prototype = { diff --git a/chrome/browser/resources/net_internals/test_view.js b/chrome/browser/resources/net_internals/test_view.js index df71808..d3251dd 100644 --- a/chrome/browser/resources/net_internals/test_view.js +++ b/chrome/browser/resources/net_internals/test_view.js @@ -41,6 +41,9 @@ var TestView = (function() { g_browser.addConnectionTestsObserver(this); } + // ID for special HTML element in category_tabs.html + TestView.TAB_HANDLE_ID = 'tab-handle-tests'; + cr.addSingletonGetter(TestView); TestView.prototype = { diff --git a/chrome/browser/resources/net_internals/time_util.js b/chrome/browser/resources/net_internals/time_util.js new file mode 100644 index 0000000..78cd379 --- /dev/null +++ b/chrome/browser/resources/net_internals/time_util.js @@ -0,0 +1,37 @@ +// 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 timeutil = (function() { + /** + * Offset needed to convert event times to Date objects. + * Updated whenever constants are loaded. + */ + var timeTickOffset = 0; + + /** + * Sets the offset used to convert tick counts to dates. + */ + function setTimeTickOffset(offset) { + // Note that the subtraction by 0 is to cast to a number (probably a float + // since the numbers are big). + timeTickOffset = offset - 0; + } + + /** + * 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. + */ + function convertTimeTicksToDate(timeTicks) { + var timeStampMs = timeTickOffset + (timeTicks - 0); + return new Date(timeStampMs); + } + + return { + setTimeTickOffset: setTimeTickOffset, + convertTimeTicksToDate: convertTimeTicksToDate + }; +})(); |