main.js revision ddb351dbec246cf1fab5ec20d2d5520909041de1
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 * Dictionary of constants (initialized by browser).
7 */
8var LogEventType = null;
9var LogEventPhase = null;
10var ClientInfo = null;
11var LogSourceType = null;
12var LogLevelType = null;
13var NetError = null;
14var LoadFlag = null;
15var AddressFamily = null;
16
17/**
18 * Object to communicate between the renderer and the browser.
19 * @type {!BrowserBridge}
20 */
21var g_browser = null;
22
23/**
24 * Main entry point. called once the page has loaded.
25 */
26function onLoaded() {
27  g_browser = new BrowserBridge();
28
29  // Create the view which displays events lists, and lets you select, filter
30  // and delete them.
31  var eventsView = new EventsView('eventsListTableBody',
32                                  'filterInput',
33                                  'filterCount',
34                                  'deleteSelected',
35                                  'deleteAll',
36                                  'selectAll',
37                                  'sortById',
38                                  'sortBySource',
39                                  'sortByDescription',
40
41                                  // IDs for the details view.
42                                  'detailsTabHandles',
43                                  'detailsLogTab',
44                                  'detailsTimelineTab',
45                                  'detailsLogBox',
46                                  'detailsTimelineBox',
47
48                                  // IDs for the layout boxes.
49                                  'filterBox',
50                                  'eventsBox',
51                                  'actionBox',
52                                  'splitterBox');
53
54  // Create a view which will display info on the proxy setup.
55  var proxyView = new ProxyView('proxyTabContent',
56                                'proxyOriginalSettings',
57                                'proxyEffectiveSettings',
58                                'proxyReloadSettings',
59                                'badProxiesTableBody',
60                                'clearBadProxies',
61                                'proxyResolverLog');
62
63  // Create a view which will display information on the host resolver.
64  var dnsView = new DnsView('dnsTabContent',
65                            'hostResolverCacheTbody',
66                            'clearHostResolverCache',
67                            'hostResolverDefaultFamily',
68                            'hostResolverIPv6Disabled',
69                            'hostResolverEnableIPv6',
70                            'hostResolverCacheCapacity',
71                            'hostResolverCacheTTLSuccess',
72                            'hostResolverCacheTTLFailure');
73
74  // Create a view which will display import/export options to control the
75  // captured data.
76  var dataView = new DataView('dataTabContent', 'exportedDataText',
77                              'exportToText', 'securityStrippingCheckbox',
78                              'byteLoggingCheckbox', 'passivelyCapturedCount',
79                              'activelyCapturedCount', 'dataViewDeleteAll',
80                              'dataViewDumpDataDiv', 'dataViewLoadDataDiv',
81                              'dataViewLoadLogFile',
82                              'dataViewCapturingTextSpan',
83                              'dataViewLoggingTextSpan');
84
85  // Create a view which will display the results and controls for connection
86  // tests.
87  var testView = new TestView('testTabContent', 'testUrlInput',
88                              'connectionTestsForm', 'testSummary');
89
90  // Create a view which allows the user to query and alter the HSTS database.
91  var hstsView = new HSTSView('hstsTabContent',
92                              'hstsQueryInput', 'hstsQueryForm',
93                              'hstsQueryOutput',
94                              'hstsAddInput', 'hstsAddForm', 'hstsCheckInput',
95                              'hstsAddPins',
96                              'hstsDeleteInput', 'hstsDeleteForm');
97
98  var httpCacheView = new HttpCacheView('httpCacheTabContent',
99                                        'httpCacheStats');
100
101  var socketsView = new SocketsView('socketsTabContent',
102                                    'socketPoolDiv',
103                                    'socketPoolGroupsDiv',
104                                    'socketPoolCloseIdleButton',
105                                    'socketPoolFlushButton');
106
107  var spdyView = new SpdyView('spdyTabContent',
108                              'spdyEnabledSpan',
109                              'spdyUseAlternateProtocolSpan',
110                              'spdyForceAlwaysSpan',
111                              'spdyForceOverSslSpan',
112                              'spdyNextProtocolsSpan',
113                              'spdyAlternateProtocolMappingsDiv',
114                              'spdySessionNoneSpan',
115                              'spdySessionLinkSpan',
116                              'spdySessionDiv');
117
118  var serviceView;
119  if (g_browser.isPlatformWindows()) {
120    serviceView = new ServiceProvidersView('serviceProvidersTab',
121                                           'serviceProvidersTabContent',
122                                           'serviceProvidersTbody',
123                                           'namespaceProvidersTbody');
124  }
125
126  var httpThrottlingView = new HttpThrottlingView(
127      'httpThrottlingTabContent', 'enableHttpThrottlingCheckbox');
128
129  // Create a view which lets you tab between the different sub-views.
130  var categoryTabSwitcher = new TabSwitcherView('categoryTabHandles');
131  g_browser.setTabSwitcher(categoryTabSwitcher);
132
133  // Populate the main tabs.
134  categoryTabSwitcher.addTab('eventsTab', eventsView, false);
135  categoryTabSwitcher.addTab('proxyTab', proxyView, false);
136  categoryTabSwitcher.addTab('dnsTab', dnsView, false);
137  categoryTabSwitcher.addTab('socketsTab', socketsView, false);
138  categoryTabSwitcher.addTab('spdyTab', spdyView, false);
139  categoryTabSwitcher.addTab('httpCacheTab', httpCacheView, false);
140  categoryTabSwitcher.addTab('dataTab', dataView, false);
141  if (g_browser.isPlatformWindows())
142    categoryTabSwitcher.addTab('serviceProvidersTab', serviceView, false);
143  categoryTabSwitcher.addTab('testTab', testView, false);
144  categoryTabSwitcher.addTab('hstsTab', hstsView, false);
145  categoryTabSwitcher.addTab('httpThrottlingTab', httpThrottlingView, false);
146
147  // Build a map from the anchor name of each tab handle to its "tab ID".
148  // We will consider navigations to the #hash as a switch tab request.
149  var anchorMap = {};
150  var tabIds = categoryTabSwitcher.getAllTabIds();
151  for (var i = 0; i < tabIds.length; ++i) {
152    var aNode = document.getElementById(tabIds[i]);
153    anchorMap[aNode.hash] = tabIds[i];
154  }
155  // Default the empty hash to the data tab.
156  anchorMap['#'] = anchorMap[''] = 'dataTab';
157
158  window.onhashchange = onUrlHashChange.bind(null, anchorMap,
159                                             categoryTabSwitcher);
160
161  // Make this category tab widget the primary view, that fills the whole page.
162  var windowView = new WindowView(categoryTabSwitcher);
163
164  // Trigger initial layout.
165  windowView.resetGeometry();
166
167  // Select the initial view based on the current URL.
168  window.onhashchange();
169
170  // Inform observers a log file is not currently being displayed.
171  g_browser.setIsViewingLogFile_(false);
172
173  // Tell the browser that we are ready to start receiving log events.
174  g_browser.sendReady();
175}
176
177/**
178 * This class provides a "bridge" for communicating between the javascript and
179 * the browser.
180 *
181 * @constructor
182 */
183function BrowserBridge() {
184  // List of observers for various bits of browser state.
185  this.logObservers_ = [];
186  this.connectionTestsObservers_ = [];
187  this.hstsObservers_ = [];
188  this.httpThrottlingObservers_ = [];
189
190  this.pollableDataHelpers_ = {};
191  this.pollableDataHelpers_.proxySettings =
192      new PollableDataHelper('onProxySettingsChanged',
193                             this.sendGetProxySettings.bind(this));
194  this.pollableDataHelpers_.badProxies =
195      new PollableDataHelper('onBadProxiesChanged',
196                             this.sendGetBadProxies.bind(this));
197  this.pollableDataHelpers_.httpCacheInfo =
198      new PollableDataHelper('onHttpCacheInfoChanged',
199                             this.sendGetHttpCacheInfo.bind(this));
200  this.pollableDataHelpers_.hostResolverInfo =
201      new PollableDataHelper('onHostResolverInfoChanged',
202                             this.sendGetHostResolverInfo.bind(this));
203  this.pollableDataHelpers_.socketPoolInfo =
204      new PollableDataHelper('onSocketPoolInfoChanged',
205                             this.sendGetSocketPoolInfo.bind(this));
206  this.pollableDataHelpers_.spdySessionInfo =
207      new PollableDataHelper('onSpdySessionInfoChanged',
208                             this.sendGetSpdySessionInfo.bind(this));
209  this.pollableDataHelpers_.spdyStatus =
210      new PollableDataHelper('onSpdyStatusChanged',
211                             this.sendGetSpdyStatus.bind(this));
212  this.pollableDataHelpers_.spdyAlternateProtocolMappings =
213      new PollableDataHelper('onSpdyAlternateProtocolMappingsChanged',
214                             this.sendGetSpdyAlternateProtocolMappings.bind(
215                                 this));
216  if (this.isPlatformWindows()) {
217    this.pollableDataHelpers_.serviceProviders =
218        new PollableDataHelper('onServiceProvidersChanged',
219                               this.sendGetServiceProviders.bind(this));
220  }
221
222  // Cache of the data received.
223  this.numPassivelyCapturedEvents_ = 0;
224  this.capturedEvents_ = [];
225
226  // Next unique id to be assigned to a log entry without a source.
227  // Needed to simplify deletion, identify associated GUI elements, etc.
228  this.nextSourcelessEventId_ = -1;
229
230  // True when viewing a log file rather than actively logged events.
231  // When viewing a log file, all tabs are hidden except the event view,
232  // and all received events are ignored.
233  this.isViewingLogFile_ = false;
234
235  // True when cookies and authentication information should be removed from
236  // displayed events.  When true, such information should be hidden from
237  // all pages.
238  this.enableSecurityStripping_ = true;
239}
240
241/*
242 * Takes the current hash in form of "#tab&param1=value1&param2=value2&...".
243 * Puts the parameters in an object, and passes the resulting object to
244 * |categoryTabSwitcher|.  Uses tab and |anchorMap| to find a tab ID,
245 * which it also passes to the tab switcher.
246 *
247 * Parameters and values are decoded with decodeURIComponent().
248 */
249function onUrlHashChange(anchorMap, categoryTabSwitcher) {
250  var parameters = window.location.hash.split('&');
251
252  var tabId = anchorMap[parameters[0]];
253  if (!tabId)
254    return;
255
256  // Split each string except the first around the '='.
257  var paramDict = null;
258  for (var i = 1; i < parameters.length; i++) {
259    var paramStrings = parameters[i].split('=');
260    if (paramStrings.length != 2)
261      continue;
262    if (paramDict == null)
263      paramDict = {};
264    var key = decodeURIComponent(paramStrings[0]);
265    var value = decodeURIComponent(paramStrings[1]);
266    paramDict[key] = value;
267  }
268
269  categoryTabSwitcher.switchToTab(tabId, paramDict);
270}
271
272/**
273 * Delay in milliseconds between updates of certain browser information.
274 */
275BrowserBridge.POLL_INTERVAL_MS = 5000;
276
277//------------------------------------------------------------------------------
278// Messages sent to the browser
279//------------------------------------------------------------------------------
280
281BrowserBridge.prototype.sendReady = function() {
282  chrome.send('notifyReady');
283
284  // Some of the data we are interested is not currently exposed as a stream,
285  // so we will poll the browser to find out when it changes and then notify
286  // the observers.
287  window.setInterval(this.checkForUpdatedInfo.bind(this, false),
288                     BrowserBridge.POLL_INTERVAL_MS);
289};
290
291BrowserBridge.prototype.isPlatformWindows = function() {
292  return /Win/.test(navigator.platform);
293};
294
295BrowserBridge.prototype.sendGetProxySettings = function() {
296  // The browser will call receivedProxySettings on completion.
297  chrome.send('getProxySettings');
298};
299
300BrowserBridge.prototype.sendReloadProxySettings = function() {
301  chrome.send('reloadProxySettings');
302};
303
304BrowserBridge.prototype.sendGetBadProxies = function() {
305  // The browser will call receivedBadProxies on completion.
306  chrome.send('getBadProxies');
307};
308
309BrowserBridge.prototype.sendGetHostResolverInfo = function() {
310  // The browser will call receivedHostResolverInfo on completion.
311  chrome.send('getHostResolverInfo');
312};
313
314BrowserBridge.prototype.sendClearBadProxies = function() {
315  chrome.send('clearBadProxies');
316};
317
318BrowserBridge.prototype.sendClearHostResolverCache = function() {
319  chrome.send('clearHostResolverCache');
320};
321
322BrowserBridge.prototype.sendStartConnectionTests = function(url) {
323  chrome.send('startConnectionTests', [url]);
324};
325
326BrowserBridge.prototype.sendHSTSQuery = function(domain) {
327  chrome.send('hstsQuery', [domain]);
328};
329
330BrowserBridge.prototype.sendHSTSAdd = function(domain,
331                                               include_subdomains,
332                                               pins) {
333  chrome.send('hstsAdd', [domain, include_subdomains, pins]);
334};
335
336BrowserBridge.prototype.sendHSTSDelete = function(domain) {
337  chrome.send('hstsDelete', [domain]);
338};
339
340BrowserBridge.prototype.sendGetHttpCacheInfo = function() {
341  chrome.send('getHttpCacheInfo');
342};
343
344BrowserBridge.prototype.sendGetSocketPoolInfo = function() {
345  chrome.send('getSocketPoolInfo');
346};
347
348BrowserBridge.prototype.sendCloseIdleSockets = function() {
349  chrome.send('closeIdleSockets');
350};
351
352BrowserBridge.prototype.sendFlushSocketPools = function() {
353  chrome.send('flushSocketPools');
354};
355
356BrowserBridge.prototype.sendGetSpdySessionInfo = function() {
357  chrome.send('getSpdySessionInfo');
358};
359
360BrowserBridge.prototype.sendGetSpdyStatus = function() {
361  chrome.send('getSpdyStatus');
362};
363
364BrowserBridge.prototype.sendGetSpdyAlternateProtocolMappings = function() {
365  chrome.send('getSpdyAlternateProtocolMappings');
366};
367
368BrowserBridge.prototype.sendGetServiceProviders = function() {
369  chrome.send('getServiceProviders');
370};
371
372BrowserBridge.prototype.enableIPv6 = function() {
373  chrome.send('enableIPv6');
374};
375
376BrowserBridge.prototype.setLogLevel = function(logLevel) {
377  chrome.send('setLogLevel', ['' + logLevel]);
378};
379
380BrowserBridge.prototype.enableHttpThrottling = function(enable) {
381  chrome.send('enableHttpThrottling', [enable]);
382};
383
384BrowserBridge.prototype.loadLogFile = function() {
385  chrome.send('loadLogFile');
386}
387
388//------------------------------------------------------------------------------
389// Messages received from the browser
390//------------------------------------------------------------------------------
391
392BrowserBridge.prototype.receivedLogEntries = function(logEntries) {
393  // Does nothing if viewing a log file.
394  if (this.isViewingLogFile_)
395    return;
396  this.addLogEntries(logEntries);
397};
398
399BrowserBridge.prototype.receivedLogEventTypeConstants = function(constantsMap) {
400  LogEventType = constantsMap;
401};
402
403BrowserBridge.prototype.receivedClientInfo =
404function(info) {
405  ClientInfo = info;
406};
407
408BrowserBridge.prototype.receivedLogEventPhaseConstants =
409function(constantsMap) {
410  LogEventPhase = constantsMap;
411};
412
413BrowserBridge.prototype.receivedLogSourceTypeConstants =
414function(constantsMap) {
415  LogSourceType = constantsMap;
416};
417
418BrowserBridge.prototype.receivedLogLevelConstants =
419function(constantsMap) {
420  LogLevelType = constantsMap;
421};
422
423BrowserBridge.prototype.receivedLoadFlagConstants = function(constantsMap) {
424  LoadFlag = constantsMap;
425};
426
427BrowserBridge.prototype.receivedNetErrorConstants = function(constantsMap) {
428  NetError = constantsMap;
429};
430
431BrowserBridge.prototype.receivedAddressFamilyConstants =
432function(constantsMap) {
433  AddressFamily = constantsMap;
434};
435
436BrowserBridge.prototype.receivedTimeTickOffset = function(timeTickOffset) {
437  this.timeTickOffset_ = timeTickOffset;
438};
439
440BrowserBridge.prototype.receivedProxySettings = function(proxySettings) {
441  this.pollableDataHelpers_.proxySettings.update(proxySettings);
442};
443
444BrowserBridge.prototype.receivedBadProxies = function(badProxies) {
445  this.pollableDataHelpers_.badProxies.update(badProxies);
446};
447
448BrowserBridge.prototype.receivedHostResolverInfo =
449function(hostResolverInfo) {
450  this.pollableDataHelpers_.hostResolverInfo.update(hostResolverInfo);
451};
452
453BrowserBridge.prototype.receivedSocketPoolInfo = function(socketPoolInfo) {
454  this.pollableDataHelpers_.socketPoolInfo.update(socketPoolInfo);
455};
456
457BrowserBridge.prototype.receivedSpdySessionInfo = function(spdySessionInfo) {
458  this.pollableDataHelpers_.spdySessionInfo.update(spdySessionInfo);
459};
460
461BrowserBridge.prototype.receivedSpdyStatus = function(spdyStatus) {
462  this.pollableDataHelpers_.spdyStatus.update(spdyStatus);
463};
464
465BrowserBridge.prototype.receivedSpdyAlternateProtocolMappings =
466    function(spdyAlternateProtocolMappings) {
467  this.pollableDataHelpers_.spdyAlternateProtocolMappings.update(
468      spdyAlternateProtocolMappings);
469};
470
471BrowserBridge.prototype.receivedServiceProviders = function(serviceProviders) {
472  this.pollableDataHelpers_.serviceProviders.update(serviceProviders);
473};
474
475BrowserBridge.prototype.receivedPassiveLogEntries = function(entries) {
476  // Due to an expected race condition, it is possible to receive actively
477  // captured log entries before the passively logged entries are received.
478  //
479  // When that happens, we create a copy of the actively logged entries, delete
480  // all entries, and, after handling all the passively logged entries, add back
481  // the deleted actively logged entries.
482  var earlyActivelyCapturedEvents = this.capturedEvents_.slice(0);
483  if (earlyActivelyCapturedEvents.length > 0)
484    this.deleteAllEvents();
485
486  this.numPassivelyCapturedEvents_ = entries.length;
487  for (var i = 0; i < entries.length; ++i)
488    entries[i].wasPassivelyCaptured = true;
489  this.receivedLogEntries(entries);
490
491  // Add back early actively captured events, if any.
492  if (earlyActivelyCapturedEvents.length)
493    this.receivedLogEntries(earlyActivelyCapturedEvents);
494};
495
496
497BrowserBridge.prototype.receivedStartConnectionTestSuite = function() {
498  for (var i = 0; i < this.connectionTestsObservers_.length; ++i)
499    this.connectionTestsObservers_[i].onStartedConnectionTestSuite();
500};
501
502BrowserBridge.prototype.receivedStartConnectionTestExperiment = function(
503    experiment) {
504  for (var i = 0; i < this.connectionTestsObservers_.length; ++i) {
505    this.connectionTestsObservers_[i].onStartedConnectionTestExperiment(
506        experiment);
507  }
508};
509
510BrowserBridge.prototype.receivedCompletedConnectionTestExperiment =
511function(info) {
512  for (var i = 0; i < this.connectionTestsObservers_.length; ++i) {
513    this.connectionTestsObservers_[i].onCompletedConnectionTestExperiment(
514        info.experiment, info.result);
515  }
516};
517
518BrowserBridge.prototype.receivedCompletedConnectionTestSuite = function() {
519  for (var i = 0; i < this.connectionTestsObservers_.length; ++i)
520    this.connectionTestsObservers_[i].onCompletedConnectionTestSuite();
521};
522
523BrowserBridge.prototype.receivedHSTSResult = function(info) {
524  for (var i = 0; i < this.hstsObservers_.length; ++i)
525    this.hstsObservers_[i].onHSTSQueryResult(info);
526};
527
528BrowserBridge.prototype.receivedHttpCacheInfo = function(info) {
529  this.pollableDataHelpers_.httpCacheInfo.update(info);
530};
531
532BrowserBridge.prototype.receivedHttpThrottlingEnabledPrefChanged = function(
533    enabled) {
534  for (var i = 0; i < this.httpThrottlingObservers_.length; ++i) {
535    this.httpThrottlingObservers_[i].onHttpThrottlingEnabledPrefChanged(
536        enabled);
537  }
538};
539
540BrowserBridge.prototype.loadedLogFile = function(logFileContents) {
541  var match;
542  // Replace carriage returns with linebreaks and then split around linebreaks.
543  var lines = logFileContents.replace(/\r/g, '\n').split('\n');
544  var entries = [];
545  var numInvalidLines = 0;
546
547  for (var i = 0; i < lines.length; ++i) {
548    if (lines[i].trim().length == 0)
549      continue;
550    // Parse all valid lines, skipping any others.
551    try {
552      var entry = JSON.parse(lines[i]);
553      if (entry &&
554          typeof(entry) == 'object' &&
555          entry.phase != undefined &&
556          entry.source != undefined &&
557          entry.time != undefined &&
558          entry.type != undefined) {
559        entries.push(entry);
560        continue;
561      }
562    } catch (err) {
563    }
564    ++numInvalidLines;
565    console.log('Unable to parse log line: ' + lines[i]);
566  }
567
568  if (entries.length == 0) {
569    window.alert('Loading log file failed.');
570    return;
571  }
572
573  this.deleteAllEvents();
574
575  this.setIsViewingLogFile_(true);
576
577  var validEntries = [];
578  for (var i = 0; i < entries.length; ++i) {
579    entries[i].wasPassivelyCaptured = true;
580    if (LogEventType[entries[i].type] != undefined &&
581        LogSourceType[entries[i].source.type] != undefined &&
582        LogEventPhase[entries[i].phase] != undefined) {
583      entries[i].type = LogEventType[entries[i].type];
584      entries[i].source.type = LogSourceType[entries[i].source.type];
585      entries[i].phase = LogEventPhase[entries[i].phase];
586      validEntries.push(entries[i]);
587    } else {
588      // TODO(mmenke):  Do something reasonable when the event type isn't
589      //                found, which could happen when event types are
590      //                removed or added between versions.  Could also happen
591      //                with source types, but less likely.
592      console.log(
593        'Unrecognized values in log entry: ' + JSON.stringify(entry));
594    }
595  }
596
597  this.numPassivelyCapturedEvents_ = validEntries.length;
598  this.addLogEntries(validEntries);
599
600  var numInvalidEntries = entries.length - validEntries.length;
601  if (numInvalidEntries > 0 || numInvalidLines > 0) {
602    window.alert(
603      numInvalidLines.toString() +
604      ' could not be parsed as JSON strings, and ' +
605      numInvalidEntries.toString() +
606      ' entries don\'t have valid data.\n\n' +
607      'Unparseable lines may indicate log file corruption.\n' +
608      'Entries with invalid data may be caused by version differences.\n\n' +
609      'See console for more information.');
610  }
611}
612
613//------------------------------------------------------------------------------
614
615/**
616 * Sets the |categoryTabSwitcher_| of BrowserBridge.  Since views depend on
617 * g_browser being initialized, have to have a BrowserBridge prior to tab
618 * construction.
619 */
620BrowserBridge.prototype.setTabSwitcher = function(categoryTabSwitcher) {
621  this.categoryTabSwitcher_ = categoryTabSwitcher;
622};
623
624/**
625 * Adds a listener of log entries. |observer| will be called back when new log
626 * data arrives, through:
627 *
628 *   observer.onLogEntryAdded(logEntry)
629 */
630BrowserBridge.prototype.addLogObserver = function(observer) {
631  this.logObservers_.push(observer);
632};
633
634/**
635 * Adds a listener of the proxy settings. |observer| will be called back when
636 * data is received, through:
637 *
638 *   observer.onProxySettingsChanged(proxySettings)
639 *
640 * |proxySettings| is a dictionary with (up to) two properties:
641 *
642 *   "original"  -- The settings that chrome was configured to use
643 *                  (i.e. system settings.)
644 *   "effective" -- The "effective" proxy settings that chrome is using.
645 *                  (decides between the manual/automatic modes of the
646 *                  fetched settings).
647 *
648 * Each of these two configurations is formatted as a string, and may be
649 * omitted if not yet initialized.
650 *
651 * TODO(eroman): send a dictionary instead.
652 */
653BrowserBridge.prototype.addProxySettingsObserver = function(observer) {
654  this.pollableDataHelpers_.proxySettings.addObserver(observer);
655};
656
657/**
658 * Adds a listener of the proxy settings. |observer| will be called back when
659 * data is received, through:
660 *
661 *   observer.onBadProxiesChanged(badProxies)
662 *
663 * |badProxies| is an array, where each entry has the property:
664 *   badProxies[i].proxy_uri: String identify the proxy.
665 *   badProxies[i].bad_until: The time when the proxy stops being considered
666 *                            bad. Note the time is in time ticks.
667 */
668BrowserBridge.prototype.addBadProxiesObserver = function(observer) {
669  this.pollableDataHelpers_.badProxies.addObserver(observer);
670};
671
672/**
673 * Adds a listener of the host resolver info. |observer| will be called back
674 * when data is received, through:
675 *
676 *   observer.onHostResolverInfoChanged(hostResolverInfo)
677 */
678BrowserBridge.prototype.addHostResolverInfoObserver = function(observer) {
679  this.pollableDataHelpers_.hostResolverInfo.addObserver(observer);
680};
681
682/**
683 * Adds a listener of the socket pool. |observer| will be called back
684 * when data is received, through:
685 *
686 *   observer.onSocketPoolInfoChanged(socketPoolInfo)
687 */
688BrowserBridge.prototype.addSocketPoolInfoObserver = function(observer) {
689  this.pollableDataHelpers_.socketPoolInfo.addObserver(observer);
690};
691
692/**
693 * Adds a listener of the SPDY info. |observer| will be called back
694 * when data is received, through:
695 *
696 *   observer.onSpdySessionInfoChanged(spdySessionInfo)
697 */
698BrowserBridge.prototype.addSpdySessionInfoObserver = function(observer) {
699  this.pollableDataHelpers_.spdySessionInfo.addObserver(observer);
700};
701
702/**
703 * Adds a listener of the SPDY status. |observer| will be called back
704 * when data is received, through:
705 *
706 *   observer.onSpdyStatusChanged(spdyStatus)
707 */
708BrowserBridge.prototype.addSpdyStatusObserver = function(observer) {
709  this.pollableDataHelpers_.spdyStatus.addObserver(observer);
710};
711
712/**
713 * Adds a listener of the AlternateProtocolMappings. |observer| will be called
714 * back when data is received, through:
715 *
716 *   observer.onSpdyAlternateProtocolMappingsChanged(
717 *       spdyAlternateProtocolMappings)
718 */
719BrowserBridge.prototype.addSpdyAlternateProtocolMappingsObserver =
720    function(observer) {
721  this.pollableDataHelpers_.spdyAlternateProtocolMappings.addObserver(observer);
722};
723
724/**
725 * Adds a listener of the service providers info. |observer| will be called
726 * back when data is received, through:
727 *
728 *   observer.onServiceProvidersChanged(serviceProviders)
729 */
730BrowserBridge.prototype.addServiceProvidersObserver = function(observer) {
731  this.pollableDataHelpers_.serviceProviders.addObserver(observer);
732};
733
734/**
735 * Adds a listener for the progress of the connection tests.
736 * The observer will be called back with:
737 *
738 *   observer.onStartedConnectionTestSuite();
739 *   observer.onStartedConnectionTestExperiment(experiment);
740 *   observer.onCompletedConnectionTestExperiment(experiment, result);
741 *   observer.onCompletedConnectionTestSuite();
742 */
743BrowserBridge.prototype.addConnectionTestsObserver = function(observer) {
744  this.connectionTestsObservers_.push(observer);
745};
746
747/**
748 * Adds a listener for the http cache info results.
749 * The observer will be called back with:
750 *
751 *   observer.onHttpCacheInfoChanged(info);
752 */
753BrowserBridge.prototype.addHttpCacheInfoObserver = function(observer) {
754  this.pollableDataHelpers_.httpCacheInfo.addObserver(observer);
755};
756
757/**
758 * Adds a listener for the results of HSTS (HTTPS Strict Transport Security)
759 * queries. The observer will be called back with:
760 *
761 *   observer.onHSTSQueryResult(result);
762 */
763BrowserBridge.prototype.addHSTSObserver = function(observer) {
764  this.hstsObservers_.push(observer);
765};
766
767/**
768 * Adds a listener for HTTP throttling-related events. |observer| will be called
769 * back when HTTP throttling is enabled/disabled, through:
770 *
771 *   observer.onHttpThrottlingEnabledPrefChanged(enabled);
772 */
773BrowserBridge.prototype.addHttpThrottlingObserver = function(observer) {
774  this.httpThrottlingObservers_.push(observer);
775};
776
777/**
778 * The browser gives us times in terms of "time ticks" in milliseconds.
779 * This function converts the tick count to a Date() object.
780 *
781 * @param {String} timeTicks.
782 * @returns {Date} The time that |timeTicks| represents.
783 */
784BrowserBridge.prototype.convertTimeTicksToDate = function(timeTicks) {
785  // Note that the subtraction by 0 is to cast to a number (probably a float
786  // since the numbers are big).
787  var timeStampMs = (this.timeTickOffset_ - 0) + (timeTicks - 0);
788  var d = new Date();
789  d.setTime(timeStampMs);
790  return d;
791};
792
793/**
794 * Returns a list of all captured events.
795 */
796BrowserBridge.prototype.getAllCapturedEvents = function() {
797  return this.capturedEvents_;
798};
799
800/**
801 * Returns the number of events that were captured while we were
802 * listening for events.
803 */
804BrowserBridge.prototype.getNumActivelyCapturedEvents = function() {
805  return this.capturedEvents_.length - this.numPassivelyCapturedEvents_;
806};
807
808/**
809 * Returns the number of events that were captured passively by the
810 * browser prior to when the net-internals page was started.
811 */
812BrowserBridge.prototype.getNumPassivelyCapturedEvents = function() {
813  return this.numPassivelyCapturedEvents_;
814};
815
816/**
817 * Sends each entry to all log observers, and updates |capturedEvents_|.
818 * Also assigns unique ids to log entries without a source.
819 */
820BrowserBridge.prototype.addLogEntries = function(logEntries) {
821  for (var e = 0; e < logEntries.length; ++e) {
822    var logEntry = logEntries[e];
823
824    // Assign unique ID, if needed.
825    if (logEntry.source.id == 0) {
826      logEntry.source.id = this.nextSourcelessEventId_;
827      --this.nextSourcelessEventId_;
828    }
829    this.capturedEvents_.push(logEntry);
830    for (var i = 0; i < this.logObservers_.length; ++i)
831      this.logObservers_[i].onLogEntryAdded(logEntry);
832  }
833};
834
835/**
836 * Deletes captured events with source IDs in |sourceIds|.
837 */
838BrowserBridge.prototype.deleteEventsBySourceId = function(sourceIds) {
839  var sourceIdDict = {};
840  for (var i = 0; i < sourceIds.length; i++)
841    sourceIdDict[sourceIds[i]] = true;
842
843  var newEventList = [];
844  for (var i = 0; i < this.capturedEvents_.length; ++i) {
845    var id = this.capturedEvents_[i].source.id;
846    if (id in sourceIdDict) {
847      if (this.capturedEvents_[i].wasPassivelyCaptured)
848        --this.numPassivelyCapturedEvents_;
849      continue;
850    }
851    newEventList.push(this.capturedEvents_[i]);
852  }
853  this.capturedEvents_ = newEventList;
854
855  for (var i = 0; i < this.logObservers_.length; ++i)
856    this.logObservers_[i].onLogEntriesDeleted(sourceIds);
857};
858
859/**
860 * Deletes all captured events.
861 */
862BrowserBridge.prototype.deleteAllEvents = function() {
863  this.capturedEvents_ = [];
864  this.numPassivelyCapturedEvents_ = 0;
865  for (var i = 0; i < this.logObservers_.length; ++i)
866    this.logObservers_[i].onAllLogEntriesDeleted();
867};
868
869/**
870 * Sets the value of |enableSecurityStripping_| and informs log observers
871 * of the change.
872 */
873BrowserBridge.prototype.setSecurityStripping =
874    function(enableSecurityStripping) {
875  this.enableSecurityStripping_ = enableSecurityStripping;
876  for (var i = 0; i < this.logObservers_.length; ++i) {
877    if (this.logObservers_[i].onSecurityStrippingChanged)
878      this.logObservers_[i].onSecurityStrippingChanged();
879  }
880};
881
882/**
883 * Returns whether or not cookies and authentication information should be
884 * displayed for events that contain them.
885 */
886BrowserBridge.prototype.getSecurityStripping = function() {
887  return this.enableSecurityStripping_;
888};
889
890/**
891 * Informs log observers whether or not future events will be from a log file.
892 * Hides all tabs except the events and data tabs when viewing a log file, shows
893 * them all otherwise.
894 */
895BrowserBridge.prototype.setIsViewingLogFile_ = function(isViewingLogFile) {
896  this.isViewingLogFile_ = isViewingLogFile;
897  var tabIds = this.categoryTabSwitcher_.getAllTabIds();
898
899  for (var i = 0; i < this.logObservers_.length; ++i)
900    this.logObservers_[i].onSetIsViewingLogFile(isViewingLogFile);
901
902  // Shows/hides tabs not used when viewing a log file.
903  for (var i = 0; i < tabIds.length; ++i) {
904    if (tabIds[i] == 'eventsTab' || tabIds[i] == 'dataTab')
905      continue;
906    this.categoryTabSwitcher_.showTabHandleNode(tabIds[i], !isViewingLogFile);
907  }
908
909  if (isViewingLogFile) {
910    var activeTab = this.categoryTabSwitcher_.findActiveTab();
911    if (activeTab.id != 'eventsTab')
912      this.categoryTabSwitcher_.switchToTab('dataTab', null);
913  }
914};
915
916/**
917 * Returns true if a log file is currently being viewed.
918 */
919BrowserBridge.prototype.isViewingLogFile = function() {
920  return this.isViewingLogFile_;
921};
922
923/**
924 * If |force| is true, calls all startUpdate functions.  Otherwise, just
925 * runs updates with active observers.
926 */
927BrowserBridge.prototype.checkForUpdatedInfo = function(force) {
928  for (name in this.pollableDataHelpers_) {
929    var helper = this.pollableDataHelpers_[name];
930    if (force || helper.hasActiveObserver())
931      helper.startUpdate();
932  }
933};
934
935/**
936 * Calls all startUpdate functions and, if |callback| is non-null,
937 * calls it with the results of all updates.
938 */
939BrowserBridge.prototype.updateAllInfo = function(callback) {
940  if (callback)
941    new UpdateAllObserver(callback, this.pollableDataHelpers_);
942  this.checkForUpdatedInfo(true);
943};
944
945/**
946 * This is a helper class used by BrowserBridge, to keep track of:
947 *   - the list of observers interested in some piece of data.
948 *   - the last known value of that piece of data.
949 *   - the name of the callback method to invoke on observers.
950 *   - the update function.
951 * @constructor
952 */
953function PollableDataHelper(observerMethodName, startUpdateFunction) {
954  this.observerMethodName_ = observerMethodName;
955  this.startUpdate = startUpdateFunction;
956  this.observerInfos_ = [];
957}
958
959PollableDataHelper.prototype.getObserverMethodName = function() {
960  return this.observerMethodName_;
961};
962
963/**
964 * This is a helper class used by PollableDataHelper, to keep track of
965 * each observer and whether or not it has received any data.  The
966 * latter is used to make sure that new observers get sent data on the
967 * update following their creation.
968 * @constructor
969 */
970function ObserverInfo(observer) {
971  this.observer = observer;
972  this.hasReceivedData = false;
973}
974
975PollableDataHelper.prototype.addObserver = function(observer) {
976  this.observerInfos_.push(new ObserverInfo(observer));
977};
978
979PollableDataHelper.prototype.removeObserver = function(observer) {
980  for (var i = 0; i < this.observerInfos_.length; ++i) {
981    if (this.observerInfos_[i].observer == observer) {
982      this.observerInfos_.splice(i, 1);
983      return;
984    }
985  }
986};
987
988/**
989 * Helper function to handle calling all the observers, but ONLY if the data has
990 * actually changed since last time or the observer has yet to receive any data.
991 * This is used for data we received from browser on an update loop.
992 */
993PollableDataHelper.prototype.update = function(data) {
994  var prevData = this.currentData_;
995  var changed = false;
996
997  // If the data hasn't changed since last time, will only need to notify
998  // observers that have not yet received any data.
999  if (!prevData || JSON.stringify(prevData) != JSON.stringify(data)) {
1000    changed = true;
1001    this.currentData_ = data;
1002  }
1003
1004  // Notify the observers of the change, as needed.
1005  for (var i = 0; i < this.observerInfos_.length; ++i) {
1006    var observerInfo = this.observerInfos_[i];
1007    if (changed || !observerInfo.hasReceivedData) {
1008      observerInfo.observer[this.observerMethodName_](this.currentData_);
1009      observerInfo.hasReceivedData = true;
1010    }
1011  }
1012};
1013
1014/**
1015 * Returns true if one of the observers actively wants the data
1016 * (i.e. is visible).
1017 */
1018PollableDataHelper.prototype.hasActiveObserver = function() {
1019  for (var i = 0; i < this.observerInfos_.length; ++i) {
1020    if (this.observerInfos_[i].observer.isActive())
1021      return true;
1022  }
1023  return false;
1024};
1025
1026/**
1027 * This is a helper class used by BrowserBridge to send data to
1028 * a callback once data from all polls has been received.
1029 *
1030 * It works by keeping track of how many polling functions have
1031 * yet to receive data, and recording the data as it it received.
1032 *
1033 * @constructor
1034 */
1035function UpdateAllObserver(callback, pollableDataHelpers) {
1036  this.callback_ = callback;
1037  this.observingCount_ = 0;
1038  this.updatedData_ = {};
1039
1040  for (name in pollableDataHelpers) {
1041    ++this.observingCount_;
1042    var helper = pollableDataHelpers[name];
1043    helper.addObserver(this);
1044    this[helper.getObserverMethodName()] =
1045        this.onDataReceived_.bind(this, helper, name);
1046  }
1047}
1048
1049UpdateAllObserver.prototype.isActive = function() {
1050  return true;
1051};
1052
1053UpdateAllObserver.prototype.onDataReceived_ = function(helper, name, data) {
1054  helper.removeObserver(this);
1055  --this.observingCount_;
1056  this.updatedData_[name] = data;
1057  if (this.observingCount_ == 0)
1058    this.callback_(this.updatedData_);
1059};
1060