Home | History | Annotate | Download | only in net_internals
      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_.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