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
5log_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