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