Home | History | Annotate | Download | only in net_internals
      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  */
      8 var LogEventType = null;
      9 var LogEventPhase = null;
     10 var ClientInfo = null;
     11 var LogSourceType = null;
     12 var LogLevelType = null;
     13 var NetError = null;
     14 var LoadFlag = null;
     15 var AddressFamily = null;
     16 
     17 /**
     18  * Object to communicate between the renderer and the browser.
     19  * @type {!BrowserBridge}
     20  */
     21 var g_browser = null;
     22 
     23 /**
     24  * Main entry point. called once the page has loaded.
     25  */
     26 function 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  */
    183 function 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  */
    249 function 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  */
    275 BrowserBridge.POLL_INTERVAL_MS = 5000;
    276 
    277 //------------------------------------------------------------------------------
    278 // Messages sent to the browser
    279 //------------------------------------------------------------------------------
    280 
    281 BrowserBridge.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 
    291 BrowserBridge.prototype.isPlatformWindows = function() {
    292   return /Win/.test(navigator.platform);
    293 };
    294 
    295 BrowserBridge.prototype.sendGetProxySettings = function() {
    296   // The browser will call receivedProxySettings on completion.
    297   chrome.send('getProxySettings');
    298 };
    299 
    300 BrowserBridge.prototype.sendReloadProxySettings = function() {
    301   chrome.send('reloadProxySettings');
    302 };
    303 
    304 BrowserBridge.prototype.sendGetBadProxies = function() {
    305   // The browser will call receivedBadProxies on completion.
    306   chrome.send('getBadProxies');
    307 };
    308 
    309 BrowserBridge.prototype.sendGetHostResolverInfo = function() {
    310   // The browser will call receivedHostResolverInfo on completion.
    311   chrome.send('getHostResolverInfo');
    312 };
    313 
    314 BrowserBridge.prototype.sendClearBadProxies = function() {
    315   chrome.send('clearBadProxies');
    316 };
    317 
    318 BrowserBridge.prototype.sendClearHostResolverCache = function() {
    319   chrome.send('clearHostResolverCache');
    320 };
    321 
    322 BrowserBridge.prototype.sendStartConnectionTests = function(url) {
    323   chrome.send('startConnectionTests', [url]);
    324 };
    325 
    326 BrowserBridge.prototype.sendHSTSQuery = function(domain) {
    327   chrome.send('hstsQuery', [domain]);
    328 };
    329 
    330 BrowserBridge.prototype.sendHSTSAdd = function(domain,
    331                                                include_subdomains,
    332                                                pins) {
    333   chrome.send('hstsAdd', [domain, include_subdomains, pins]);
    334 };
    335 
    336 BrowserBridge.prototype.sendHSTSDelete = function(domain) {
    337   chrome.send('hstsDelete', [domain]);
    338 };
    339 
    340 BrowserBridge.prototype.sendGetHttpCacheInfo = function() {
    341   chrome.send('getHttpCacheInfo');
    342 };
    343 
    344 BrowserBridge.prototype.sendGetSocketPoolInfo = function() {
    345   chrome.send('getSocketPoolInfo');
    346 };
    347 
    348 BrowserBridge.prototype.sendCloseIdleSockets = function() {
    349   chrome.send('closeIdleSockets');
    350 };
    351 
    352 BrowserBridge.prototype.sendFlushSocketPools = function() {
    353   chrome.send('flushSocketPools');
    354 };
    355 
    356 BrowserBridge.prototype.sendGetSpdySessionInfo = function() {
    357   chrome.send('getSpdySessionInfo');
    358 };
    359 
    360 BrowserBridge.prototype.sendGetSpdyStatus = function() {
    361   chrome.send('getSpdyStatus');
    362 };
    363 
    364 BrowserBridge.prototype.sendGetSpdyAlternateProtocolMappings = function() {
    365   chrome.send('getSpdyAlternateProtocolMappings');
    366 };
    367 
    368 BrowserBridge.prototype.sendGetServiceProviders = function() {
    369   chrome.send('getServiceProviders');
    370 };
    371 
    372 BrowserBridge.prototype.enableIPv6 = function() {
    373   chrome.send('enableIPv6');
    374 };
    375 
    376 BrowserBridge.prototype.setLogLevel = function(logLevel) {
    377   chrome.send('setLogLevel', ['' + logLevel]);
    378 };
    379 
    380 BrowserBridge.prototype.enableHttpThrottling = function(enable) {
    381   chrome.send('enableHttpThrottling', [enable]);
    382 };
    383 
    384 BrowserBridge.prototype.loadLogFile = function() {
    385   chrome.send('loadLogFile');
    386 }
    387 
    388 //------------------------------------------------------------------------------
    389 // Messages received from the browser
    390 //------------------------------------------------------------------------------
    391 
    392 BrowserBridge.prototype.receivedLogEntries = function(logEntries) {
    393   // Does nothing if viewing a log file.
    394   if (this.isViewingLogFile_)
    395     return;
    396   this.addLogEntries(logEntries);
    397 };
    398 
    399 BrowserBridge.prototype.receivedLogEventTypeConstants = function(constantsMap) {
    400   LogEventType = constantsMap;
    401 };
    402 
    403 BrowserBridge.prototype.receivedClientInfo =
    404 function(info) {
    405   ClientInfo = info;
    406 };
    407 
    408 BrowserBridge.prototype.receivedLogEventPhaseConstants =
    409 function(constantsMap) {
    410   LogEventPhase = constantsMap;
    411 };
    412 
    413 BrowserBridge.prototype.receivedLogSourceTypeConstants =
    414 function(constantsMap) {
    415   LogSourceType = constantsMap;
    416 };
    417 
    418 BrowserBridge.prototype.receivedLogLevelConstants =
    419 function(constantsMap) {
    420   LogLevelType = constantsMap;
    421 };
    422 
    423 BrowserBridge.prototype.receivedLoadFlagConstants = function(constantsMap) {
    424   LoadFlag = constantsMap;
    425 };
    426 
    427 BrowserBridge.prototype.receivedNetErrorConstants = function(constantsMap) {
    428   NetError = constantsMap;
    429 };
    430 
    431 BrowserBridge.prototype.receivedAddressFamilyConstants =
    432 function(constantsMap) {
    433   AddressFamily = constantsMap;
    434 };
    435 
    436 BrowserBridge.prototype.receivedTimeTickOffset = function(timeTickOffset) {
    437   this.timeTickOffset_ = timeTickOffset;
    438 };
    439 
    440 BrowserBridge.prototype.receivedProxySettings = function(proxySettings) {
    441   this.pollableDataHelpers_.proxySettings.update(proxySettings);
    442 };
    443 
    444 BrowserBridge.prototype.receivedBadProxies = function(badProxies) {
    445   this.pollableDataHelpers_.badProxies.update(badProxies);
    446 };
    447 
    448 BrowserBridge.prototype.receivedHostResolverInfo =
    449 function(hostResolverInfo) {
    450   this.pollableDataHelpers_.hostResolverInfo.update(hostResolverInfo);
    451 };
    452 
    453 BrowserBridge.prototype.receivedSocketPoolInfo = function(socketPoolInfo) {
    454   this.pollableDataHelpers_.socketPoolInfo.update(socketPoolInfo);
    455 };
    456 
    457 BrowserBridge.prototype.receivedSpdySessionInfo = function(spdySessionInfo) {
    458   this.pollableDataHelpers_.spdySessionInfo.update(spdySessionInfo);
    459 };
    460 
    461 BrowserBridge.prototype.receivedSpdyStatus = function(spdyStatus) {
    462   this.pollableDataHelpers_.spdyStatus.update(spdyStatus);
    463 };
    464 
    465 BrowserBridge.prototype.receivedSpdyAlternateProtocolMappings =
    466     function(spdyAlternateProtocolMappings) {
    467   this.pollableDataHelpers_.spdyAlternateProtocolMappings.update(
    468       spdyAlternateProtocolMappings);
    469 };
    470 
    471 BrowserBridge.prototype.receivedServiceProviders = function(serviceProviders) {
    472   this.pollableDataHelpers_.serviceProviders.update(serviceProviders);
    473 };
    474 
    475 BrowserBridge.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 
    497 BrowserBridge.prototype.receivedStartConnectionTestSuite = function() {
    498   for (var i = 0; i < this.connectionTestsObservers_.length; ++i)
    499     this.connectionTestsObservers_[i].onStartedConnectionTestSuite();
    500 };
    501 
    502 BrowserBridge.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 
    510 BrowserBridge.prototype.receivedCompletedConnectionTestExperiment =
    511 function(info) {
    512   for (var i = 0; i < this.connectionTestsObservers_.length; ++i) {
    513     this.connectionTestsObservers_[i].onCompletedConnectionTestExperiment(
    514         info.experiment, info.result);
    515   }
    516 };
    517 
    518 BrowserBridge.prototype.receivedCompletedConnectionTestSuite = function() {
    519   for (var i = 0; i < this.connectionTestsObservers_.length; ++i)
    520     this.connectionTestsObservers_[i].onCompletedConnectionTestSuite();
    521 };
    522 
    523 BrowserBridge.prototype.receivedHSTSResult = function(info) {
    524   for (var i = 0; i < this.hstsObservers_.length; ++i)
    525     this.hstsObservers_[i].onHSTSQueryResult(info);
    526 };
    527 
    528 BrowserBridge.prototype.receivedHttpCacheInfo = function(info) {
    529   this.pollableDataHelpers_.httpCacheInfo.update(info);
    530 };
    531 
    532 BrowserBridge.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 
    540 BrowserBridge.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  */
    620 BrowserBridge.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  */
    630 BrowserBridge.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  */
    653 BrowserBridge.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  */
    668 BrowserBridge.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  */
    678 BrowserBridge.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  */
    688 BrowserBridge.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  */
    698 BrowserBridge.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  */
    708 BrowserBridge.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  */
    719 BrowserBridge.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  */
    730 BrowserBridge.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  */
    743 BrowserBridge.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  */
    753 BrowserBridge.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  */
    763 BrowserBridge.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  */
    773 BrowserBridge.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  */
    784 BrowserBridge.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  */
    796 BrowserBridge.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  */
    804 BrowserBridge.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  */
    812 BrowserBridge.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  */
    820 BrowserBridge.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  */
    838 BrowserBridge.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  */
    862 BrowserBridge.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  */
    873 BrowserBridge.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  */
    886 BrowserBridge.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  */
    895 BrowserBridge.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  */
    919 BrowserBridge.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  */
    927 BrowserBridge.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  */
    939 BrowserBridge.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  */
    953 function PollableDataHelper(observerMethodName, startUpdateFunction) {
    954   this.observerMethodName_ = observerMethodName;
    955   this.startUpdate = startUpdateFunction;
    956   this.observerInfos_ = [];
    957 }
    958 
    959 PollableDataHelper.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  */
    970 function ObserverInfo(observer) {
    971   this.observer = observer;
    972   this.hasReceivedData = false;
    973 }
    974 
    975 PollableDataHelper.prototype.addObserver = function(observer) {
    976   this.observerInfos_.push(new ObserverInfo(observer));
    977 };
    978 
    979 PollableDataHelper.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  */
    993 PollableDataHelper.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  */
   1018 PollableDataHelper.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  */
   1035 function 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 
   1049 UpdateAllObserver.prototype.isActive = function() {
   1050   return true;
   1051 };
   1052 
   1053 UpdateAllObserver.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