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