main.js revision 731df977c0511bca2206b5f333555b1205ff1f43
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 62 // Create a view which will display information on the host resolver. 63 var dnsView = new DnsView('dnsTabContent', 64 'hostResolverCacheTbody', 65 'clearHostResolverCache', 66 'hostResolverDefaultFamily', 67 'hostResolverIPv6Disabled', 68 'hostResolverEnableIPv6', 69 'hostResolverCacheCapacity', 70 'hostResolverCacheTTLSuccess', 71 'hostResolverCacheTTLFailure'); 72 73 // Create a view which will display import/export options to control the 74 // captured data. 75 var dataView = new DataView('dataTabContent', 'exportedDataText', 76 'exportToText', 'securityStrippingCheckbox', 77 'byteLoggingCheckbox', 78 'passivelyCapturedCount', 79 'activelyCapturedCount', 80 'dataViewDeleteAll'); 81 82 // Create a view which will display the results and controls for connection 83 // tests. 84 var testView = new TestView('testTabContent', 'testUrlInput', 85 'connectionTestsForm', 'testSummary'); 86 87 var httpCacheView = new HttpCacheView('httpCacheTabContent', 88 'httpCacheStats'); 89 90 var socketsView = new SocketsView('socketsTabContent', 91 'socketPoolDiv', 92 'socketPoolGroupsDiv'); 93 94 var spdyView = new SpdyView('spdyTabContent', 95 'spdySessionNoneSpan', 96 'spdySessionLinkSpan', 97 'spdySessionDiv'); 98 99 100 var serviceView; 101 if (g_browser.isPlatformWindows()) { 102 serviceView = new ServiceProvidersView('serviceProvidersTab', 103 'serviceProvidersTabContent', 104 'serviceProvidersTbody', 105 'namespaceProvidersTbody'); 106 } 107 108 // Create a view which lets you tab between the different sub-views. 109 var categoryTabSwitcher = 110 new TabSwitcherView(new DivView('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.receivedLogEntry = function(logEntry) { 310 // Silently drop entries received before ready to receive them. 311 if (!this.areLogTypesReady_()) 312 return; 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 323BrowserBridge.prototype.receivedLogEventTypeConstants = function(constantsMap) { 324 LogEventType = constantsMap; 325}; 326 327BrowserBridge.prototype.receivedClientInfo = 328function(info) { 329 ClientInfo = info; 330}; 331 332BrowserBridge.prototype.receivedLogEventPhaseConstants = 333function(constantsMap) { 334 LogEventPhase = constantsMap; 335}; 336 337BrowserBridge.prototype.receivedLogSourceTypeConstants = 338function(constantsMap) { 339 LogSourceType = constantsMap; 340}; 341 342BrowserBridge.prototype.receivedLogLevelConstants = 343function(constantsMap) { 344 LogLevelType = constantsMap; 345}; 346 347BrowserBridge.prototype.receivedLoadFlagConstants = function(constantsMap) { 348 LoadFlag = constantsMap; 349}; 350 351BrowserBridge.prototype.receivedNetErrorConstants = function(constantsMap) { 352 NetError = constantsMap; 353}; 354 355BrowserBridge.prototype.receivedAddressFamilyConstants = 356function(constantsMap) { 357 AddressFamily = constantsMap; 358}; 359 360BrowserBridge.prototype.receivedTimeTickOffset = function(timeTickOffset) { 361 this.timeTickOffset_ = timeTickOffset; 362}; 363 364BrowserBridge.prototype.receivedProxySettings = function(proxySettings) { 365 this.pollableDataHelpers_.proxySettings.update(proxySettings); 366}; 367 368BrowserBridge.prototype.receivedBadProxies = function(badProxies) { 369 this.pollableDataHelpers_.badProxies.update(badProxies); 370}; 371 372BrowserBridge.prototype.receivedHostResolverInfo = 373function(hostResolverInfo) { 374 this.pollableDataHelpers_.hostResolverInfo.update(hostResolverInfo); 375}; 376 377BrowserBridge.prototype.receivedSocketPoolInfo = function(socketPoolInfo) { 378 this.pollableDataHelpers_.socketPoolInfo.update(socketPoolInfo); 379}; 380 381BrowserBridge.prototype.receivedSpdySessionInfo = function(spdySessionInfo) { 382 this.pollableDataHelpers_.spdySessionInfo.update(spdySessionInfo); 383}; 384 385BrowserBridge.prototype.receivedServiceProviders = function(serviceProviders) { 386 this.pollableDataHelpers_.serviceProviders.update(serviceProviders); 387}; 388 389BrowserBridge.prototype.receivedPassiveLogEntries = function(entries) { 390 this.numPassivelyCapturedEvents_ += entries.length; 391 for (var i = 0; i < entries.length; ++i) { 392 var entry = entries[i]; 393 entry.wasPassivelyCaptured = true; 394 this.receivedLogEntry(entry); 395 } 396}; 397 398 399BrowserBridge.prototype.receivedStartConnectionTestSuite = function() { 400 for (var i = 0; i < this.connectionTestsObservers_.length; ++i) 401 this.connectionTestsObservers_[i].onStartedConnectionTestSuite(); 402}; 403 404BrowserBridge.prototype.receivedStartConnectionTestExperiment = function( 405 experiment) { 406 for (var i = 0; i < this.connectionTestsObservers_.length; ++i) { 407 this.connectionTestsObservers_[i].onStartedConnectionTestExperiment( 408 experiment); 409 } 410}; 411 412BrowserBridge.prototype.receivedCompletedConnectionTestExperiment = 413function(info) { 414 for (var i = 0; i < this.connectionTestsObservers_.length; ++i) { 415 this.connectionTestsObservers_[i].onCompletedConnectionTestExperiment( 416 info.experiment, info.result); 417 } 418}; 419 420BrowserBridge.prototype.receivedCompletedConnectionTestSuite = function() { 421 for (var i = 0; i < this.connectionTestsObservers_.length; ++i) 422 this.connectionTestsObservers_[i].onCompletedConnectionTestSuite(); 423}; 424 425BrowserBridge.prototype.receivedHttpCacheInfo = function(info) { 426 this.pollableDataHelpers_.httpCacheInfo.update(info); 427}; 428 429BrowserBridge.prototype.areLogTypesReady_ = function() { 430 return (LogEventType != null && 431 LogEventPhase != null && 432 LogSourceType != null); 433}; 434 435//------------------------------------------------------------------------------ 436 437/** 438 * Adds a listener of log entries. |observer| will be called back when new log 439 * data arrives, through: 440 * 441 * observer.onLogEntryAdded(logEntry) 442 */ 443BrowserBridge.prototype.addLogObserver = function(observer) { 444 this.logObservers_.push(observer); 445}; 446 447/** 448 * Adds a listener of the proxy settings. |observer| will be called back when 449 * data is received, through: 450 * 451 * observer.onProxySettingsChanged(proxySettings) 452 * 453 * |proxySettings| is a dictionary with (up to) two properties: 454 * 455 * "original" -- The settings that chrome was configured to use 456 * (i.e. system settings.) 457 * "effective" -- The "effective" proxy settings that chrome is using. 458 * (decides between the manual/automatic modes of the 459 * fetched settings). 460 * 461 * Each of these two configurations is formatted as a string, and may be 462 * omitted if not yet initialized. 463 * 464 * TODO(eroman): send a dictionary instead. 465 */ 466BrowserBridge.prototype.addProxySettingsObserver = function(observer) { 467 this.pollableDataHelpers_.proxySettings.addObserver(observer); 468}; 469 470/** 471 * Adds a listener of the proxy settings. |observer| will be called back when 472 * data is received, through: 473 * 474 * observer.onBadProxiesChanged(badProxies) 475 * 476 * |badProxies| is an array, where each entry has the property: 477 * badProxies[i].proxy_uri: String identify the proxy. 478 * badProxies[i].bad_until: The time when the proxy stops being considered 479 * bad. Note the time is in time ticks. 480 */ 481BrowserBridge.prototype.addBadProxiesObserver = function(observer) { 482 this.pollableDataHelpers_.badProxies.addObserver(observer); 483}; 484 485/** 486 * Adds a listener of the host resolver info. |observer| will be called back 487 * when data is received, through: 488 * 489 * observer.onHostResolverInfoChanged(hostResolverInfo) 490 */ 491BrowserBridge.prototype.addHostResolverInfoObserver = function(observer) { 492 this.pollableDataHelpers_.hostResolverInfo.addObserver(observer); 493}; 494 495/** 496 * Adds a listener of the socket pool. |observer| will be called back 497 * when data is received, through: 498 * 499 * observer.onSocketPoolInfoChanged(socketPoolInfo) 500 */ 501BrowserBridge.prototype.addSocketPoolInfoObserver = function(observer) { 502 this.pollableDataHelpers_.socketPoolInfo.addObserver(observer); 503}; 504 505/** 506 * Adds a listener of the SPDY info. |observer| will be called back 507 * when data is received, through: 508 * 509 * observer.onSpdySessionInfoChanged(spdySessionInfo) 510 */ 511BrowserBridge.prototype.addSpdySessionInfoObserver = function(observer) { 512 this.pollableDataHelpers_.spdySessionInfo.addObserver(observer); 513}; 514 515/** 516 * Adds a listener of the service providers info. |observer| will be called 517 * back when data is received, through: 518 * 519 * observer.onServiceProvidersChanged(serviceProviders) 520 */ 521BrowserBridge.prototype.addServiceProvidersObserver = function(observer) { 522 this.pollableDataHelpers_.serviceProviders.addObserver(observer); 523}; 524 525/** 526 * Adds a listener for the progress of the connection tests. 527 * The observer will be called back with: 528 * 529 * observer.onStartedConnectionTestSuite(); 530 * observer.onStartedConnectionTestExperiment(experiment); 531 * observer.onCompletedConnectionTestExperiment(experiment, result); 532 * observer.onCompletedConnectionTestSuite(); 533 */ 534BrowserBridge.prototype.addConnectionTestsObserver = function(observer) { 535 this.connectionTestsObservers_.push(observer); 536}; 537 538/** 539 * Adds a listener for the http cache info results. 540 * The observer will be called back with: 541 * 542 * observer.onHttpCacheInfoChanged(info); 543 */ 544BrowserBridge.prototype.addHttpCacheInfoObserver = function(observer) { 545 this.pollableDataHelpers_.httpCacheInfo.addObserver(observer); 546}; 547 548/** 549 * The browser gives us times in terms of "time ticks" in milliseconds. 550 * This function converts the tick count to a Date() object. 551 * 552 * @param {String} timeTicks. 553 * @returns {Date} The time that |timeTicks| represents. 554 */ 555BrowserBridge.prototype.convertTimeTicksToDate = function(timeTicks) { 556 // Note that the subtraction by 0 is to cast to a number (probably a float 557 // since the numbers are big). 558 var timeStampMs = (this.timeTickOffset_ - 0) + (timeTicks - 0); 559 var d = new Date(); 560 d.setTime(timeStampMs); 561 return d; 562}; 563 564/** 565 * Returns a list of all captured events. 566 */ 567BrowserBridge.prototype.getAllCapturedEvents = function() { 568 return this.capturedEvents_; 569}; 570 571/** 572 * Returns the number of events that were captured while we were 573 * listening for events. 574 */ 575BrowserBridge.prototype.getNumActivelyCapturedEvents = function() { 576 return this.capturedEvents_.length - this.numPassivelyCapturedEvents_; 577}; 578 579/** 580 * Returns the number of events that were captured passively by the 581 * browser prior to when the net-internals page was started. 582 */ 583BrowserBridge.prototype.getNumPassivelyCapturedEvents = function() { 584 return this.numPassivelyCapturedEvents_; 585}; 586 587/** 588 * Deletes captured events with source IDs in |sourceIds|. 589 */ 590BrowserBridge.prototype.deleteEventsBySourceId = function(sourceIds) { 591 var sourceIdDict = {}; 592 for (var i = 0; i < sourceIds.length; i++) 593 sourceIdDict[sourceIds[i]] = true; 594 595 var newEventList = []; 596 for (var i = 0; i < this.capturedEvents_.length; ++i) { 597 var id = this.capturedEvents_[i].source.id; 598 if (id in sourceIdDict) { 599 if (this.capturedEvents_[i].wasPassivelyCaptured) 600 --this.numPassivelyCapturedEvents_; 601 continue; 602 } 603 newEventList.push(this.capturedEvents_[i]); 604 } 605 this.capturedEvents_ = newEventList; 606 607 for (var i = 0; i < this.logObservers_.length; ++i) 608 this.logObservers_[i].onLogEntriesDeleted(sourceIds); 609}; 610 611/** 612 * Deletes all captured events. 613 */ 614BrowserBridge.prototype.deleteAllEvents = function() { 615 this.capturedEvents_ = []; 616 this.numPassivelyCapturedEvents_ = 0; 617 for (var i = 0; i < this.logObservers_.length; ++i) 618 this.logObservers_[i].onAllLogEntriesDeleted(); 619}; 620 621/** 622 * If |force| is true, calls all startUpdate functions. Otherwise, just 623 * runs updates with active observers. 624 */ 625BrowserBridge.prototype.checkForUpdatedInfo = function(force) { 626 for (name in this.pollableDataHelpers_) { 627 var helper = this.pollableDataHelpers_[name]; 628 if (force || helper.hasActiveObserver()) 629 helper.startUpdate(); 630 } 631}; 632 633/** 634 * Calls all startUpdate functions and, if |callback| is non-null, 635 * calls it with the results of all updates. 636 */ 637BrowserBridge.prototype.updateAllInfo = function(callback) { 638 if (callback) 639 new UpdateAllObserver(callback, this.pollableDataHelpers_); 640 this.checkForUpdatedInfo(true); 641}; 642 643/** 644 * This is a helper class used by BrowserBridge, to keep track of: 645 * - the list of observers interested in some piece of data. 646 * - the last known value of that piece of data. 647 * - the name of the callback method to invoke on observers. 648 * - the update function. 649 * @constructor 650 */ 651function PollableDataHelper(observerMethodName, startUpdateFunction) { 652 this.observerMethodName_ = observerMethodName; 653 this.startUpdate = startUpdateFunction; 654 this.observerInfos_ = []; 655} 656 657PollableDataHelper.prototype.getObserverMethodName = function() { 658 return this.observerMethodName_; 659}; 660 661/** 662 * This is a helper class used by PollableDataHelper, to keep track of 663 * each observer and whether or not it has received any data. The 664 * latter is used to make sure that new observers get sent data on the 665 * update following their creation. 666 * @constructor 667 */ 668function ObserverInfo(observer) { 669 this.observer = observer; 670 this.hasReceivedData = false; 671} 672 673PollableDataHelper.prototype.addObserver = function(observer) { 674 this.observerInfos_.push(new ObserverInfo(observer)); 675}; 676 677PollableDataHelper.prototype.removeObserver = function(observer) { 678 for (var i = 0; i < this.observerInfos_.length; ++i) { 679 if (this.observerInfos_[i].observer == observer) { 680 this.observerInfos_.splice(i, 1); 681 return; 682 } 683 } 684}; 685 686/** 687 * Helper function to handle calling all the observers, but ONLY if the data has 688 * actually changed since last time or the observer has yet to receive any data. 689 * This is used for data we received from browser on an update loop. 690 */ 691PollableDataHelper.prototype.update = function(data) { 692 var prevData = this.currentData_; 693 var changed = false; 694 695 // If the data hasn't changed since last time, will only need to notify 696 // observers that have not yet received any data. 697 if (!prevData || JSON.stringify(prevData) != JSON.stringify(data)) { 698 changed = true; 699 this.currentData_ = data; 700 } 701 702 // Notify the observers of the change, as needed. 703 for (var i = 0; i < this.observerInfos_.length; ++i) { 704 var observerInfo = this.observerInfos_[i]; 705 if (changed || !observerInfo.hasReceivedData) { 706 observerInfo.observer[this.observerMethodName_](this.currentData_); 707 observerInfo.hasReceivedData = true; 708 } 709 } 710}; 711 712/** 713 * Returns true if one of the observers actively wants the data 714 * (i.e. is visible). 715 */ 716PollableDataHelper.prototype.hasActiveObserver = function() { 717 for (var i = 0; i < this.observerInfos_.length; ++i) { 718 if (this.observerInfos_[i].observer.isActive()) 719 return true; 720 } 721 return false; 722}; 723 724/** 725 * This is a helper class used by BrowserBridge to send data to 726 * a callback once data from all polls has been received. 727 * 728 * It works by keeping track of how many polling functions have 729 * yet to receive data, and recording the data as it it received. 730 * 731 * @constructor 732 */ 733function UpdateAllObserver(callback, pollableDataHelpers) { 734 this.callback_ = callback; 735 this.observingCount_ = 0; 736 this.updatedData_ = {}; 737 738 for (name in pollableDataHelpers) { 739 ++this.observingCount_; 740 var helper = pollableDataHelpers[name]; 741 helper.addObserver(this); 742 this[helper.getObserverMethodName()] = 743 this.onDataReceived_.bind(this, helper, name); 744 } 745} 746 747UpdateAllObserver.prototype.isActive = function() { 748 return true; 749}; 750 751UpdateAllObserver.prototype.onDataReceived_ = function(helper, name, data) { 752 helper.removeObserver(this); 753 --this.observingCount_; 754 this.updatedData_[name] = data; 755 if (this.observingCount_ == 0) 756 this.callback_(this.updatedData_); 757}; 758