1// Copyright (c) 2011 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/**
6 * This view displays options for importing/exporting the captured data. Its
7 * primarily usefulness is to allow users to copy-paste their data in an easy
8 * to read format for bug reports.
9 *
10 *   - Has a button to generate a text report.
11 *
12 *   - Shows how many events have been captured.
13 *  @constructor
14 */
15function DataView(mainBoxId,
16                  outputTextBoxId,
17                  exportTextButtonId,
18                  securityStrippingCheckboxId,
19                  byteLoggingCheckboxId,
20                  passivelyCapturedCountId,
21                  activelyCapturedCountId,
22                  deleteAllId,
23                  dumpDataDivId,
24                  loadDataDivId,
25                  loadLogFileId,
26                  capturingTextSpanId,
27                  loggingTextSpanId) {
28  DivView.call(this, mainBoxId);
29
30  this.textPre_ = document.getElementById(outputTextBoxId);
31
32  var securityStrippingCheckbox =
33      document.getElementById(securityStrippingCheckboxId);
34  securityStrippingCheckbox.onclick =
35      this.onSetSecurityStripping_.bind(this, securityStrippingCheckbox);
36
37  var byteLoggingCheckbox = document.getElementById(byteLoggingCheckboxId);
38  byteLoggingCheckbox.onclick =
39      this.onSetByteLogging_.bind(this, byteLoggingCheckbox);
40
41  var exportTextButton = document.getElementById(exportTextButtonId);
42  exportTextButton.onclick = this.onExportToText_.bind(this);
43
44  this.activelyCapturedCountBox_ =
45      document.getElementById(activelyCapturedCountId);
46  this.passivelyCapturedCountBox_ =
47      document.getElementById(passivelyCapturedCountId);
48  document.getElementById(deleteAllId).onclick =
49      g_browser.deleteAllEvents.bind(g_browser);
50
51  this.dumpDataDiv_ = document.getElementById(dumpDataDivId);
52  this.loadDataDiv_ = document.getElementById(loadDataDivId);
53  this.capturingTextSpan_ = document.getElementById(capturingTextSpanId);
54  this.loggingTextSpan_ = document.getElementById(loggingTextSpanId);
55
56  document.getElementById(loadLogFileId).onclick =
57      g_browser.loadLogFile.bind(g_browser);
58
59  this.updateEventCounts_();
60  this.waitingForUpdate_ = false;
61
62  g_browser.addLogObserver(this);
63}
64
65inherits(DataView, DivView);
66
67/**
68 * Called whenever a new event is received.
69 */
70DataView.prototype.onLogEntryAdded = function(logEntry) {
71  this.updateEventCounts_();
72};
73
74/**
75 * Called whenever some log events are deleted.  |sourceIds| lists
76 * the source IDs of all deleted log entries.
77 */
78DataView.prototype.onLogEntriesDeleted = function(sourceIds) {
79  this.updateEventCounts_();
80};
81
82/**
83 * Called whenever all log events are deleted.
84 */
85DataView.prototype.onAllLogEntriesDeleted = function() {
86  this.updateEventCounts_();
87};
88
89/**
90 * Called when either a log file is loaded or when going back to actively
91 * logging events.  In either case, called after clearing the old entries,
92 * but before getting any new ones.
93 */
94DataView.prototype.onSetIsViewingLogFile = function(isViewingLogFile) {
95  setNodeDisplay(this.dumpDataDiv_, !isViewingLogFile);
96  setNodeDisplay(this.capturingTextSpan_, !isViewingLogFile);
97  setNodeDisplay(this.loggingTextSpan_, isViewingLogFile);
98  this.setText_('');
99};
100
101/**
102 * Updates the counters showing how many events have been captured.
103 */
104DataView.prototype.updateEventCounts_ = function() {
105  this.activelyCapturedCountBox_.innerText =
106      g_browser.getNumActivelyCapturedEvents()
107  this.passivelyCapturedCountBox_.innerText =
108      g_browser.getNumPassivelyCapturedEvents();
109};
110
111/**
112 * Depending on the value of the checkbox, enables or disables logging of
113 * actual bytes transferred.
114 */
115DataView.prototype.onSetByteLogging_ = function(byteLoggingCheckbox) {
116  if (byteLoggingCheckbox.checked) {
117    g_browser.setLogLevel(LogLevelType.LOG_ALL);
118  } else {
119    g_browser.setLogLevel(LogLevelType.LOG_ALL_BUT_BYTES);
120  }
121};
122
123/**
124 * Depending on the value of the checkbox, enables or disables stripping
125 * cookies and passwords from log dumps and displayed events.
126 */
127DataView.prototype.onSetSecurityStripping_ =
128    function(securityStrippingCheckbox) {
129  g_browser.setSecurityStripping(securityStrippingCheckbox.checked);
130};
131
132/**
133 * Clears displayed text when security stripping is toggled.
134 */
135DataView.prototype.onSecurityStrippingChanged = function() {
136  this.setText_('');
137}
138
139/**
140 * If not already waiting for results from all updates, triggers all
141 * updates and starts waiting for them to complete.
142 */
143DataView.prototype.onExportToText_ = function() {
144  if (this.waitingForUpdate_)
145    return;
146  this.waitingForUpdate = true;
147  this.setText_('Generating...');
148  g_browser.updateAllInfo(this.onUpdateAllCompleted.bind(this));
149};
150
151/**
152 * Presents the captured data as formatted text.
153 */
154DataView.prototype.onUpdateAllCompleted = function(data) {
155  // It's possible for a log file to be loaded while a dump is being generated.
156  // When that happens, don't display the log dump, to avoid any confusion.
157  if (g_browser.isViewingLogFile())
158    return;
159  this.waitingForUpdate_ = false;
160  var text = [];
161
162  // Print some basic information about our environment.
163  text.push('Data exported on: ' + (new Date()).toLocaleString());
164  text.push('');
165  text.push('Number of passively captured events: ' +
166            g_browser.getNumPassivelyCapturedEvents());
167  text.push('Number of actively captured events: ' +
168            g_browser.getNumActivelyCapturedEvents());
169  text.push('');
170
171  text.push('Chrome version: ' + ClientInfo.version +
172            ' (' + ClientInfo.official +
173            ' ' + ClientInfo.cl +
174            ') ' + ClientInfo.version_mod);
175  // Third value in first set of parentheses in user-agent string.
176  var platform = /\(.*?;.*?; (.*?);/.exec(navigator.userAgent);
177  if (platform)
178    text.push('Platform: ' + platform[1]);
179  text.push('Command line: ' + ClientInfo.command_line);
180
181  text.push('');
182  var default_address_family = data.hostResolverInfo.default_address_family;
183  text.push('Default address family: ' +
184      getKeyWithValue(AddressFamily, default_address_family));
185  if (default_address_family == AddressFamily.ADDRESS_FAMILY_IPV4)
186    text.push('  (IPv6 disabled)');
187
188  text.push('');
189  text.push('----------------------------------------------');
190  text.push(' Proxy settings (effective)');
191  text.push('----------------------------------------------');
192  text.push('');
193
194  text.push(proxySettingsToString(data.proxySettings.effective));
195
196  text.push('');
197  text.push('----------------------------------------------');
198  text.push(' Proxy settings (original)');
199  text.push('----------------------------------------------');
200  text.push('');
201
202  text.push(proxySettingsToString(data.proxySettings.original));
203
204  text.push('');
205  text.push('----------------------------------------------');
206  text.push(' Bad proxies cache');
207  text.push('----------------------------------------------');
208
209  var badProxiesList = data.badProxies;
210  if (badProxiesList.length == 0) {
211    text.push('');
212    text.push('None');
213  } else {
214    for (var i = 0; i < badProxiesList.length; ++i) {
215      var e = badProxiesList[i];
216      text.push('');
217      text.push('(' + (i+1) + ')');
218      text.push('Proxy: ' + e.proxy_uri);
219      text.push('Bad until: ' + this.formatExpirationTime_(e.bad_until));
220    }
221  }
222
223  text.push('');
224  text.push('----------------------------------------------');
225  text.push(' Host resolver cache');
226  text.push('----------------------------------------------');
227  text.push('');
228
229  var hostResolverCache = data.hostResolverInfo.cache;
230
231  text.push('Capacity: ' + hostResolverCache.capacity);
232  text.push('Time to live for successful resolves (ms): ' +
233            hostResolverCache.ttl_success_ms);
234  text.push('Time to live for failed resolves (ms): ' +
235            hostResolverCache.ttl_failure_ms);
236
237  if (hostResolverCache.entries.length > 0) {
238    for (var i = 0; i < hostResolverCache.entries.length; ++i) {
239      var e = hostResolverCache.entries[i];
240
241      text.push('');
242      text.push('(' + (i+1) + ')');
243      text.push('Hostname: ' + e.hostname);
244      text.push('Address family: ' +
245                getKeyWithValue(AddressFamily, e.address_family));
246
247      if (e.error != undefined) {
248         text.push('Error: ' + e.error);
249      } else {
250        for (var j = 0; j < e.addresses.length; ++j) {
251          text.push('Address ' + (j + 1) + ': ' + e.addresses[j]);
252        }
253      }
254
255      text.push('Valid until: ' + this.formatExpirationTime_(e.expiration));
256      var expirationDate = g_browser.convertTimeTicksToDate(e.expiration);
257      text.push('  (' + expirationDate.toLocaleString() + ')');
258    }
259  } else {
260    text.push('');
261    text.push('None');
262  }
263
264  text.push('');
265  text.push('----------------------------------------------');
266  text.push(' Events');
267  text.push('----------------------------------------------');
268  text.push('');
269
270  this.appendEventsPrintedAsText_(text);
271
272  text.push('');
273  text.push('----------------------------------------------');
274  text.push(' Http cache stats');
275  text.push('----------------------------------------------');
276  text.push('');
277
278  var httpCacheStats = data.httpCacheInfo.stats;
279  for (var statName in httpCacheStats)
280    text.push(statName + ': ' + httpCacheStats[statName]);
281
282  text.push('');
283  text.push('----------------------------------------------');
284  text.push(' Socket pools');
285  text.push('----------------------------------------------');
286  text.push('');
287
288  this.appendSocketPoolsAsText_(text, data.socketPoolInfo);
289
290  text.push('');
291  text.push('----------------------------------------------');
292  text.push(' SPDY Status');
293  text.push('----------------------------------------------');
294  text.push('');
295
296  text.push('SPDY Enabled: ' + data.spdyStatus.spdy_enabled);
297  text.push('Use Alternate Protocol: ' +
298      data.spdyStatus.use_alternate_protocols);
299  text.push('Force SPDY Always: ' + data.spdyStatus.force_spdy_always);
300  text.push('Force SPDY Over SSL: ' + data.spdyStatus.force_spdy_over_ssl);
301  text.push('Next Protocols: ' + data.spdyStatus.next_protos);
302
303
304  text.push('');
305  text.push('----------------------------------------------');
306  text.push(' SPDY Sessions');
307  text.push('----------------------------------------------');
308  text.push('');
309
310  if (data.spdySessionInfo == null || data.spdySessionInfo.length == 0) {
311    text.push('None');
312  } else {
313    var spdyTablePrinter =
314      SpdyView.createSessionTablePrinter(data.spdySessionInfo);
315    text.push(spdyTablePrinter.toText(2));
316  }
317
318  text.push('');
319  text.push('----------------------------------------------');
320  text.push(' Alternate Protocol Mappings');
321  text.push('----------------------------------------------');
322  text.push('');
323
324  if (data.spdyAlternateProtocolMappings == null ||
325      data.spdyAlternateProtocolMappings.length == 0) {
326    text.push('None');
327  } else {
328    var spdyTablePrinter =
329      SpdyView.createAlternateProtocolMappingsTablePrinter(
330          data.spdyAlternateProtocolMappings);
331    text.push(spdyTablePrinter.toText(2));
332  }
333
334  if (g_browser.isPlatformWindows()) {
335    text.push('');
336    text.push('----------------------------------------------');
337    text.push(' Winsock layered service providers');
338    text.push('----------------------------------------------');
339    text.push('');
340
341    var serviceProviders = data.serviceProviders;
342    var layeredServiceProviders = serviceProviders.service_providers;
343    for (var i = 0; i < layeredServiceProviders.length; ++i) {
344      var provider = layeredServiceProviders[i];
345      text.push('name: ' + provider.name);
346      text.push('version: ' + provider.version);
347      text.push('type: ' +
348                ServiceProvidersView.getLayeredServiceProviderType(provider));
349      text.push('socket_type: ' +
350                ServiceProvidersView.getSocketType(provider));
351      text.push('socket_protocol: ' +
352                ServiceProvidersView.getProtocolType(provider));
353      text.push('path: ' + provider.path);
354      text.push('');
355    }
356
357    text.push('');
358    text.push('----------------------------------------------');
359    text.push(' Winsock namespace providers');
360    text.push('----------------------------------------------');
361    text.push('');
362
363    var namespaceProviders = serviceProviders.namespace_providers;
364    for (var i = 0; i < namespaceProviders.length; ++i) {
365      var provider = namespaceProviders[i];
366      text.push('name: ' + provider.name);
367      text.push('version: ' + provider.version);
368      text.push('type: ' +
369                ServiceProvidersView.getNamespaceProviderType(provider));
370      text.push('active: ' + provider.active);
371      text.push('');
372    }
373  }
374
375  // Open a new window to display this text.
376  this.setText_(text.join('\n'));
377
378  this.selectText_();
379};
380
381DataView.prototype.appendEventsPrintedAsText_ = function(out) {
382  var allEvents = g_browser.getAllCapturedEvents();
383
384  // Group the events into buckets by source ID, and buckets by source type.
385  var sourceIds = [];
386  var sourceIdToEventList = {};
387  var sourceTypeToSourceIdList = {};
388
389  // Lists used for actual output.
390  var eventLists = [];
391
392  for (var i = 0; i < allEvents.length; ++i) {
393    var e = allEvents[i];
394    var eventList = sourceIdToEventList[e.source.id];
395    if (!eventList) {
396      eventList = [];
397      eventLists.push(eventList);
398      if (e.source.type != LogSourceType.NONE)
399        sourceIdToEventList[e.source.id] = eventList;
400
401      // Update sourceIds
402      sourceIds.push(e.source.id);
403
404      // Update the sourceTypeToSourceIdList list.
405      var idList = sourceTypeToSourceIdList[e.source.type];
406      if (!idList) {
407        idList = [];
408        sourceTypeToSourceIdList[e.source.type] = idList;
409      }
410      idList.push(e.source.id);
411    }
412    eventList.push(e);
413  }
414
415
416  // For each source or event without a source (ordered by when the first
417  // output event for that source happened).
418  for (var i = 0; i < eventLists.length; ++i) {
419    var eventList = eventLists[i];
420    var sourceId = eventList[0].source.id;
421    var sourceType = eventList[0].source.type;
422
423    var startDate = g_browser.convertTimeTicksToDate(eventList[0].time);
424
425    out.push('------------------------------------------');
426    out.push(getKeyWithValue(LogSourceType, sourceType) +
427             ' (id=' + sourceId + ')' +
428             '  [start=' + startDate.toLocaleString() + ']');
429    out.push('------------------------------------------');
430
431    out.push(PrintSourceEntriesAsText(eventList));
432  }
433};
434
435DataView.prototype.appendSocketPoolsAsText_ = function(text, socketPoolInfo) {
436  var socketPools = SocketPoolWrapper.createArrayFrom(socketPoolInfo);
437  var tablePrinter = SocketPoolWrapper.createTablePrinter(socketPools);
438  text.push(tablePrinter.toText(2));
439
440  text.push('');
441
442  for (var i = 0; i < socketPools.length; ++i) {
443    if (socketPools[i].origPool.groups == undefined)
444      continue;
445    var groupTablePrinter = socketPools[i].createGroupTablePrinter();
446    text.push(groupTablePrinter.toText(2));
447  }
448};
449
450/**
451 * Helper function to set this view's content to |text|.
452 */
453DataView.prototype.setText_ = function(text) {
454  this.textPre_.innerHTML = '';
455  addTextNode(this.textPre_, text);
456};
457
458/**
459 * Format a time ticks count as a timestamp.
460 */
461DataView.prototype.formatExpirationTime_ = function(timeTicks) {
462  var d = g_browser.convertTimeTicksToDate(timeTicks);
463  var isExpired = d.getTime() < (new Date()).getTime();
464  return 't=' + d.getTime() + (isExpired ? ' [EXPIRED]' : '');
465};
466
467/**
468 * Select all text from log dump.
469 */
470DataView.prototype.selectText_ = function() {
471  var selection = window.getSelection();
472  selection.removeAllRanges();
473
474  var range = document.createRange();
475  range.selectNodeContents(this.textPre_);
476  selection.addRange(range);
477};
478