main.js revision 3f50c38dc070f4bb515c1b64450dae14f316474e
1// Copyright (c) 2010 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', 79 'passivelyCapturedCount', 80 'activelyCapturedCount', 81 'dataViewDeleteAll'); 82 83 // Create a view which will display the results and controls for connection 84 // tests. 85 var testView = new TestView('testTabContent', 'testUrlInput', 86 'connectionTestsForm', 'testSummary'); 87 88 var httpCacheView = new HttpCacheView('httpCacheTabContent', 89 'httpCacheStats'); 90 91 var socketsView = new SocketsView('socketsTabContent', 92 'socketPoolDiv', 93 'socketPoolGroupsDiv'); 94 95 var spdyView = new SpdyView('spdyTabContent', 96 'spdySessionNoneSpan', 97 'spdySessionLinkSpan', 98 'spdySessionDiv'); 99 100 101 var serviceView; 102 if (g_browser.isPlatformWindows()) { 103 serviceView = new ServiceProvidersView('serviceProvidersTab', 104 'serviceProvidersTabContent', 105 'serviceProvidersTbody', 106 'namespaceProvidersTbody'); 107 } 108 109 // Create a view which lets you tab between the different sub-views. 110 var categoryTabSwitcher = new TabSwitcherView('categoryTabHandles'); 111 112 // Populate the main tabs. 113 categoryTabSwitcher.addTab('eventsTab', eventsView, false); 114 categoryTabSwitcher.addTab('proxyTab', proxyView, false); 115 categoryTabSwitcher.addTab('dnsTab', dnsView, false); 116 categoryTabSwitcher.addTab('socketsTab', socketsView, false); 117 categoryTabSwitcher.addTab('spdyTab', spdyView, false); 118 categoryTabSwitcher.addTab('httpCacheTab', httpCacheView, false); 119 categoryTabSwitcher.addTab('dataTab', dataView, false); 120 if (g_browser.isPlatformWindows()) 121 categoryTabSwitcher.addTab('serviceProvidersTab', serviceView, false); 122 categoryTabSwitcher.addTab('testTab', testView, false); 123 124 // Build a map from the anchor name of each tab handle to its "tab ID". 125 // We will consider navigations to the #hash as a switch tab request. 126 var anchorMap = {}; 127 var tabIds = categoryTabSwitcher.getAllTabIds(); 128 for (var i = 0; i < tabIds.length; ++i) { 129 var aNode = document.getElementById(tabIds[i]); 130 anchorMap[aNode.hash] = tabIds[i]; 131 } 132 // Default the empty hash to the data tab. 133 anchorMap['#'] = anchorMap[''] = 'dataTab'; 134 135 window.onhashchange = onUrlHashChange.bind(null, anchorMap, 136 categoryTabSwitcher); 137 138 // Make this category tab widget the primary view, that fills the whole page. 139 var windowView = new WindowView(categoryTabSwitcher); 140 141 // Trigger initial layout. 142 windowView.resetGeometry(); 143 144 // Select the initial view based on the current URL. 145 window.onhashchange(); 146 147 // Tell the browser that we are ready to start receiving log events. 148 g_browser.sendReady(); 149} 150 151/** 152 * This class provides a "bridge" for communicating between the javascript and 153 * the browser. 154 * 155 * @constructor 156 */ 157function BrowserBridge() { 158 // List of observers for various bits of browser state. 159 this.logObservers_ = []; 160 this.connectionTestsObservers_ = []; 161 162 this.pollableDataHelpers_ = {}; 163 this.pollableDataHelpers_.proxySettings = 164 new PollableDataHelper('onProxySettingsChanged', 165 this.sendGetProxySettings.bind(this)); 166 this.pollableDataHelpers_.badProxies = 167 new PollableDataHelper('onBadProxiesChanged', 168 this.sendGetBadProxies.bind(this)); 169 this.pollableDataHelpers_.httpCacheInfo = 170 new PollableDataHelper('onHttpCacheInfoChanged', 171 this.sendGetHttpCacheInfo.bind(this)); 172 this.pollableDataHelpers_.hostResolverInfo = 173 new PollableDataHelper('onHostResolverInfoChanged', 174 this.sendGetHostResolverInfo.bind(this)); 175 this.pollableDataHelpers_.socketPoolInfo = 176 new PollableDataHelper('onSocketPoolInfoChanged', 177 this.sendGetSocketPoolInfo.bind(this)); 178 this.pollableDataHelpers_.spdySessionInfo = 179 new PollableDataHelper('onSpdySessionInfoChanged', 180 this.sendGetSpdySessionInfo.bind(this)); 181 if (this.isPlatformWindows()) { 182 this.pollableDataHelpers_.serviceProviders = 183 new PollableDataHelper('onServiceProvidersChanged', 184 this.sendGetServiceProviders.bind(this)); 185 } 186 187 // Cache of the data received. 188 this.numPassivelyCapturedEvents_ = 0; 189 this.capturedEvents_ = []; 190 191 // Next unique id to be assigned to a log entry without a source. 192 // Needed to simplify deletion, identify associated GUI elements, etc. 193 this.nextSourcelessEventId_ = -1; 194} 195 196/* 197 * Takes the current hash in form of "#tab¶m1=value1¶m2=value2&...". 198 * Puts the parameters in an object, and passes the resulting object to 199 * |categoryTabSwitcher|. Uses tab and |anchorMap| to find a tab ID, 200 * which it also passes to the tab switcher. 201 * 202 * Parameters and values are decoded with decodeURIComponent(). 203 */ 204function onUrlHashChange(anchorMap, categoryTabSwitcher) { 205 var parameters = window.location.hash.split('&'); 206 207 var tabId = anchorMap[parameters[0]]; 208 if (!tabId) 209 return; 210 211 // Split each string except the first around the '='. 212 var paramDict = null; 213 for (var i = 1; i < parameters.length; i++) { 214 var paramStrings = parameters[i].split('='); 215 if (paramStrings.length != 2) 216 continue; 217 if (paramDict == null) 218 paramDict = {}; 219 var key = decodeURIComponent(paramStrings[0]); 220 var value = decodeURIComponent(paramStrings[1]); 221 paramDict[key] = value; 222 } 223 224 categoryTabSwitcher.switchToTab(tabId, paramDict); 225} 226 227/** 228 * Delay in milliseconds between updates of certain browser information. 229 */ 230BrowserBridge.POLL_INTERVAL_MS = 5000; 231 232//------------------------------------------------------------------------------ 233// Messages sent to the browser 234//------------------------------------------------------------------------------ 235 236BrowserBridge.prototype.sendReady = function() { 237 chrome.send('notifyReady'); 238 239 // Some of the data we are interested is not currently exposed as a stream, 240 // so we will poll the browser to find out when it changes and then notify 241 // the observers. 242 window.setInterval(this.checkForUpdatedInfo.bind(this, false), 243 BrowserBridge.POLL_INTERVAL_MS); 244}; 245 246BrowserBridge.prototype.isPlatformWindows = function() { 247 return /Win/.test(navigator.platform); 248}; 249 250BrowserBridge.prototype.sendGetProxySettings = function() { 251 // The browser will call receivedProxySettings on completion. 252 chrome.send('getProxySettings'); 253}; 254 255BrowserBridge.prototype.sendReloadProxySettings = function() { 256 chrome.send('reloadProxySettings'); 257}; 258 259BrowserBridge.prototype.sendGetBadProxies = function() { 260 // The browser will call receivedBadProxies on completion. 261 chrome.send('getBadProxies'); 262}; 263 264BrowserBridge.prototype.sendGetHostResolverInfo = function() { 265 // The browser will call receivedHostResolverInfo on completion. 266 chrome.send('getHostResolverInfo'); 267}; 268 269BrowserBridge.prototype.sendClearBadProxies = function() { 270 chrome.send('clearBadProxies'); 271}; 272 273BrowserBridge.prototype.sendClearHostResolverCache = function() { 274 chrome.send('clearHostResolverCache'); 275}; 276 277BrowserBridge.prototype.sendStartConnectionTests = function(url) { 278 chrome.send('startConnectionTests', [url]); 279}; 280 281BrowserBridge.prototype.sendGetHttpCacheInfo = function() { 282 chrome.send('getHttpCacheInfo'); 283}; 284 285BrowserBridge.prototype.sendGetSocketPoolInfo = function() { 286 chrome.send('getSocketPoolInfo'); 287}; 288 289BrowserBridge.prototype.sendGetSpdySessionInfo = function() { 290 chrome.send('getSpdySessionInfo'); 291}; 292 293BrowserBridge.prototype.sendGetServiceProviders = function() { 294 chrome.send('getServiceProviders'); 295}; 296 297BrowserBridge.prototype.enableIPv6 = function() { 298 chrome.send('enableIPv6'); 299}; 300 301BrowserBridge.prototype.setLogLevel = function(logLevel) { 302 chrome.send('setLogLevel', ['' + logLevel]); 303} 304 305//------------------------------------------------------------------------------ 306// Messages received from the browser 307//------------------------------------------------------------------------------ 308 309BrowserBridge.prototype.receivedLogEntries = function(logEntries) { 310 for (var e = 0; e < logEntries.length; ++e) { 311 var logEntry = logEntries[e]; 312 313 // Assign unique ID, if needed. 314 if (logEntry.source.id == 0) { 315 logEntry.source.id = this.nextSourcelessEventId_; 316 --this.nextSourcelessEventId_; 317 } 318 this.capturedEvents_.push(logEntry); 319 for (var i = 0; i < this.logObservers_.length; ++i) 320 this.logObservers_[i].onLogEntryAdded(logEntry); 321 } 322}; 323 324BrowserBridge.prototype.receivedLogEventTypeConstants = function(constantsMap) { 325 LogEventType = constantsMap; 326}; 327 328BrowserBridge.prototype.receivedClientInfo = 329function(info) { 330 ClientInfo = info; 331}; 332 333BrowserBridge.prototype.receivedLogEventPhaseConstants = 334function(constantsMap) { 335 LogEventPhase = constantsMap; 336}; 337 338BrowserBridge.prototype.receivedLogSourceTypeConstants = 339function(constantsMap) { 340 LogSourceType = constantsMap; 341}; 342 343BrowserBridge.prototype.receivedLogLevelConstants = 344function(constantsMap) { 345 LogLevelType = constantsMap; 346}; 347 348BrowserBridge.prototype.receivedLoadFlagConstants = function(constantsMap) { 349 LoadFlag = constantsMap; 350}; 351 352BrowserBridge.prototype.receivedNetErrorConstants = function(constantsMap) { 353 NetError = constantsMap; 354}; 355 356BrowserBridge.prototype.receivedAddressFamilyConstants = 357function(constantsMap) { 358 AddressFamily = constantsMap; 359}; 360 361BrowserBridge.prototype.receivedTimeTickOffset = function(timeTickOffset) { 362 this.timeTickOffset_ = timeTickOffset; 363}; 364 365BrowserBridge.prototype.receivedProxySettings = function(proxySettings) { 366 this.pollableDataHelpers_.proxySettings.update(proxySettings); 367}; 368 369BrowserBridge.prototype.receivedBadProxies = function(badProxies) { 370 this.pollableDataHelpers_.badProxies.update(badProxies); 371}; 372 373BrowserBridge.prototype.receivedHostResolverInfo = 374function(hostResolverInfo) { 375 this.pollableDataHelpers_.hostResolverInfo.update(hostResolverInfo); 376}; 377 378BrowserBridge.prototype.receivedSocketPoolInfo = function(socketPoolInfo) { 379 this.pollableDataHelpers_.socketPoolInfo.update(socketPoolInfo); 380}; 381 382BrowserBridge.prototype.receivedSpdySessionInfo = function(spdySessionInfo) { 383 this.pollableDataHelpers_.spdySessionInfo.update(spdySessionInfo); 384}; 385 386BrowserBridge.prototype.receivedServiceProviders = function(serviceProviders) { 387 this.pollableDataHelpers_.serviceProviders.update(serviceProviders); 388}; 389 390BrowserBridge.prototype.receivedPassiveLogEntries = function(entries) { 391 // Due to an expected race condition, it is possible to receive actively 392 // captured log entries before the passively logged entries are received. 393 // 394 // When that happens, we create a copy of the actively logged entries, delete 395 // all entries, and, after handling all the passively logged entries, add back 396 // the deleted actively logged entries. 397 var earlyActivelyCapturedEvents = this.capturedEvents_.slice(0); 398 if (earlyActivelyCapturedEvents.length > 0) 399 this.deleteAllEvents(); 400 401 this.numPassivelyCapturedEvents_ = entries.length; 402 for (var i = 0; i < entries.length; ++i) 403 entries[i].wasPassivelyCaptured = true; 404 this.receivedLogEntries(entries); 405 406 // Add back early actively captured events, if any. 407 if (earlyActivelyCapturedEvents.length) 408 this.receivedLogEntries(earlyActivelyCapturedEvents); 409}; 410 411 412BrowserBridge.prototype.receivedStartConnectionTestSuite = function() { 413 for (var i = 0; i < this.connectionTestsObservers_.length; ++i) 414 this.connectionTestsObservers_[i].onStartedConnectionTestSuite(); 415}; 416 417BrowserBridge.prototype.receivedStartConnectionTestExperiment = function( 418 experiment) { 419 for (var i = 0; i < this.connectionTestsObservers_.length; ++i) { 420 this.connectionTestsObservers_[i].onStartedConnectionTestExperiment( 421 experiment); 422 } 423}; 424 425BrowserBridge.prototype.receivedCompletedConnectionTestExperiment = 426function(info) { 427 for (var i = 0; i < this.connectionTestsObservers_.length; ++i) { 428 this.connectionTestsObservers_[i].onCompletedConnectionTestExperiment( 429 info.experiment, info.result); 430 } 431}; 432 433BrowserBridge.prototype.receivedCompletedConnectionTestSuite = function() { 434 for (var i = 0; i < this.connectionTestsObservers_.length; ++i) 435 this.connectionTestsObservers_[i].onCompletedConnectionTestSuite(); 436}; 437 438BrowserBridge.prototype.receivedHttpCacheInfo = function(info) { 439 this.pollableDataHelpers_.httpCacheInfo.update(info); 440}; 441 442//------------------------------------------------------------------------------ 443 444/** 445 * Adds a listener of log entries. |observer| will be called back when new log 446 * data arrives, through: 447 * 448 * observer.onLogEntryAdded(logEntry) 449 */ 450BrowserBridge.prototype.addLogObserver = function(observer) { 451 this.logObservers_.push(observer); 452}; 453 454/** 455 * Adds a listener of the proxy settings. |observer| will be called back when 456 * data is received, through: 457 * 458 * observer.onProxySettingsChanged(proxySettings) 459 * 460 * |proxySettings| is a dictionary with (up to) two properties: 461 * 462 * "original" -- The settings that chrome was configured to use 463 * (i.e. system settings.) 464 * "effective" -- The "effective" proxy settings that chrome is using. 465 * (decides between the manual/automatic modes of the 466 * fetched settings). 467 * 468 * Each of these two configurations is formatted as a string, and may be 469 * omitted if not yet initialized. 470 * 471 * TODO(eroman): send a dictionary instead. 472 */ 473BrowserBridge.prototype.addProxySettingsObserver = function(observer) { 474 this.pollableDataHelpers_.proxySettings.addObserver(observer); 475}; 476 477/** 478 * Adds a listener of the proxy settings. |observer| will be called back when 479 * data is received, through: 480 * 481 * observer.onBadProxiesChanged(badProxies) 482 * 483 * |badProxies| is an array, where each entry has the property: 484 * badProxies[i].proxy_uri: String identify the proxy. 485 * badProxies[i].bad_until: The time when the proxy stops being considered 486 * bad. Note the time is in time ticks. 487 */ 488BrowserBridge.prototype.addBadProxiesObserver = function(observer) { 489 this.pollableDataHelpers_.badProxies.addObserver(observer); 490}; 491 492/** 493 * Adds a listener of the host resolver info. |observer| will be called back 494 * when data is received, through: 495 * 496 * observer.onHostResolverInfoChanged(hostResolverInfo) 497 */ 498BrowserBridge.prototype.addHostResolverInfoObserver = function(observer) { 499 this.pollableDataHelpers_.hostResolverInfo.addObserver(observer); 500}; 501 502/** 503 * Adds a listener of the socket pool. |observer| will be called back 504 * when data is received, through: 505 * 506 * observer.onSocketPoolInfoChanged(socketPoolInfo) 507 */ 508BrowserBridge.prototype.addSocketPoolInfoObserver = function(observer) { 509 this.pollableDataHelpers_.socketPoolInfo.addObserver(observer); 510}; 511 512/** 513 * Adds a listener of the SPDY info. |observer| will be called back 514 * when data is received, through: 515 * 516 * observer.onSpdySessionInfoChanged(spdySessionInfo) 517 */ 518BrowserBridge.prototype.addSpdySessionInfoObserver = function(observer) { 519 this.pollableDataHelpers_.spdySessionInfo.addObserver(observer); 520}; 521 522/** 523 * Adds a listener of the service providers info. |observer| will be called 524 * back when data is received, through: 525 * 526 * observer.onServiceProvidersChanged(serviceProviders) 527 */ 528BrowserBridge.prototype.addServiceProvidersObserver = function(observer) { 529 this.pollableDataHelpers_.serviceProviders.addObserver(observer); 530}; 531 532/** 533 * Adds a listener for the progress of the connection tests. 534 * The observer will be called back with: 535 * 536 * observer.onStartedConnectionTestSuite(); 537 * observer.onStartedConnectionTestExperiment(experiment); 538 * observer.onCompletedConnectionTestExperiment(experiment, result); 539 * observer.onCompletedConnectionTestSuite(); 540 */ 541BrowserBridge.prototype.addConnectionTestsObserver = function(observer) { 542 this.connectionTestsObservers_.push(observer); 543}; 544 545/** 546 * Adds a listener for the http cache info results. 547 * The observer will be called back with: 548 * 549 * observer.onHttpCacheInfoChanged(info); 550 */ 551BrowserBridge.prototype.addHttpCacheInfoObserver = function(observer) { 552 this.pollableDataHelpers_.httpCacheInfo.addObserver(observer); 553}; 554 555/** 556 * The browser gives us times in terms of "time ticks" in milliseconds. 557 * This function converts the tick count to a Date() object. 558 * 559 * @param {String} timeTicks. 560 * @returns {Date} The time that |timeTicks| represents. 561 */ 562BrowserBridge.prototype.convertTimeTicksToDate = function(timeTicks) { 563 // Note that the subtraction by 0 is to cast to a number (probably a float 564 // since the numbers are big). 565 var timeStampMs = (this.timeTickOffset_ - 0) + (timeTicks - 0); 566 var d = new Date(); 567 d.setTime(timeStampMs); 568 return d; 569}; 570 571/** 572 * Returns a list of all captured events. 573 */ 574BrowserBridge.prototype.getAllCapturedEvents = function() { 575 return this.capturedEvents_; 576}; 577 578/** 579 * Returns the number of events that were captured while we were 580 * listening for events. 581 */ 582BrowserBridge.prototype.getNumActivelyCapturedEvents = function() { 583 return this.capturedEvents_.length - this.numPassivelyCapturedEvents_; 584}; 585 586/** 587 * Returns the number of events that were captured passively by the 588 * browser prior to when the net-internals page was started. 589 */ 590BrowserBridge.prototype.getNumPassivelyCapturedEvents = function() { 591 return this.numPassivelyCapturedEvents_; 592}; 593 594/** 595 * Deletes captured events with source IDs in |sourceIds|. 596 */ 597BrowserBridge.prototype.deleteEventsBySourceId = function(sourceIds) { 598 var sourceIdDict = {}; 599 for (var i = 0; i < sourceIds.length; i++) 600 sourceIdDict[sourceIds[i]] = true; 601 602 var newEventList = []; 603 for (var i = 0; i < this.capturedEvents_.length; ++i) { 604 var id = this.capturedEvents_[i].source.id; 605 if (id in sourceIdDict) { 606 if (this.capturedEvents_[i].wasPassivelyCaptured) 607 --this.numPassivelyCapturedEvents_; 608 continue; 609 } 610 newEventList.push(this.capturedEvents_[i]); 611 } 612 this.capturedEvents_ = newEventList; 613 614 for (var i = 0; i < this.logObservers_.length; ++i) 615 this.logObservers_[i].onLogEntriesDeleted(sourceIds); 616}; 617 618/** 619 * Deletes all captured events. 620 */ 621BrowserBridge.prototype.deleteAllEvents = function() { 622 this.capturedEvents_ = []; 623 this.numPassivelyCapturedEvents_ = 0; 624 for (var i = 0; i < this.logObservers_.length; ++i) 625 this.logObservers_[i].onAllLogEntriesDeleted(); 626}; 627 628/** 629 * If |force| is true, calls all startUpdate functions. Otherwise, just 630 * runs updates with active observers. 631 */ 632BrowserBridge.prototype.checkForUpdatedInfo = function(force) { 633 for (name in this.pollableDataHelpers_) { 634 var helper = this.pollableDataHelpers_[name]; 635 if (force || helper.hasActiveObserver()) 636 helper.startUpdate(); 637 } 638}; 639 640/** 641 * Calls all startUpdate functions and, if |callback| is non-null, 642 * calls it with the results of all updates. 643 */ 644BrowserBridge.prototype.updateAllInfo = function(callback) { 645 if (callback) 646 new UpdateAllObserver(callback, this.pollableDataHelpers_); 647 this.checkForUpdatedInfo(true); 648}; 649 650/** 651 * This is a helper class used by BrowserBridge, to keep track of: 652 * - the list of observers interested in some piece of data. 653 * - the last known value of that piece of data. 654 * - the name of the callback method to invoke on observers. 655 * - the update function. 656 * @constructor 657 */ 658function PollableDataHelper(observerMethodName, startUpdateFunction) { 659 this.observerMethodName_ = observerMethodName; 660 this.startUpdate = startUpdateFunction; 661 this.observerInfos_ = []; 662} 663 664PollableDataHelper.prototype.getObserverMethodName = function() { 665 return this.observerMethodName_; 666}; 667 668/** 669 * This is a helper class used by PollableDataHelper, to keep track of 670 * each observer and whether or not it has received any data. The 671 * latter is used to make sure that new observers get sent data on the 672 * update following their creation. 673 * @constructor 674 */ 675function ObserverInfo(observer) { 676 this.observer = observer; 677 this.hasReceivedData = false; 678} 679 680PollableDataHelper.prototype.addObserver = function(observer) { 681 this.observerInfos_.push(new ObserverInfo(observer)); 682}; 683 684PollableDataHelper.prototype.removeObserver = function(observer) { 685 for (var i = 0; i < this.observerInfos_.length; ++i) { 686 if (this.observerInfos_[i].observer == observer) { 687 this.observerInfos_.splice(i, 1); 688 return; 689 } 690 } 691}; 692 693/** 694 * Helper function to handle calling all the observers, but ONLY if the data has 695 * actually changed since last time or the observer has yet to receive any data. 696 * This is used for data we received from browser on an update loop. 697 */ 698PollableDataHelper.prototype.update = function(data) { 699 var prevData = this.currentData_; 700 var changed = false; 701 702 // If the data hasn't changed since last time, will only need to notify 703 // observers that have not yet received any data. 704 if (!prevData || JSON.stringify(prevData) != JSON.stringify(data)) { 705 changed = true; 706 this.currentData_ = data; 707 } 708 709 // Notify the observers of the change, as needed. 710 for (var i = 0; i < this.observerInfos_.length; ++i) { 711 var observerInfo = this.observerInfos_[i]; 712 if (changed || !observerInfo.hasReceivedData) { 713 observerInfo.observer[this.observerMethodName_](this.currentData_); 714 observerInfo.hasReceivedData = true; 715 } 716 } 717}; 718 719/** 720 * Returns true if one of the observers actively wants the data 721 * (i.e. is visible). 722 */ 723PollableDataHelper.prototype.hasActiveObserver = function() { 724 for (var i = 0; i < this.observerInfos_.length; ++i) { 725 if (this.observerInfos_[i].observer.isActive()) 726 return true; 727 } 728 return false; 729}; 730 731/** 732 * This is a helper class used by BrowserBridge to send data to 733 * a callback once data from all polls has been received. 734 * 735 * It works by keeping track of how many polling functions have 736 * yet to receive data, and recording the data as it it received. 737 * 738 * @constructor 739 */ 740function UpdateAllObserver(callback, pollableDataHelpers) { 741 this.callback_ = callback; 742 this.observingCount_ = 0; 743 this.updatedData_ = {}; 744 745 for (name in pollableDataHelpers) { 746 ++this.observingCount_; 747 var helper = pollableDataHelpers[name]; 748 helper.addObserver(this); 749 this[helper.getObserverMethodName()] = 750 this.onDataReceived_.bind(this, helper, name); 751 } 752} 753 754UpdateAllObserver.prototype.isActive = function() { 755 return true; 756}; 757 758UpdateAllObserver.prototype.onDataReceived_ = function(helper, name, data) { 759 helper.removeObserver(this); 760 --this.observingCount_; 761 this.updatedData_[name] = data; 762 if (this.observingCount_ == 0) 763 this.callback_(this.updatedData_); 764}; 765