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