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