ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / chrome / browser / resources / net_internals / browser_bridge.js
blob20315821d92b3ce209df05251f6be04d79b7fb46
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     enableIPv6: function() {
215       this.send('enableIPv6');
216     },
218     setLogLevel: function(logLevel) {
219       this.send('setLogLevel', ['' + logLevel]);
220     },
222     importONCFile: function(fileContent, passcode) {
223       this.send('importONCFile', [fileContent, passcode]);
224     },
226     storeDebugLogs: function() {
227       this.send('storeDebugLogs');
228     },
230     setNetworkDebugMode: function(subsystem) {
231       this.send('setNetworkDebugMode', [subsystem]);
232     },
234     //--------------------------------------------------------------------------
235     // Messages received from the browser.
236     //--------------------------------------------------------------------------
238     receive: function(command, params) {
239       // Does nothing if disabled.
240       if (this.disabled_)
241         return;
243       // If no constants have been received, and params does not contain the
244       // constants, delay handling the data.
245       if (Constants == null && command != 'receivedConstants') {
246         this.earlyReceivedData_.push({ command: command, params: params });
247         return;
248       }
250       this[command](params);
252       // Handle any data that was received early in the order it was received,
253       // once the constants have been processed.
254       if (this.earlyReceivedData_ != null) {
255         for (var i = 0; i < this.earlyReceivedData_.length; i++) {
256           var command = this.earlyReceivedData_[i];
257           this[command.command](command.params);
258         }
259         this.earlyReceivedData_ = null;
260       }
261     },
263     receivedConstants: function(constants) {
264       NetInfoSources = constants.netInfoSources;
265       for (var i = 0; i < this.constantsObservers_.length; i++)
266         this.constantsObservers_[i].onReceivedConstants(constants);
267       // May have been waiting for the constants to be received before getting
268       // information for the currently displayed tab.
269       this.checkForUpdatedInfo();
270     },
272     receivedLogEntries: function(logEntries) {
273       EventsTracker.getInstance().addLogEntries(logEntries);
274     },
276     receivedNetInfo: function(netInfo) {
277       // Dispatch |netInfo| to the various PollableDataHelpers listening to
278       // each field it contains.
279       //
280       // Currently information is only received from one source at a time, but
281       // the API does allow for data from more that one to be requested at once.
282       for (var source in netInfo)
283         this.pollableDataHelpers_[source].update(netInfo[source]);
284     },
286     receivedSessionNetworkStats: function(sessionNetworkStats) {
287       this.pollableDataHelpers_.sessionNetworkStats.update(sessionNetworkStats);
288     },
290     receivedHistoricNetworkStats: function(historicNetworkStats) {
291       this.pollableDataHelpers_.historicNetworkStats.update(
292           historicNetworkStats);
293     },
295     receivedServiceProviders: function(serviceProviders) {
296       this.pollableDataHelpers_.serviceProviders.update(serviceProviders);
297     },
299     receivedStartConnectionTestSuite: function() {
300       for (var i = 0; i < this.connectionTestsObservers_.length; i++)
301         this.connectionTestsObservers_[i].onStartedConnectionTestSuite();
302     },
304     receivedStartConnectionTestExperiment: function(experiment) {
305       for (var i = 0; i < this.connectionTestsObservers_.length; i++) {
306         this.connectionTestsObservers_[i].onStartedConnectionTestExperiment(
307             experiment);
308       }
309     },
311     receivedCompletedConnectionTestExperiment: function(info) {
312       for (var i = 0; i < this.connectionTestsObservers_.length; i++) {
313         this.connectionTestsObservers_[i].onCompletedConnectionTestExperiment(
314             info.experiment, info.result);
315       }
316     },
318     receivedCompletedConnectionTestSuite: function() {
319       for (var i = 0; i < this.connectionTestsObservers_.length; i++)
320         this.connectionTestsObservers_[i].onCompletedConnectionTestSuite();
321     },
323     receivedHSTSResult: function(info) {
324       for (var i = 0; i < this.hstsObservers_.length; i++)
325         this.hstsObservers_[i].onHSTSQueryResult(info);
326     },
328     receivedONCFileParse: function(error) {
329       for (var i = 0; i < this.crosONCFileParseObservers_.length; i++)
330         this.crosONCFileParseObservers_[i].onONCFileParse(error);
331     },
333     receivedStoreDebugLogs: function(status) {
334       for (var i = 0; i < this.storeDebugLogsObservers_.length; i++)
335         this.storeDebugLogsObservers_[i].onStoreDebugLogs(status);
336     },
338     receivedSetNetworkDebugMode: function(status) {
339       for (var i = 0; i < this.setNetworkDebugModeObservers_.length; i++)
340         this.setNetworkDebugModeObservers_[i].onSetNetworkDebugMode(status);
341     },
343     receivedPrerenderInfo: function(prerenderInfo) {
344       this.pollableDataHelpers_.prerenderInfo.update(prerenderInfo);
345     },
347     receivedExtensionInfo: function(extensionInfo) {
348       this.pollableDataHelpers_.extensionInfo.update(extensionInfo);
349     },
351     receivedDataReductionProxyInfo: function(dataReductionProxyInfo) {
352       this.pollableDataHelpers_.dataReductionProxyInfo.update(
353           dataReductionProxyInfo);
354     },
356     //--------------------------------------------------------------------------
358     /**
359      * Prevents receiving/sending events to/from the browser.
360      */
361     disable: function() {
362       this.disabled_ = true;
363       this.setPollInterval(0);
364     },
366     /**
367      * Returns true if the BrowserBridge has been disabled.
368      */
369     isDisabled: function() {
370       return this.disabled_;
371     },
373     /**
374      * Adds a listener of the proxy settings. |observer| will be called back
375      * when data is received, through:
376      *
377      *   observer.onProxySettingsChanged(proxySettings)
378      *
379      * |proxySettings| is a dictionary with (up to) two properties:
380      *
381      *   "original"  -- The settings that chrome was configured to use
382      *                  (i.e. system settings.)
383      *   "effective" -- The "effective" proxy settings that chrome is using.
384      *                  (decides between the manual/automatic modes of the
385      *                  fetched settings).
386      *
387      * Each of these two configurations is formatted as a string, and may be
388      * omitted if not yet initialized.
389      *
390      * If |ignoreWhenUnchanged| is true, data is only sent when it changes.
391      * If it's false, data is sent whenever it's received from the browser.
392      */
393     addProxySettingsObserver: function(observer, ignoreWhenUnchanged) {
394       this.pollableDataHelpers_.proxySettings.addObserver(observer,
395                                                           ignoreWhenUnchanged);
396     },
398     /**
399      * Adds a listener of the proxy settings. |observer| will be called back
400      * when data is received, through:
401      *
402      *   observer.onBadProxiesChanged(badProxies)
403      *
404      * |badProxies| is an array, where each entry has the property:
405      *   badProxies[i].proxy_uri: String identify the proxy.
406      *   badProxies[i].bad_until: The time when the proxy stops being considered
407      *                            bad. Note the time is in time ticks.
408      */
409     addBadProxiesObserver: function(observer, ignoreWhenUnchanged) {
410       this.pollableDataHelpers_.badProxies.addObserver(observer,
411                                                        ignoreWhenUnchanged);
412     },
414     /**
415      * Adds a listener of the host resolver info. |observer| will be called back
416      * when data is received, through:
417      *
418      *   observer.onHostResolverInfoChanged(hostResolverInfo)
419      */
420     addHostResolverInfoObserver: function(observer, ignoreWhenUnchanged) {
421       this.pollableDataHelpers_.hostResolverInfo.addObserver(
422           observer, ignoreWhenUnchanged);
423     },
425     /**
426      * Adds a listener of the socket pool. |observer| will be called back
427      * when data is received, through:
428      *
429      *   observer.onSocketPoolInfoChanged(socketPoolInfo)
430      */
431     addSocketPoolInfoObserver: function(observer, ignoreWhenUnchanged) {
432       this.pollableDataHelpers_.socketPoolInfo.addObserver(observer,
433                                                            ignoreWhenUnchanged);
434     },
436     /**
437      * Adds a listener of the network session. |observer| will be called back
438      * when data is received, through:
439      *
440      *   observer.onSessionNetworkStatsChanged(sessionNetworkStats)
441      */
442     addSessionNetworkStatsObserver: function(observer, ignoreWhenUnchanged) {
443       this.pollableDataHelpers_.sessionNetworkStats.addObserver(
444           observer, ignoreWhenUnchanged);
445     },
447     /**
448      * Adds a listener of persistent network session data. |observer| will be
449      * called back when data is received, through:
450      *
451      *   observer.onHistoricNetworkStatsChanged(historicNetworkStats)
452      */
453     addHistoricNetworkStatsObserver: function(observer, ignoreWhenUnchanged) {
454       this.pollableDataHelpers_.historicNetworkStats.addObserver(
455           observer, ignoreWhenUnchanged);
456     },
458     /**
459      * Adds a listener of the QUIC info. |observer| will be called back
460      * when data is received, through:
461      *
462      *   observer.onQuicInfoChanged(quicInfo)
463      */
464     addQuicInfoObserver: function(observer, ignoreWhenUnchanged) {
465       this.pollableDataHelpers_.quicInfo.addObserver(
466           observer, ignoreWhenUnchanged);
467     },
469     /**
470      * Adds a listener of the SPDY info. |observer| will be called back
471      * when data is received, through:
472      *
473      *   observer.onSpdySessionInfoChanged(spdySessionInfo)
474      */
475     addSpdySessionInfoObserver: function(observer, ignoreWhenUnchanged) {
476       this.pollableDataHelpers_.spdySessionInfo.addObserver(
477           observer, ignoreWhenUnchanged);
478     },
480     /**
481      * Adds a listener of the SPDY status. |observer| will be called back
482      * when data is received, through:
483      *
484      *   observer.onSpdyStatusChanged(spdyStatus)
485      */
486     addSpdyStatusObserver: function(observer, ignoreWhenUnchanged) {
487       this.pollableDataHelpers_.spdyStatus.addObserver(observer,
488                                                        ignoreWhenUnchanged);
489     },
491     /**
492      * Adds a listener of the AlternateProtocolMappings. |observer| will be
493      * called back when data is received, through:
494      *
495      *   observer.onSpdyAlternateProtocolMappingsChanged(
496      *       spdyAlternateProtocolMappings)
497      */
498     addSpdyAlternateProtocolMappingsObserver: function(observer,
499                                                        ignoreWhenUnchanged) {
500       this.pollableDataHelpers_.spdyAlternateProtocolMappings.addObserver(
501           observer, ignoreWhenUnchanged);
502     },
504     /**
505      * Adds a listener of the service providers info. |observer| will be called
506      * back when data is received, through:
507      *
508      *   observer.onServiceProvidersChanged(serviceProviders)
509      *
510      * Will do nothing if on a platform other than Windows, as service providers
511      * are only present on Windows.
512      */
513     addServiceProvidersObserver: function(observer, ignoreWhenUnchanged) {
514       if (this.pollableDataHelpers_.serviceProviders) {
515         this.pollableDataHelpers_.serviceProviders.addObserver(
516             observer, ignoreWhenUnchanged);
517       }
518     },
520     /**
521      * Adds a listener for the progress of the connection tests.
522      * The observer will be called back with:
523      *
524      *   observer.onStartedConnectionTestSuite();
525      *   observer.onStartedConnectionTestExperiment(experiment);
526      *   observer.onCompletedConnectionTestExperiment(experiment, result);
527      *   observer.onCompletedConnectionTestSuite();
528      */
529     addConnectionTestsObserver: function(observer) {
530       this.connectionTestsObservers_.push(observer);
531     },
533     /**
534      * Adds a listener for the http cache info results.
535      * The observer will be called back with:
536      *
537      *   observer.onHttpCacheInfoChanged(info);
538      */
539     addHttpCacheInfoObserver: function(observer, ignoreWhenUnchanged) {
540       this.pollableDataHelpers_.httpCacheInfo.addObserver(
541           observer, ignoreWhenUnchanged);
542     },
544     /**
545      * Adds a listener for the results of HSTS (HTTPS Strict Transport Security)
546      * queries. The observer will be called back with:
547      *
548      *   observer.onHSTSQueryResult(result);
549      */
550     addHSTSObserver: function(observer) {
551       this.hstsObservers_.push(observer);
552     },
554     /**
555      * Adds a listener for ONC file parse status. The observer will be called
556      * back with:
557      *
558      *   observer.onONCFileParse(error);
559      */
560     addCrosONCFileParseObserver: function(observer) {
561       this.crosONCFileParseObservers_.push(observer);
562     },
564     /**
565      * Adds a listener for storing log file status. The observer will be called
566      * back with:
567      *
568      *   observer.onStoreDebugLogs(status);
569      */
570     addStoreDebugLogsObserver: function(observer) {
571       this.storeDebugLogsObservers_.push(observer);
572     },
574     /**
575      * Adds a listener for network debugging mode status. The observer
576      * will be called back with:
577      *
578      *   observer.onSetNetworkDebugMode(status);
579      */
580     addSetNetworkDebugModeObserver: function(observer) {
581       this.setNetworkDebugModeObservers_.push(observer);
582     },
584     /**
585      * Adds a listener for the received constants event. |observer| will be
586      * called back when the constants are received, through:
587      *
588      *   observer.onReceivedConstants(constants);
589      */
590     addConstantsObserver: function(observer) {
591       this.constantsObservers_.push(observer);
592     },
594     /**
595      * Adds a listener for updated prerender info events
596      * |observer| will be called back with:
597      *
598      *   observer.onPrerenderInfoChanged(prerenderInfo);
599      */
600     addPrerenderInfoObserver: function(observer, ignoreWhenUnchanged) {
601       this.pollableDataHelpers_.prerenderInfo.addObserver(
602           observer, ignoreWhenUnchanged);
603     },
605     /**
606      * Adds a listener of extension information. |observer| will be called
607      * back when data is received, through:
608      *
609      *   observer.onExtensionInfoChanged(extensionInfo)
610      */
611     addExtensionInfoObserver: function(observer, ignoreWhenUnchanged) {
612       this.pollableDataHelpers_.extensionInfo.addObserver(
613           observer, ignoreWhenUnchanged);
614     },
616     /**
617      * Adds a listener of the data reduction proxy info. |observer| will be
618      * called back when data is received, through:
619      *
620      *   observer.onDataReductionProxyInfoChanged(dataReductionProxyInfo)
621      */
622     addDataReductionProxyInfoObserver: function(observer, ignoreWhenUnchanged) {
623       this.pollableDataHelpers_.dataReductionProxyInfo.addObserver(
624           observer, ignoreWhenUnchanged);
625     },
627     /**
628      * Adds a listener of SDCH information. |observer| will be called
629      * back when data is received, through:
630      *
631      *   observer.onSdchInfoChanged(sdchInfo)
632      */
633     addSdchInfoObserver: function(observer, ignoreWhenUnchanged) {
634       this.pollableDataHelpers_.sdchInfo.addObserver(
635           observer, ignoreWhenUnchanged);
636     },
638     /**
639      * If |force| is true, calls all startUpdate functions.  Otherwise, just
640      * runs updates with active observers.
641      */
642     checkForUpdatedInfo: function(force) {
643       for (var name in this.pollableDataHelpers_) {
644         var helper = this.pollableDataHelpers_[name];
645         if (force || helper.hasActiveObserver())
646           helper.startUpdate();
647       }
648     },
650     /**
651      * Calls all startUpdate functions and, if |callback| is non-null,
652      * calls it with the results of all updates.
653      */
654     updateAllInfo: function(callback) {
655       if (callback)
656         new UpdateAllObserver(callback, this.pollableDataHelpers_);
657       this.checkForUpdatedInfo(true);
658     },
660     /**
661      * Adds a PollableDataHelper that listens to the specified NetInfoSource.
662      */
663     addNetInfoPollableDataHelper: function(sourceName, observerMethodName) {
664       this.pollableDataHelpers_[sourceName] = new PollableDataHelper(
665           observerMethodName, this.sendGetNetInfo.bind(this, sourceName));
666     },
667   };
669   /**
670    * This is a helper class used by BrowserBridge, to keep track of:
671    *   - the list of observers interested in some piece of data.
672    *   - the last known value of that piece of data.
673    *   - the name of the callback method to invoke on observers.
674    *   - the update function.
675    * @constructor
676    */
677   function PollableDataHelper(observerMethodName, startUpdateFunction) {
678     this.observerMethodName_ = observerMethodName;
679     this.startUpdate = startUpdateFunction;
680     this.observerInfos_ = [];
681   }
683   PollableDataHelper.prototype = {
684     getObserverMethodName: function() {
685       return this.observerMethodName_;
686     },
688     isObserver: function(object) {
689       for (var i = 0; i < this.observerInfos_.length; i++) {
690         if (this.observerInfos_[i].observer === object)
691           return true;
692       }
693       return false;
694     },
696     /**
697      * If |ignoreWhenUnchanged| is true, we won't send data again until it
698      * changes.
699      */
700     addObserver: function(observer, ignoreWhenUnchanged) {
701       this.observerInfos_.push(new ObserverInfo(observer, ignoreWhenUnchanged));
702     },
704     removeObserver: function(observer) {
705       for (var i = 0; i < this.observerInfos_.length; i++) {
706         if (this.observerInfos_[i].observer === observer) {
707           this.observerInfos_.splice(i, 1);
708           return;
709         }
710       }
711     },
713     /**
714      * Helper function to handle calling all the observers, but ONLY if the data
715      * has actually changed since last time or the observer has yet to receive
716      * any data. This is used for data we received from browser on an update
717      * loop.
718      */
719     update: function(data) {
720       var prevData = this.currentData_;
721       var changed = false;
723       // If the data hasn't changed since last time, will only need to notify
724       // observers that have not yet received any data.
725       if (!prevData || JSON.stringify(prevData) != JSON.stringify(data)) {
726         changed = true;
727         this.currentData_ = data;
728       }
730       // Notify the observers of the change, as needed.
731       for (var i = 0; i < this.observerInfos_.length; i++) {
732         var observerInfo = this.observerInfos_[i];
733         if (changed || !observerInfo.hasReceivedData ||
734             !observerInfo.ignoreWhenUnchanged) {
735           observerInfo.observer[this.observerMethodName_](this.currentData_);
736           observerInfo.hasReceivedData = true;
737         }
738       }
739     },
741     /**
742      * Returns true if one of the observers actively wants the data
743      * (i.e. is visible).
744      */
745     hasActiveObserver: function() {
746       for (var i = 0; i < this.observerInfos_.length; i++) {
747         if (this.observerInfos_[i].observer.isActive())
748           return true;
749       }
750       return false;
751     }
752   };
754   /**
755    * This is a helper class used by PollableDataHelper, to keep track of
756    * each observer and whether or not it has received any data.  The
757    * latter is used to make sure that new observers get sent data on the
758    * update following their creation.
759    * @constructor
760    */
761   function ObserverInfo(observer, ignoreWhenUnchanged) {
762     this.observer = observer;
763     this.hasReceivedData = false;
764     this.ignoreWhenUnchanged = ignoreWhenUnchanged;
765   }
767   /**
768    * This is a helper class used by BrowserBridge to send data to
769    * a callback once data from all polls has been received.
770    *
771    * It works by keeping track of how many polling functions have
772    * yet to receive data, and recording the data as it it received.
773    *
774    * @constructor
775    */
776   function UpdateAllObserver(callback, pollableDataHelpers) {
777     this.callback_ = callback;
778     this.observingCount_ = 0;
779     this.updatedData_ = {};
781     for (var name in pollableDataHelpers) {
782       ++this.observingCount_;
783       var helper = pollableDataHelpers[name];
784       helper.addObserver(this);
785       this[helper.getObserverMethodName()] =
786           this.onDataReceived_.bind(this, helper, name);
787     }
788   }
790   UpdateAllObserver.prototype = {
791     isActive: function() {
792       return true;
793     },
795     onDataReceived_: function(helper, name, data) {
796       helper.removeObserver(this);
797       --this.observingCount_;
798       this.updatedData_[name] = data;
799       if (this.observingCount_ == 0)
800         this.callback_(this.updatedData_);
801     }
802   };
804   return BrowserBridge;
805 })();