Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / resources / net_internals / log_util.js
blob5438c64851069b91a20ff569bd8c2db9310665d9
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.
5 log_util = (function() {
6   'use strict';
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);
39     var logDump = {
40       'userComments': userComments,
41       'constants': constants,
42       'events': events,
43       'polledData': polledData,
44       'tabData': tabData
45     };
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     }
52     return logDump;
53   }
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   }
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   }
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   }
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   }
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.';
146     // String listing text summary of load errors, if any.
147     var errorString = '';
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';
156     if (errorString.length > 0)
157       return 'Load failed:\n\n' + errorString;
159     if (typeof(logDump.polledData) != 'object')
160       logDump.polledData = {};
161     if (typeof(logDump.tabData) != 'object')
162       logDump.tabData = {};
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     }
170     g_browser.receivedConstants(logDump.constants);
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     }
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';
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     }
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);
219     // Delete all events.  This will also update all logObservers.
220     EventsTracker.getInstance().deleteAllLogEntries();
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);
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     }
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     }
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     }
265     return errorString + 'Log loaded.';
266   }
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     var errorString = '';
279     try {
280       parsedDump = JSON.parse(logFileContents);
281     } catch (error) {
282       try {
283         // We may have a --log-net-log=blah log dump.  If so, remove the comma
284         // after the final good entry, and add the necessary close brackets.
285         var end = Math.max(logFileContents.lastIndexOf(',\n'),
286                            logFileContents.lastIndexOf(',\r'));
287         if (end != -1) {
288           parsedDump = JSON.parse(logFileContents.substring(0, end) + ']}');
289           errorString += 'Log file truncated.  Events may be missing.\n';
290         }
291       }
292       catch (error2) {
293       }
294     }
296     if (!parsedDump)
297       return 'Unable to parse log dump as JSON file.';
298     return errorString + loadLogDump(parsedDump, fileName);
299   }
301   // Exports.
302   return {
303     createUpdatedLogDump: createUpdatedLogDump,
304     createLogDumpAsync: createLogDumpAsync,
305     loadLogFile: loadLogFile
306   };
307 })();