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_.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