Index: netlog_viewer/log_util.js |
diff --git a/netlog_viewer/log_util.js b/netlog_viewer/log_util.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d360fdab51f6d6adb16d0427569fefd7bc2e43e5 |
--- /dev/null |
+++ b/netlog_viewer/log_util.js |
@@ -0,0 +1,310 @@ |
+// Copyright (c) 2012 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. |
+ |
+log_util = (function() { |
+ 'use strict'; |
+ |
+ /** |
+ * 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, |date| is the time the dump was |
+ * created, as a formatted string, and |privacyStripping| is whether or not |
+ * private information should be removed from the generated dump. |
+ * |
+ * Returns the new log dump as an object. Resulting object may have a null |
+ * |numericDate|. |
+ * |
+ * 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, |
+ numericDate, privacyStripping) { |
+ if (privacyStripping) |
+ events = events.map(stripPrivacyInfo); |
+ |
+ 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 (numericDate && constants.clientInfo) { |
+ constants.clientInfo.numericDate = numericDate; |
+ } |
+ |
+ return logDump; |
+ } |
+ |
+ /** |
+ * Returns a new log dump created using the polled data and date from the |
+ * |oldLogDump|. The other parts of the log dump come from current |
+ * net-internals state. |
+ */ |
+ function createUpdatedLogDump(userComments, oldLogDump, privacyStripping) { |
+ var numericDate = null; |
+ if (oldLogDump.constants.clientInfo && |
+ oldLogDump.constants.clientInfo.numericDate) { |
+ numericDate = oldLogDump.constants.clientInfo.numericDate; |
+ } |
+ var logDump = createLogDump( |
+ userComments, |
+ Constants, |
+ EventsTracker.getInstance().getAllCapturedEvents(), |
+ oldLogDump.polledData, |
+ getTabData_(), |
+ numericDate, |
+ privacyStripping); |
+ return JSON.stringify(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, privacyStripping, |
+ polledData) { |
+ var logDump = createLogDump( |
+ userComments, |
+ Constants, |
+ EventsTracker.getInstance().getAllCapturedEvents(), |
+ polledData, |
+ getTabData_(), |
+ timeutil.getCurrentTime(), |
+ privacyStripping); |
+ callback(JSON.stringify(logDump)); |
+ } |
+ |
+ /** |
+ * 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, privacyStripping) { |
+ g_browser.updateAllInfo( |
+ onUpdateAllCompleted.bind(null, userComments, callback, |
+ privacyStripping)); |
+ } |
+ |
+ /** |
+ * Gather any tab-specific state information prior to creating a log dump. |
+ */ |
+ function getTabData_() { |
+ var tabData = {}; |
+ var tabSwitcher = MainView.getInstance().tabSwitcher(); |
+ var tabIdToView = tabSwitcher.getAllTabViews(); |
+ for (var tabId in tabIdToView) { |
+ var view = tabIdToView[tabId]; |
+ if (view.saveState) |
+ tabData[tabId] = view.saveState(); |
+ } |
+ } |
+ |
+ /** |
+ * Loads a full log dump. Returns a string containing a log of the load. |
+ * |opt_fileName| should always be given when loading from a file, instead of |
+ * from a log dump generated in-memory. |
+ * 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, opt_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 = {}; |
+ |
+ var kSupportedLogFormatVersion = 1; |
+ |
+ if (logDump.constants.logFormatVersion != kSupportedLogFormatVersion) { |
+ return 'Unable to load different log version.' + |
+ ' Found ' + logDump.constants.logFormatVersion + |
+ ', Expected ' + kSupportedLogFormatVersion; |
+ } |
+ |
+ g_browser.receivedConstants(logDump.constants); |
+ |
+ // 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 validEvents = []; |
+ var numDeprecatedPassiveEvents = 0; |
+ 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' && |
+ typeof EventTypeNames[event.type] == 'string' && |
+ typeof EventSourceTypeNames[event.source.type] == 'string' && |
+ getKeyWithValue(EventPhase, event.phase) != '?') { |
+ if (event.wasPassivelyCaptured) { |
+ // NOTE: Up until Chrome 18, log dumps included "passively captured" |
+ // events. These are no longer supported, so skip past them |
+ // to avoid confusing the rest of the code. |
+ numDeprecatedPassiveEvents++; |
+ continue; |
+ } |
+ validEvents.push(event); |
+ } |
+ } |
+ |
+ // Make sure the loaded log contained an export date. If not we will |
+ // synthesize one. This can legitimately happen for dump files created |
+ // via command line flag, or for older dump formats (before Chrome 17). |
+ if (typeof logDump.constants.clientInfo.numericDate != 'number') { |
+ errorString += 'The log file is missing clientInfo.numericDate.\n'; |
+ |
+ if (validEvents.length > 0) { |
+ errorString += |
+ 'Synthesizing export date as time of last event captured.\n'; |
+ var lastEvent = validEvents[validEvents.length - 1]; |
+ ClientInfo.numericDate = |
+ timeutil.convertTimeTicksToDate(lastEvent.time).getTime(); |
+ } else { |
+ errorString += 'Can\'t guess export date!\n'; |
+ ClientInfo.numericDate = 0; |
+ } |
+ } |
+ |
+ // 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().onLoadLog(opt_fileName); |
+ |
+ // Delete all events. This will also update all logObservers. |
+ EventsTracker.getInstance().deleteAllLogEntries(); |
+ |
+ // Inform all the views that a log file is being loaded, and pass in |
+ // view-specific saved state, if any. |
+ var tabSwitcher = MainView.getInstance().tabSwitcher(); |
+ var tabIdToView = tabSwitcher.getAllTabViews(); |
+ for (var tabId in tabIdToView) { |
+ var view = tabIdToView[tabId]; |
+ view.onLoadLogStart(logDump.polledData, logDump.tabData[tabId]); |
+ } |
+ EventsTracker.getInstance().addLogEntries(validEvents); |
+ |
+ var numInvalidEvents = logDump.events.length - |
+ (validEvents.length + numDeprecatedPassiveEvents); |
+ if (numInvalidEvents > 0) { |
+ errorString += 'Unable to load ' + numInvalidEvents + |
+ ' events, due to invalid data.\n\n'; |
+ } |
+ |
+ if (numDeprecatedPassiveEvents > 0) { |
+ errorString += 'Discarded ' + numDeprecatedPassiveEvents + |
+ ' passively collected events. Use an older version of Chrome to' + |
+ ' load this dump if you want to see them.\n\n'; |
+ } |
+ |
+ // Update all views with data from the file. Show only those views which |
+ // successfully load the data. |
+ for (var tabId in tabIdToView) { |
+ var view = tabIdToView[tabId]; |
+ 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[tabId], |
+ logDump)) { |
+ showView = true; |
+ } |
+ } catch (error) { |
+ errorString += 'Caught error while calling onLoadLogFinish: ' + |
+ error + '\n\n'; |
+ } |
+ tabSwitcher.showTabLink(tabId, 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; |
+ var errorString = ''; |
+ 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) + ']}'); |
+ errorString += 'Log file truncated. Events may be missing.\n'; |
+ } |
+ } |
+ catch (error2) { |
+ } |
+ } |
+ |
+ if (!parsedDump) |
+ return 'Unable to parse log dump as JSON file.'; |
+ return errorString + loadLogDump(parsedDump, fileName); |
+ } |
+ |
+ // Exports. |
+ return { |
+ createUpdatedLogDump: createUpdatedLogDump, |
+ createLogDumpAsync: createLogDumpAsync, |
+ loadLogFile: loadLogFile |
+ }; |
+})(); |
+ |