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¶m1=value1¶m2=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