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