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