1 // Copyright (c) 2012 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 * This class provides a "bridge" for communicating between the javascript and 7 * the browser. 8 */ 9 var BrowserBridge = (function() { 10 'use strict'; 11 12 /** 13 * Delay in milliseconds between updates of certain browser information. 14 */ 15 var POLL_INTERVAL_MS = 5000; 16 17 /** 18 * @constructor 19 */ 20 function BrowserBridge() { 21 assertFirstConstructorCall(BrowserBridge); 22 23 // List of observers for various bits of browser state. 24 this.connectionTestsObservers_ = []; 25 this.hstsObservers_ = []; 26 this.constantsObservers_ = []; 27 this.crosONCFileParseObservers_ = []; 28 this.storeDebugLogsObservers_ = []; 29 this.setNetworkDebugModeObservers_ = []; 30 // Unprocessed data received before the constants. This serves to protect 31 // against passing along data before having information on how to interpret 32 // it. 33 this.earlyReceivedData_ = []; 34 35 this.pollableDataHelpers_ = {}; 36 this.pollableDataHelpers_.proxySettings = 37 new PollableDataHelper('onProxySettingsChanged', 38 this.sendGetProxySettings.bind(this)); 39 this.pollableDataHelpers_.badProxies = 40 new PollableDataHelper('onBadProxiesChanged', 41 this.sendGetBadProxies.bind(this)); 42 this.pollableDataHelpers_.httpCacheInfo = 43 new PollableDataHelper('onHttpCacheInfoChanged', 44 this.sendGetHttpCacheInfo.bind(this)); 45 this.pollableDataHelpers_.hostResolverInfo = 46 new PollableDataHelper('onHostResolverInfoChanged', 47 this.sendGetHostResolverInfo.bind(this)); 48 this.pollableDataHelpers_.socketPoolInfo = 49 new PollableDataHelper('onSocketPoolInfoChanged', 50 this.sendGetSocketPoolInfo.bind(this)); 51 this.pollableDataHelpers_.sessionNetworkStats = 52 new PollableDataHelper('onSessionNetworkStatsChanged', 53 this.sendGetSessionNetworkStats.bind(this)); 54 this.pollableDataHelpers_.historicNetworkStats = 55 new PollableDataHelper('onHistoricNetworkStatsChanged', 56 this.sendGetHistoricNetworkStats.bind(this)); 57 this.pollableDataHelpers_.quicInfo = 58 new PollableDataHelper('onQuicInfoChanged', 59 this.sendGetQuicInfo.bind(this)); 60 this.pollableDataHelpers_.spdySessionInfo = 61 new PollableDataHelper('onSpdySessionInfoChanged', 62 this.sendGetSpdySessionInfo.bind(this)); 63 this.pollableDataHelpers_.spdyStatus = 64 new PollableDataHelper('onSpdyStatusChanged', 65 this.sendGetSpdyStatus.bind(this)); 66 this.pollableDataHelpers_.spdyAlternateProtocolMappings = 67 new PollableDataHelper('onSpdyAlternateProtocolMappingsChanged', 68 this.sendGetSpdyAlternateProtocolMappings.bind( 69 this)); 70 if (cr.isWindows) { 71 this.pollableDataHelpers_.serviceProviders = 72 new PollableDataHelper('onServiceProvidersChanged', 73 this.sendGetServiceProviders.bind(this)); 74 } 75 this.pollableDataHelpers_.prerenderInfo = 76 new PollableDataHelper('onPrerenderInfoChanged', 77 this.sendGetPrerenderInfo.bind(this)); 78 this.pollableDataHelpers_.extensionInfo = 79 new PollableDataHelper('onExtensionInfoChanged', 80 this.sendGetExtensionInfo.bind(this)); 81 if (cr.isChromeOS) { 82 this.pollableDataHelpers_.systemLog = 83 new PollableDataHelper('onSystemLogChanged', 84 this.getSystemLog.bind(this, 'syslog')); 85 } 86 87 // Setting this to true will cause messages from the browser to be ignored, 88 // and no messages will be sent to the browser, either. Intended for use 89 // when viewing log files. 90 this.disabled_ = false; 91 92 // Interval id returned by window.setInterval for polling timer. 93 this.pollIntervalId_ = null; 94 } 95 96 cr.addSingletonGetter(BrowserBridge); 97 98 BrowserBridge.prototype = { 99 100 //-------------------------------------------------------------------------- 101 // Messages sent to the browser 102 //-------------------------------------------------------------------------- 103 104 /** 105 * Wraps |chrome.send|. Doesn't send anything when disabled. 106 */ 107 send: function(value1, value2) { 108 if (!this.disabled_) { 109 if (arguments.length == 1) { 110 chrome.send(value1); 111 } else if (arguments.length == 2) { 112 chrome.send(value1, value2); 113 } else { 114 throw 'Unsupported number of arguments.'; 115 } 116 } 117 }, 118 119 sendReady: function() { 120 this.send('notifyReady'); 121 this.setPollInterval(POLL_INTERVAL_MS); 122 }, 123 124 /** 125 * Some of the data we are interested is not currently exposed as a 126 * stream. This starts polling those with active observers (visible 127 * views) every |intervalMs|. Subsequent calls override previous calls 128 * to this function. If |intervalMs| is 0, stops polling. 129 */ 130 setPollInterval: function(intervalMs) { 131 if (this.pollIntervalId_ !== null) { 132 window.clearInterval(this.pollIntervalId_); 133 this.pollIntervalId_ = null; 134 } 135 136 if (intervalMs > 0) { 137 this.pollIntervalId_ = 138 window.setInterval(this.checkForUpdatedInfo.bind(this, false), 139 intervalMs); 140 } 141 }, 142 143 sendGetProxySettings: function() { 144 // The browser will call receivedProxySettings on completion. 145 this.send('getProxySettings'); 146 }, 147 148 sendReloadProxySettings: function() { 149 this.send('reloadProxySettings'); 150 }, 151 152 sendGetBadProxies: function() { 153 // The browser will call receivedBadProxies on completion. 154 this.send('getBadProxies'); 155 }, 156 157 sendGetHostResolverInfo: function() { 158 // The browser will call receivedHostResolverInfo on completion. 159 this.send('getHostResolverInfo'); 160 }, 161 162 sendClearBadProxies: function() { 163 this.send('clearBadProxies'); 164 }, 165 166 sendClearHostResolverCache: function() { 167 this.send('clearHostResolverCache'); 168 }, 169 170 sendClearBrowserCache: function() { 171 this.send('clearBrowserCache'); 172 }, 173 174 sendClearAllCache: function() { 175 this.sendClearHostResolverCache(); 176 this.sendClearBrowserCache(); 177 }, 178 179 sendStartConnectionTests: function(url) { 180 this.send('startConnectionTests', [url]); 181 }, 182 183 sendHSTSQuery: function(domain) { 184 this.send('hstsQuery', [domain]); 185 }, 186 187 sendHSTSAdd: function(domain, sts_include_subdomains, 188 pkp_include_subdomains, pins) { 189 this.send('hstsAdd', [domain, sts_include_subdomains, 190 pkp_include_subdomains, pins]); 191 }, 192 193 sendHSTSDelete: function(domain) { 194 this.send('hstsDelete', [domain]); 195 }, 196 197 sendGetHttpCacheInfo: function() { 198 this.send('getHttpCacheInfo'); 199 }, 200 201 sendGetSocketPoolInfo: function() { 202 this.send('getSocketPoolInfo'); 203 }, 204 205 sendGetSessionNetworkStats: function() { 206 this.send('getSessionNetworkStats'); 207 }, 208 209 sendGetHistoricNetworkStats: function() { 210 this.send('getHistoricNetworkStats'); 211 }, 212 213 sendCloseIdleSockets: function() { 214 this.send('closeIdleSockets'); 215 }, 216 217 sendFlushSocketPools: function() { 218 this.send('flushSocketPools'); 219 }, 220 221 sendGetQuicInfo: function() { 222 this.send('getQuicInfo'); 223 }, 224 225 sendGetSpdySessionInfo: function() { 226 this.send('getSpdySessionInfo'); 227 }, 228 229 sendGetSpdyStatus: function() { 230 this.send('getSpdyStatus'); 231 }, 232 233 sendGetSpdyAlternateProtocolMappings: function() { 234 this.send('getSpdyAlternateProtocolMappings'); 235 }, 236 237 sendGetServiceProviders: function() { 238 this.send('getServiceProviders'); 239 }, 240 241 sendGetPrerenderInfo: function() { 242 this.send('getPrerenderInfo'); 243 }, 244 245 sendGetExtensionInfo: function() { 246 this.send('getExtensionInfo'); 247 }, 248 249 enableIPv6: function() { 250 this.send('enableIPv6'); 251 }, 252 253 setLogLevel: function(logLevel) { 254 this.send('setLogLevel', ['' + logLevel]); 255 }, 256 257 refreshSystemLogs: function() { 258 this.send('refreshSystemLogs'); 259 }, 260 261 getSystemLog: function(log_key, cellId) { 262 this.send('getSystemLog', [log_key, cellId]); 263 }, 264 265 importONCFile: function(fileContent, passcode) { 266 this.send('importONCFile', [fileContent, passcode]); 267 }, 268 269 storeDebugLogs: function() { 270 this.send('storeDebugLogs'); 271 }, 272 273 setNetworkDebugMode: function(subsystem) { 274 this.send('setNetworkDebugMode', [subsystem]); 275 }, 276 277 //-------------------------------------------------------------------------- 278 // Messages received from the browser. 279 //-------------------------------------------------------------------------- 280 281 receive: function(command, params) { 282 // Does nothing if disabled. 283 if (this.disabled_) 284 return; 285 286 // If no constants have been received, and params does not contain the 287 // constants, delay handling the data. 288 if (Constants == null && command != 'receivedConstants') { 289 this.earlyReceivedData_.push({ command: command, params: params }); 290 return; 291 } 292 293 this[command](params); 294 295 // Handle any data that was received early in the order it was received, 296 // once the constants have been processed. 297 if (this.earlyReceivedData_ != null) { 298 for (var i = 0; i < this.earlyReceivedData_.length; i++) { 299 var command = this.earlyReceivedData_[i]; 300 this[command.command](command.params); 301 } 302 this.earlyReceivedData_ = null; 303 } 304 }, 305 306 receivedConstants: function(constants) { 307 for (var i = 0; i < this.constantsObservers_.length; i++) 308 this.constantsObservers_[i].onReceivedConstants(constants); 309 }, 310 311 receivedLogEntries: function(logEntries) { 312 EventsTracker.getInstance().addLogEntries(logEntries); 313 }, 314 315 receivedProxySettings: function(proxySettings) { 316 this.pollableDataHelpers_.proxySettings.update(proxySettings); 317 }, 318 319 receivedBadProxies: function(badProxies) { 320 this.pollableDataHelpers_.badProxies.update(badProxies); 321 }, 322 323 receivedHostResolverInfo: function(hostResolverInfo) { 324 this.pollableDataHelpers_.hostResolverInfo.update(hostResolverInfo); 325 }, 326 327 receivedSocketPoolInfo: function(socketPoolInfo) { 328 this.pollableDataHelpers_.socketPoolInfo.update(socketPoolInfo); 329 }, 330 331 receivedSessionNetworkStats: function(sessionNetworkStats) { 332 this.pollableDataHelpers_.sessionNetworkStats.update(sessionNetworkStats); 333 }, 334 335 receivedHistoricNetworkStats: function(historicNetworkStats) { 336 this.pollableDataHelpers_.historicNetworkStats.update( 337 historicNetworkStats); 338 }, 339 340 receivedQuicInfo: function(quicInfo) { 341 this.pollableDataHelpers_.quicInfo.update(quicInfo); 342 }, 343 344 receivedSpdySessionInfo: function(spdySessionInfo) { 345 this.pollableDataHelpers_.spdySessionInfo.update(spdySessionInfo); 346 }, 347 348 receivedSpdyStatus: function(spdyStatus) { 349 this.pollableDataHelpers_.spdyStatus.update(spdyStatus); 350 }, 351 352 receivedSpdyAlternateProtocolMappings: 353 function(spdyAlternateProtocolMappings) { 354 this.pollableDataHelpers_.spdyAlternateProtocolMappings.update( 355 spdyAlternateProtocolMappings); 356 }, 357 358 receivedServiceProviders: function(serviceProviders) { 359 this.pollableDataHelpers_.serviceProviders.update(serviceProviders); 360 }, 361 362 receivedStartConnectionTestSuite: function() { 363 for (var i = 0; i < this.connectionTestsObservers_.length; i++) 364 this.connectionTestsObservers_[i].onStartedConnectionTestSuite(); 365 }, 366 367 receivedStartConnectionTestExperiment: function(experiment) { 368 for (var i = 0; i < this.connectionTestsObservers_.length; i++) { 369 this.connectionTestsObservers_[i].onStartedConnectionTestExperiment( 370 experiment); 371 } 372 }, 373 374 receivedCompletedConnectionTestExperiment: function(info) { 375 for (var i = 0; i < this.connectionTestsObservers_.length; i++) { 376 this.connectionTestsObservers_[i].onCompletedConnectionTestExperiment( 377 info.experiment, info.result); 378 } 379 }, 380 381 receivedCompletedConnectionTestSuite: function() { 382 for (var i = 0; i < this.connectionTestsObservers_.length; i++) 383 this.connectionTestsObservers_[i].onCompletedConnectionTestSuite(); 384 }, 385 386 receivedHSTSResult: function(info) { 387 for (var i = 0; i < this.hstsObservers_.length; i++) 388 this.hstsObservers_[i].onHSTSQueryResult(info); 389 }, 390 391 receivedONCFileParse: function(error) { 392 for (var i = 0; i < this.crosONCFileParseObservers_.length; i++) 393 this.crosONCFileParseObservers_[i].onONCFileParse(error); 394 }, 395 396 receivedStoreDebugLogs: function(status) { 397 for (var i = 0; i < this.storeDebugLogsObservers_.length; i++) 398 this.storeDebugLogsObservers_[i].onStoreDebugLogs(status); 399 }, 400 401 receivedSetNetworkDebugMode: function(status) { 402 for (var i = 0; i < this.setNetworkDebugModeObservers_.length; i++) 403 this.setNetworkDebugModeObservers_[i].onSetNetworkDebugMode(status); 404 }, 405 406 receivedHttpCacheInfo: function(info) { 407 this.pollableDataHelpers_.httpCacheInfo.update(info); 408 }, 409 410 receivedPrerenderInfo: function(prerenderInfo) { 411 this.pollableDataHelpers_.prerenderInfo.update(prerenderInfo); 412 }, 413 414 receivedExtensionInfo: function(extensionInfo) { 415 this.pollableDataHelpers_.extensionInfo.update(extensionInfo); 416 }, 417 418 getSystemLogCallback: function(systemLog) { 419 this.pollableDataHelpers_.systemLog.update(systemLog); 420 }, 421 422 //-------------------------------------------------------------------------- 423 424 /** 425 * Prevents receiving/sending events to/from the browser. 426 */ 427 disable: function() { 428 this.disabled_ = true; 429 this.setPollInterval(0); 430 }, 431 432 /** 433 * Returns true if the BrowserBridge has been disabled. 434 */ 435 isDisabled: function() { 436 return this.disabled_; 437 }, 438 439 /** 440 * Adds a listener of the proxy settings. |observer| will be called back 441 * when data is received, through: 442 * 443 * observer.onProxySettingsChanged(proxySettings) 444 * 445 * |proxySettings| is a dictionary with (up to) two properties: 446 * 447 * "original" -- The settings that chrome was configured to use 448 * (i.e. system settings.) 449 * "effective" -- The "effective" proxy settings that chrome is using. 450 * (decides between the manual/automatic modes of the 451 * fetched settings). 452 * 453 * Each of these two configurations is formatted as a string, and may be 454 * omitted if not yet initialized. 455 * 456 * If |ignoreWhenUnchanged| is true, data is only sent when it changes. 457 * If it's false, data is sent whenever it's received from the browser. 458 */ 459 addProxySettingsObserver: function(observer, ignoreWhenUnchanged) { 460 this.pollableDataHelpers_.proxySettings.addObserver(observer, 461 ignoreWhenUnchanged); 462 }, 463 464 /** 465 * Adds a listener of the proxy settings. |observer| will be called back 466 * when data is received, through: 467 * 468 * observer.onBadProxiesChanged(badProxies) 469 * 470 * |badProxies| is an array, where each entry has the property: 471 * badProxies[i].proxy_uri: String identify the proxy. 472 * badProxies[i].bad_until: The time when the proxy stops being considered 473 * bad. Note the time is in time ticks. 474 */ 475 addBadProxiesObserver: function(observer, ignoreWhenUnchanged) { 476 this.pollableDataHelpers_.badProxies.addObserver(observer, 477 ignoreWhenUnchanged); 478 }, 479 480 /** 481 * Adds a listener of the host resolver info. |observer| will be called back 482 * when data is received, through: 483 * 484 * observer.onHostResolverInfoChanged(hostResolverInfo) 485 */ 486 addHostResolverInfoObserver: function(observer, ignoreWhenUnchanged) { 487 this.pollableDataHelpers_.hostResolverInfo.addObserver( 488 observer, ignoreWhenUnchanged); 489 }, 490 491 /** 492 * Adds a listener of the socket pool. |observer| will be called back 493 * when data is received, through: 494 * 495 * observer.onSocketPoolInfoChanged(socketPoolInfo) 496 */ 497 addSocketPoolInfoObserver: function(observer, ignoreWhenUnchanged) { 498 this.pollableDataHelpers_.socketPoolInfo.addObserver(observer, 499 ignoreWhenUnchanged); 500 }, 501 502 /** 503 * Adds a listener of the network session. |observer| will be called back 504 * when data is received, through: 505 * 506 * observer.onSessionNetworkStatsChanged(sessionNetworkStats) 507 */ 508 addSessionNetworkStatsObserver: function(observer, ignoreWhenUnchanged) { 509 this.pollableDataHelpers_.sessionNetworkStats.addObserver( 510 observer, ignoreWhenUnchanged); 511 }, 512 513 /** 514 * Adds a listener of persistent network session data. |observer| will be 515 * called back when data is received, through: 516 * 517 * observer.onHistoricNetworkStatsChanged(historicNetworkStats) 518 */ 519 addHistoricNetworkStatsObserver: function(observer, ignoreWhenUnchanged) { 520 this.pollableDataHelpers_.historicNetworkStats.addObserver( 521 observer, ignoreWhenUnchanged); 522 }, 523 524 /** 525 * Adds a listener of the QUIC info. |observer| will be called back 526 * when data is received, through: 527 * 528 * observer.onQuicInfoChanged(quicInfo) 529 */ 530 addQuicInfoObserver: function(observer, ignoreWhenUnchanged) { 531 this.pollableDataHelpers_.quicInfo.addObserver( 532 observer, ignoreWhenUnchanged); 533 }, 534 535 /** 536 * Adds a listener of the SPDY info. |observer| will be called back 537 * when data is received, through: 538 * 539 * observer.onSpdySessionInfoChanged(spdySessionInfo) 540 */ 541 addSpdySessionInfoObserver: function(observer, ignoreWhenUnchanged) { 542 this.pollableDataHelpers_.spdySessionInfo.addObserver( 543 observer, ignoreWhenUnchanged); 544 }, 545 546 /** 547 * Adds a listener of the SPDY status. |observer| will be called back 548 * when data is received, through: 549 * 550 * observer.onSpdyStatusChanged(spdyStatus) 551 */ 552 addSpdyStatusObserver: function(observer, ignoreWhenUnchanged) { 553 this.pollableDataHelpers_.spdyStatus.addObserver(observer, 554 ignoreWhenUnchanged); 555 }, 556 557 /** 558 * Adds a listener of the AlternateProtocolMappings. |observer| will be 559 * called back when data is received, through: 560 * 561 * observer.onSpdyAlternateProtocolMappingsChanged( 562 * spdyAlternateProtocolMappings) 563 */ 564 addSpdyAlternateProtocolMappingsObserver: function(observer, 565 ignoreWhenUnchanged) { 566 this.pollableDataHelpers_.spdyAlternateProtocolMappings.addObserver( 567 observer, ignoreWhenUnchanged); 568 }, 569 570 /** 571 * Adds a listener of the service providers info. |observer| will be called 572 * back when data is received, through: 573 * 574 * observer.onServiceProvidersChanged(serviceProviders) 575 * 576 * Will do nothing if on a platform other than Windows, as service providers 577 * are only present on Windows. 578 */ 579 addServiceProvidersObserver: function(observer, ignoreWhenUnchanged) { 580 if (this.pollableDataHelpers_.serviceProviders) { 581 this.pollableDataHelpers_.serviceProviders.addObserver( 582 observer, ignoreWhenUnchanged); 583 } 584 }, 585 586 /** 587 * Adds a listener for the progress of the connection tests. 588 * The observer will be called back with: 589 * 590 * observer.onStartedConnectionTestSuite(); 591 * observer.onStartedConnectionTestExperiment(experiment); 592 * observer.onCompletedConnectionTestExperiment(experiment, result); 593 * observer.onCompletedConnectionTestSuite(); 594 */ 595 addConnectionTestsObserver: function(observer) { 596 this.connectionTestsObservers_.push(observer); 597 }, 598 599 /** 600 * Adds a listener for the http cache info results. 601 * The observer will be called back with: 602 * 603 * observer.onHttpCacheInfoChanged(info); 604 */ 605 addHttpCacheInfoObserver: function(observer, ignoreWhenUnchanged) { 606 this.pollableDataHelpers_.httpCacheInfo.addObserver( 607 observer, ignoreWhenUnchanged); 608 }, 609 610 /** 611 * Adds a listener for the results of HSTS (HTTPS Strict Transport Security) 612 * queries. The observer will be called back with: 613 * 614 * observer.onHSTSQueryResult(result); 615 */ 616 addHSTSObserver: function(observer) { 617 this.hstsObservers_.push(observer); 618 }, 619 620 /** 621 * Adds a listener for ONC file parse status. The observer will be called 622 * back with: 623 * 624 * observer.onONCFileParse(error); 625 */ 626 addCrosONCFileParseObserver: function(observer) { 627 this.crosONCFileParseObservers_.push(observer); 628 }, 629 630 /** 631 * Adds a listener for storing log file status. The observer will be called 632 * back with: 633 * 634 * observer.onStoreDebugLogs(status); 635 */ 636 addStoreDebugLogsObserver: function(observer) { 637 this.storeDebugLogsObservers_.push(observer); 638 }, 639 640 /** 641 * Adds a listener for network debugging mode status. The observer 642 * will be called back with: 643 * 644 * observer.onSetNetworkDebugMode(status); 645 */ 646 addSetNetworkDebugModeObserver: function(observer) { 647 this.setNetworkDebugModeObservers_.push(observer); 648 }, 649 650 /** 651 * Adds a listener for the received constants event. |observer| will be 652 * called back when the constants are received, through: 653 * 654 * observer.onReceivedConstants(constants); 655 */ 656 addConstantsObserver: function(observer) { 657 this.constantsObservers_.push(observer); 658 }, 659 660 /** 661 * Adds a listener for updated prerender info events 662 * |observer| will be called back with: 663 * 664 * observer.onPrerenderInfoChanged(prerenderInfo); 665 */ 666 addPrerenderInfoObserver: function(observer, ignoreWhenUnchanged) { 667 this.pollableDataHelpers_.prerenderInfo.addObserver( 668 observer, ignoreWhenUnchanged); 669 }, 670 671 /** 672 * Adds a listener of extension information. |observer| will be called 673 * back when data is received, through: 674 * 675 * observer.onExtensionInfoChanged(extensionInfo) 676 */ 677 addExtensionInfoObserver: function(observer, ignoreWhenUnchanged) { 678 this.pollableDataHelpers_.extensionInfo.addObserver( 679 observer, ignoreWhenUnchanged); 680 }, 681 682 /** 683 * Adds a listener of system log information. |observer| will be called 684 * back when data is received, through: 685 * 686 * observer.onSystemLogChanged(systemLogInfo) 687 */ 688 addSystemLogObserver: function(observer, ignoreWhenUnchanged) { 689 if (this.pollableDataHelpers_.systemLog) { 690 this.pollableDataHelpers_.systemLog.addObserver( 691 observer, ignoreWhenUnchanged); 692 } 693 }, 694 695 /** 696 * If |force| is true, calls all startUpdate functions. Otherwise, just 697 * runs updates with active observers. 698 */ 699 checkForUpdatedInfo: function(force) { 700 for (var name in this.pollableDataHelpers_) { 701 var helper = this.pollableDataHelpers_[name]; 702 if (force || helper.hasActiveObserver()) 703 helper.startUpdate(); 704 } 705 }, 706 707 /** 708 * Calls all startUpdate functions and, if |callback| is non-null, 709 * calls it with the results of all updates. 710 */ 711 updateAllInfo: function(callback) { 712 if (callback) 713 new UpdateAllObserver(callback, this.pollableDataHelpers_); 714 this.checkForUpdatedInfo(true); 715 } 716 }; 717 718 /** 719 * This is a helper class used by BrowserBridge, to keep track of: 720 * - the list of observers interested in some piece of data. 721 * - the last known value of that piece of data. 722 * - the name of the callback method to invoke on observers. 723 * - the update function. 724 * @constructor 725 */ 726 function PollableDataHelper(observerMethodName, startUpdateFunction) { 727 this.observerMethodName_ = observerMethodName; 728 this.startUpdate = startUpdateFunction; 729 this.observerInfos_ = []; 730 } 731 732 PollableDataHelper.prototype = { 733 getObserverMethodName: function() { 734 return this.observerMethodName_; 735 }, 736 737 isObserver: function(object) { 738 for (var i = 0; i < this.observerInfos_.length; i++) { 739 if (this.observerInfos_[i].observer === object) 740 return true; 741 } 742 return false; 743 }, 744 745 /** 746 * If |ignoreWhenUnchanged| is true, we won't send data again until it 747 * changes. 748 */ 749 addObserver: function(observer, ignoreWhenUnchanged) { 750 this.observerInfos_.push(new ObserverInfo(observer, ignoreWhenUnchanged)); 751 }, 752 753 removeObserver: function(observer) { 754 for (var i = 0; i < this.observerInfos_.length; i++) { 755 if (this.observerInfos_[i].observer === observer) { 756 this.observerInfos_.splice(i, 1); 757 return; 758 } 759 } 760 }, 761 762 /** 763 * Helper function to handle calling all the observers, but ONLY if the data 764 * has actually changed since last time or the observer has yet to receive 765 * any data. This is used for data we received from browser on an update 766 * loop. 767 */ 768 update: function(data) { 769 var prevData = this.currentData_; 770 var changed = false; 771 772 // If the data hasn't changed since last time, will only need to notify 773 // observers that have not yet received any data. 774 if (!prevData || JSON.stringify(prevData) != JSON.stringify(data)) { 775 changed = true; 776 this.currentData_ = data; 777 } 778 779 // Notify the observers of the change, as needed. 780 for (var i = 0; i < this.observerInfos_.length; i++) { 781 var observerInfo = this.observerInfos_[i]; 782 if (changed || !observerInfo.hasReceivedData || 783 !observerInfo.ignoreWhenUnchanged) { 784 observerInfo.observer[this.observerMethodName_](this.currentData_); 785 observerInfo.hasReceivedData = true; 786 } 787 } 788 }, 789 790 /** 791 * Returns true if one of the observers actively wants the data 792 * (i.e. is visible). 793 */ 794 hasActiveObserver: function() { 795 for (var i = 0; i < this.observerInfos_.length; i++) { 796 if (this.observerInfos_[i].observer.isActive()) 797 return true; 798 } 799 return false; 800 } 801 }; 802 803 /** 804 * This is a helper class used by PollableDataHelper, to keep track of 805 * each observer and whether or not it has received any data. The 806 * latter is used to make sure that new observers get sent data on the 807 * update following their creation. 808 * @constructor 809 */ 810 function ObserverInfo(observer, ignoreWhenUnchanged) { 811 this.observer = observer; 812 this.hasReceivedData = false; 813 this.ignoreWhenUnchanged = ignoreWhenUnchanged; 814 } 815 816 /** 817 * This is a helper class used by BrowserBridge to send data to 818 * a callback once data from all polls has been received. 819 * 820 * It works by keeping track of how many polling functions have 821 * yet to receive data, and recording the data as it it received. 822 * 823 * @constructor 824 */ 825 function UpdateAllObserver(callback, pollableDataHelpers) { 826 this.callback_ = callback; 827 this.observingCount_ = 0; 828 this.updatedData_ = {}; 829 830 for (var name in pollableDataHelpers) { 831 ++this.observingCount_; 832 var helper = pollableDataHelpers[name]; 833 helper.addObserver(this); 834 this[helper.getObserverMethodName()] = 835 this.onDataReceived_.bind(this, helper, name); 836 } 837 } 838 839 UpdateAllObserver.prototype = { 840 isActive: function() { 841 return true; 842 }, 843 844 onDataReceived_: function(helper, name, data) { 845 helper.removeObserver(this); 846 --this.observingCount_; 847 this.updatedData_[name] = data; 848 if (this.observingCount_ == 0) 849 this.callback_(this.updatedData_); 850 } 851 }; 852 853 return BrowserBridge; 854 })(); 855