Home | History | Annotate | Download | only in net_internals
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 log_util = (function() {
      6   'use strict';
      7 
      8   /**
      9    * Creates a new log dump.  |events| is a list of all events, |polledData| is
     10    * an object containing the results of each poll, |tabData| is an object
     11    * containing data for individual tabs, |date| is the time the dump was
     12    * created, as a formatted string, and |privacyStripping| is whether or not
     13    * private information should be removed from the generated dump.
     14    *
     15    * Returns the new log dump as an object.  Resulting object may have a null
     16    * |numericDate|.
     17    *
     18    * TODO(eroman): Use javadoc notation for these parameters.
     19    *
     20    * Log dumps are just JSON objects containing five values:
     21    *
     22    *   |userComments| User-provided notes describing what this dump file is
     23    *                  about.
     24    *   |constants| needed to interpret the data.  This also includes some
     25    *               browser state information.
     26    *   |events| from the NetLog.
     27    *   |polledData| from each PollableDataHelper available on the source OS.
     28    *   |tabData| containing any tab-specific state that's not present in
     29    *             |polledData|.
     30    *
     31    * |polledData| and |tabData| may be empty objects, or may be missing data for
     32    * tabs not present on the OS the log is from.
     33    */
     34   function createLogDump(userComments, constants, events, polledData, tabData,
     35                          numericDate, privacyStripping) {
     36     if (privacyStripping)
     37       events = events.map(stripCookiesAndLoginInfo);
     38 
     39     var logDump = {
     40       'userComments': userComments,
     41       'constants': constants,
     42       'events': events,
     43       'polledData': polledData,
     44       'tabData': tabData
     45     };
     46 
     47     // Not technically client info, but it's used at the same point in the code.
     48     if (numericDate && constants.clientInfo) {
     49       constants.clientInfo.numericDate = numericDate;
     50     }
     51 
     52     return logDump;
     53   }
     54 
     55   /**
     56    * Returns a new log dump created using the polled data and date from the
     57    * |oldLogDump|.  The other parts of the log dump come from current
     58    * net-internals state.
     59    */
     60   function createUpdatedLogDump(userComments, oldLogDump, privacyStripping) {
     61     var numericDate = null;
     62     if (oldLogDump.constants.clientInfo &&
     63         oldLogDump.constants.clientInfo.numericDate) {
     64       numericDate = oldLogDump.constants.clientInfo.numericDate;
     65     }
     66     var logDump = createLogDump(
     67         userComments,
     68         Constants,
     69         EventsTracker.getInstance().getAllCapturedEvents(),
     70         oldLogDump.polledData,
     71         getTabData_(),
     72         numericDate,
     73         privacyStripping);
     74     return JSON.stringify(logDump);
     75   }
     76 
     77   /**
     78    * Creates a full log dump using |polledData| and the return value of each
     79    * tab's saveState function and passes it to |callback|.
     80    */
     81   function onUpdateAllCompleted(userComments, callback, privacyStripping,
     82                                 polledData) {
     83     var logDump = createLogDump(
     84         userComments,
     85         Constants,
     86         EventsTracker.getInstance().getAllCapturedEvents(),
     87         polledData,
     88         getTabData_(),
     89         timeutil.getCurrentTime(),
     90         privacyStripping);
     91     callback(JSON.stringify(logDump));
     92   }
     93 
     94   /**
     95    * Called to create a new log dump.  Must not be called once a dump has been
     96    * loaded.  Once a log dump has been created, |callback| is passed the dumped
     97    * text as a string.
     98    */
     99   function createLogDumpAsync(userComments, callback, privacyStripping) {
    100     g_browser.updateAllInfo(
    101         onUpdateAllCompleted.bind(null, userComments, callback,
    102                                   privacyStripping));
    103   }
    104 
    105   /**
    106    * Gather any tab-specific state information prior to creating a log dump.
    107    */
    108   function getTabData_() {
    109     var tabData = {};
    110     var tabSwitcher = MainView.getInstance().tabSwitcher();
    111     var tabIdToView = tabSwitcher.getAllTabViews();
    112     for (var tabId in tabIdToView) {
    113       var view = tabIdToView[tabId];
    114       if (view.saveState)
    115         tabData[tabId] = view.saveState();
    116     }
    117   }
    118 
    119   /**
    120    * Loads a full log dump.  Returns a string containing a log of the load.
    121    * |opt_fileName| should always be given when loading from a file, instead of
    122    * from a log dump generated in-memory.
    123    * The process goes like this:
    124    * 1)  Load constants.  If this fails, or the version number can't be handled,
    125    *     abort the load.  If this step succeeds, the load cannot be aborted.
    126    * 2)  Clear all events.  Any event observers are informed of the clear as
    127    *     normal.
    128    * 3)  Call onLoadLogStart(polledData, tabData) for each view with an
    129    *     onLoadLogStart function.  This allows tabs to clear any extra state
    130    *     that would affect the next step.  |polledData| contains the data polled
    131    *     for all helpers, but |tabData| contains only the data from that
    132    *     specific tab.
    133    * 4)  Add all events from the log file.
    134    * 5)  Call onLoadLogFinish(polledData, tabData) for each view with an
    135    *     onLoadLogFinish function.  The arguments are the same as in step 3.  If
    136    *     there is no onLoadLogFinish function, it throws an exception, or it
    137    *     returns false instead of true, the data dump is assumed to contain no
    138    *     valid data for the tab, so the tab is hidden.  Otherwise, the tab is
    139    *     shown.
    140    */
    141   function loadLogDump(logDump, opt_fileName) {
    142     // Perform minimal validity check, and abort if it fails.
    143     if (typeof(logDump) != 'object')
    144       return 'Load failed.  Top level JSON data is not an object.';
    145 
    146     // String listing text summary of load errors, if any.
    147     var errorString = '';
    148 
    149     if (!areValidConstants(logDump.constants))
    150       errorString += 'Invalid constants object.\n';
    151     if (typeof(logDump.events) != 'object')
    152       errorString += 'NetLog events missing.\n';
    153     if (typeof(logDump.constants.logFormatVersion) != 'number')
    154       errorString += 'Invalid version number.\n';
    155 
    156     if (errorString.length > 0)
    157       return 'Load failed:\n\n' + errorString;
    158 
    159     if (typeof(logDump.polledData) != 'object')
    160       logDump.polledData = {};
    161     if (typeof(logDump.tabData) != 'object')
    162       logDump.tabData = {};
    163 
    164     if (logDump.constants.logFormatVersion != Constants.logFormatVersion) {
    165       return 'Unable to load different log version.' +
    166              ' Found ' + logDump.constants.logFormatVersion +
    167              ', Expected ' + Constants.logFormatVersion;
    168     }
    169 
    170     g_browser.receivedConstants(logDump.constants);
    171 
    172     // Check for validity of each log entry, and then add the ones that pass.
    173     // Since the events are kept around, and we can't just hide a single view
    174     // on a bad event, we have more error checking for them than other data.
    175     var validEvents = [];
    176     var numDeprecatedPassiveEvents = 0;
    177     for (var eventIndex = 0; eventIndex < logDump.events.length; ++eventIndex) {
    178       var event = logDump.events[eventIndex];
    179       if (typeof event == 'object' &&
    180           typeof event.source == 'object' &&
    181           typeof event.time == 'string' &&
    182           typeof EventTypeNames[event.type] == 'string' &&
    183           typeof EventSourceTypeNames[event.source.type] == 'string' &&
    184           getKeyWithValue(EventPhase, event.phase) != '?') {
    185         if (event.wasPassivelyCaptured) {
    186           // NOTE: Up until Chrome 18, log dumps included "passively captured"
    187           // events. These are no longer supported, so skip past them
    188           // to avoid confusing the rest of the code.
    189           numDeprecatedPassiveEvents++;
    190           continue;
    191         }
    192         validEvents.push(event);
    193       }
    194     }
    195 
    196     // Make sure the loaded log contained an export date. If not we will
    197     // synthesize one. This can legitimately happen for dump files created
    198     // via command line flag, or for older dump formats (before Chrome 17).
    199     if (typeof logDump.constants.clientInfo.numericDate != 'number') {
    200       errorString += 'The log file is missing clientInfo.numericDate.\n';
    201 
    202       if (validEvents.length > 0) {
    203         errorString +=
    204             'Synthesizing export date as time of last event captured.\n';
    205         var lastEvent = validEvents[validEvents.length - 1];
    206         ClientInfo.numericDate =
    207             timeutil.convertTimeTicksToDate(lastEvent.time).getTime();
    208       } else {
    209         errorString += 'Can\'t guess export date!\n';
    210         ClientInfo.numericDate = 0;
    211       }
    212     }
    213 
    214     // Prevent communication with the browser.  Once the constants have been
    215     // loaded, it's safer to continue trying to load the log, even in the case
    216     // of bad data.
    217     MainView.getInstance().onLoadLog(opt_fileName);
    218 
    219     // Delete all events.  This will also update all logObservers.
    220     EventsTracker.getInstance().deleteAllLogEntries();
    221 
    222     // Inform all the views that a log file is being loaded, and pass in
    223     // view-specific saved state, if any.
    224     var tabSwitcher = MainView.getInstance().tabSwitcher();
    225     var tabIdToView = tabSwitcher.getAllTabViews();
    226     for (var tabId in tabIdToView) {
    227       var view = tabIdToView[tabId];
    228       view.onLoadLogStart(logDump.polledData, logDump.tabData[tabId]);
    229     }
    230     EventsTracker.getInstance().addLogEntries(validEvents);
    231 
    232     var numInvalidEvents = logDump.events.length -
    233         (validEvents.length + numDeprecatedPassiveEvents);
    234     if (numInvalidEvents > 0) {
    235       errorString += 'Unable to load ' + numInvalidEvents +
    236                      ' events, due to invalid data.\n\n';
    237     }
    238 
    239     if (numDeprecatedPassiveEvents > 0) {
    240       errorString += 'Discarded ' + numDeprecatedPassiveEvents +
    241           ' passively collected events. Use an older version of Chrome to' +
    242           ' load this dump if you want to see them.\n\n';
    243     }
    244 
    245     // Update all views with data from the file.  Show only those views which
    246     // successfully load the data.
    247     for (var tabId in tabIdToView) {
    248       var view = tabIdToView[tabId];
    249       var showView = false;
    250       // The try block eliminates the need for checking every single value
    251       // before trying to access it.
    252       try {
    253         if (view.onLoadLogFinish(logDump.polledData,
    254                                  logDump.tabData[tabId],
    255                                  logDump)) {
    256           showView = true;
    257         }
    258       } catch (error) {
    259         errorString += 'Caught error while calling onLoadLogFinish: ' +
    260                        error + '\n\n';
    261       }
    262       tabSwitcher.showMenuItem(tabId, showView);
    263     }
    264 
    265     return errorString + 'Log loaded.';
    266   }
    267 
    268   /**
    269    * Loads a log dump from the string |logFileContents|, which can be either a
    270    * full net-internals dump, or a NetLog dump only.  Returns a string
    271    * containing a log of the load.
    272    */
    273   function loadLogFile(logFileContents, fileName) {
    274     // Try and parse the log dump as a single JSON string.  If this succeeds,
    275     // it's most likely a full log dump.  Otherwise, it may be a dump created by
    276     // --log-net-log.
    277     var parsedDump = null;
    278     try {
    279       parsedDump = JSON.parse(logFileContents);
    280     } catch (error) {
    281       try {
    282         // We may have a --log-net-log=blah log dump.  If so, remove the comma
    283         // after the final good entry, and add the necessary close brackets.
    284         var end = Math.max(logFileContents.lastIndexOf(',\n'),
    285                            logFileContents.lastIndexOf(',\r'));
    286         if (end != -1)
    287           parsedDump = JSON.parse(logFileContents.substring(0, end) + ']}');
    288       }
    289       catch (error2) {
    290       }
    291     }
    292 
    293     if (!parsedDump)
    294       return 'Unable to parse log dump as JSON file.';
    295     return loadLogDump(parsedDump, fileName);
    296   }
    297 
    298   // Exports.
    299   return {
    300     createUpdatedLogDump: createUpdatedLogDump,
    301     createLogDumpAsync: createLogDumpAsync,
    302     loadLogFile: loadLogFile
    303   };
    304 })();
    305