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#include "content/browser/geolocation/geolocation_dispatcher_host.h"
6
7#include <map>
8#include <set>
9#include <utility>
10
11#include "base/bind.h"
12#include "base/metrics/histogram.h"
13#include "content/browser/geolocation/geolocation_provider_impl.h"
14#include "content/browser/renderer_host/render_message_filter.h"
15#include "content/browser/renderer_host/render_process_host_impl.h"
16#include "content/browser/renderer_host/render_view_host_impl.h"
17#include "content/public/browser/geolocation_permission_context.h"
18#include "content/public/common/geoposition.h"
19#include "content/common/geolocation_messages.h"
20
21namespace content {
22namespace {
23
24void NotifyGeolocationProviderPermissionGranted() {
25  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
26  GeolocationProviderImpl::GetInstance()->UserDidOptIntoLocationServices();
27}
28
29void SendGeolocationPermissionResponse(int render_process_id,
30                                       int render_view_id,
31                                       int bridge_id,
32                                       bool allowed) {
33  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
34  RenderViewHostImpl* render_view_host =
35      RenderViewHostImpl::FromID(render_process_id, render_view_id);
36  if (!render_view_host)
37    return;
38  render_view_host->Send(
39      new GeolocationMsg_PermissionSet(render_view_id, bridge_id, allowed));
40
41  if (allowed) {
42    BrowserThread::PostTask(
43        BrowserThread::IO, FROM_HERE,
44        base::Bind(&NotifyGeolocationProviderPermissionGranted));
45  }
46}
47
48class GeolocationDispatcherHostImpl : public GeolocationDispatcherHost {
49 public:
50  GeolocationDispatcherHostImpl(
51      int render_process_id,
52      GeolocationPermissionContext* geolocation_permission_context);
53
54  // GeolocationDispatcherHost
55  virtual bool OnMessageReceived(const IPC::Message& msg,
56                                 bool* msg_was_ok) OVERRIDE;
57
58 private:
59  virtual ~GeolocationDispatcherHostImpl();
60
61  void OnRequestPermission(int render_view_id,
62                           int bridge_id,
63                           const GURL& requesting_frame);
64  void OnCancelPermissionRequest(int render_view_id,
65                                 int bridge_id,
66                                 const GURL& requesting_frame);
67  void OnStartUpdating(int render_view_id,
68                       const GURL& requesting_frame,
69                       bool enable_high_accuracy);
70  void OnStopUpdating(int render_view_id);
71
72
73  virtual void PauseOrResume(int render_view_id, bool should_pause) OVERRIDE;
74
75  // Updates the |geolocation_provider_| with the currently required update
76  // options.
77  void RefreshGeolocationOptions();
78
79  void OnLocationUpdate(const Geoposition& position);
80
81  int render_process_id_;
82  scoped_refptr<GeolocationPermissionContext> geolocation_permission_context_;
83
84  struct RendererGeolocationOptions {
85    bool high_accuracy;
86    bool is_paused;
87  };
88
89  // Used to keep track of the renderers in this process that are using
90  // geolocation and the options associated with them. The map is iterated
91  // when a location update is available and the fan out to individual bridge
92  // IDs happens renderer side, in order to minimize context switches.
93  // Only used on the IO thread.
94  std::map<int, RendererGeolocationOptions> geolocation_renderers_;
95
96  // Used by Android WebView to support that case that a renderer is in the
97  // 'paused' state but not yet using geolocation. If the renderer does start
98  // using geolocation while paused, we move from this set into
99  // |geolocation_renderers_|. If the renderer doesn't end up wanting to use
100  // geolocation while 'paused' then we remove from this set. A renderer id
101  // can exist only in this set or |geolocation_renderers_|, never both.
102  std::set<int> pending_paused_geolocation_renderers_;
103
104  // Only set whilst we are registered with the geolocation provider.
105  GeolocationProviderImpl* geolocation_provider_;
106
107  GeolocationProviderImpl::LocationUpdateCallback callback_;
108
109  DISALLOW_COPY_AND_ASSIGN(GeolocationDispatcherHostImpl);
110};
111
112GeolocationDispatcherHostImpl::GeolocationDispatcherHostImpl(
113    int render_process_id,
114    GeolocationPermissionContext* geolocation_permission_context)
115    : render_process_id_(render_process_id),
116      geolocation_permission_context_(geolocation_permission_context),
117      geolocation_provider_(NULL) {
118  callback_ = base::Bind(
119      &GeolocationDispatcherHostImpl::OnLocationUpdate, base::Unretained(this));
120  // This is initialized by ResourceMessageFilter. Do not add any non-trivial
121  // initialization here, defer to OnRegisterBridge which is triggered whenever
122  // a javascript geolocation object is actually initialized.
123}
124
125GeolocationDispatcherHostImpl::~GeolocationDispatcherHostImpl() {
126  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
127  if (geolocation_provider_)
128    geolocation_provider_->RemoveLocationUpdateCallback(callback_);
129}
130
131bool GeolocationDispatcherHostImpl::OnMessageReceived(
132    const IPC::Message& msg, bool* msg_was_ok) {
133  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
134  *msg_was_ok = true;
135  bool handled = true;
136  IPC_BEGIN_MESSAGE_MAP_EX(GeolocationDispatcherHostImpl, msg, *msg_was_ok)
137    IPC_MESSAGE_HANDLER(GeolocationHostMsg_CancelPermissionRequest,
138                        OnCancelPermissionRequest)
139    IPC_MESSAGE_HANDLER(GeolocationHostMsg_RequestPermission,
140                        OnRequestPermission)
141    IPC_MESSAGE_HANDLER(GeolocationHostMsg_StartUpdating, OnStartUpdating)
142    IPC_MESSAGE_HANDLER(GeolocationHostMsg_StopUpdating, OnStopUpdating)
143    IPC_MESSAGE_UNHANDLED(handled = false)
144  IPC_END_MESSAGE_MAP()
145  return handled;
146}
147
148void GeolocationDispatcherHostImpl::OnLocationUpdate(
149    const Geoposition& geoposition) {
150  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
151  for (std::map<int, RendererGeolocationOptions>::iterator it =
152       geolocation_renderers_.begin();
153       it != geolocation_renderers_.end(); ++it) {
154    if (!(it->second.is_paused))
155      Send(new GeolocationMsg_PositionUpdated(it->first, geoposition));
156  }
157}
158
159void GeolocationDispatcherHostImpl::OnRequestPermission(
160    int render_view_id,
161    int bridge_id,
162    const GURL& requesting_frame) {
163  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
164  DVLOG(1) << __FUNCTION__ << " " << render_process_id_ << ":"
165           << render_view_id << ":" << bridge_id;
166  if (geolocation_permission_context_.get()) {
167    geolocation_permission_context_->RequestGeolocationPermission(
168        render_process_id_,
169        render_view_id,
170        bridge_id,
171        requesting_frame,
172        base::Bind(&SendGeolocationPermissionResponse,
173                   render_process_id_,
174                   render_view_id,
175                   bridge_id));
176  } else {
177    BrowserThread::PostTask(
178        BrowserThread::UI, FROM_HERE,
179        base::Bind(&SendGeolocationPermissionResponse, render_process_id_,
180                   render_view_id, bridge_id, true));
181  }
182}
183
184void GeolocationDispatcherHostImpl::OnCancelPermissionRequest(
185    int render_view_id,
186    int bridge_id,
187    const GURL& requesting_frame) {
188  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
189  DVLOG(1) << __FUNCTION__ << " " << render_process_id_ << ":"
190           << render_view_id << ":" << bridge_id;
191  if (geolocation_permission_context_.get()) {
192    geolocation_permission_context_->CancelGeolocationPermissionRequest(
193        render_process_id_, render_view_id, bridge_id, requesting_frame);
194  }
195}
196
197void GeolocationDispatcherHostImpl::OnStartUpdating(
198    int render_view_id,
199    const GURL& requesting_frame,
200    bool enable_high_accuracy) {
201  // StartUpdating() can be invoked as a result of high-accuracy mode
202  // being enabled / disabled. No need to record the dispatcher again.
203  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
204  DVLOG(1) << __FUNCTION__ << " " << render_process_id_ << ":"
205           << render_view_id;
206  UMA_HISTOGRAM_BOOLEAN(
207      "Geolocation.GeolocationDispatcherHostImpl.EnableHighAccuracy",
208      enable_high_accuracy);
209
210  std::map<int, RendererGeolocationOptions>::iterator it =
211            geolocation_renderers_.find(render_view_id);
212  if (it == geolocation_renderers_.end()) {
213    bool should_start_paused = false;
214    if (pending_paused_geolocation_renderers_.erase(render_view_id) == 1) {
215      should_start_paused = true;
216    }
217    RendererGeolocationOptions opts = {
218      enable_high_accuracy,
219      should_start_paused
220    };
221    geolocation_renderers_[render_view_id] = opts;
222  } else {
223    it->second.high_accuracy = enable_high_accuracy;
224  }
225  RefreshGeolocationOptions();
226}
227
228void GeolocationDispatcherHostImpl::OnStopUpdating(int render_view_id) {
229  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
230  DVLOG(1) << __FUNCTION__ << " " << render_process_id_ << ":"
231           << render_view_id;
232  DCHECK_EQ(1U, geolocation_renderers_.count(render_view_id));
233  geolocation_renderers_.erase(render_view_id);
234  RefreshGeolocationOptions();
235}
236
237void GeolocationDispatcherHostImpl::PauseOrResume(int render_view_id,
238                                                  bool should_pause) {
239  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
240  std::map<int, RendererGeolocationOptions>::iterator it =
241      geolocation_renderers_.find(render_view_id);
242  if (it == geolocation_renderers_.end()) {
243    // This renderer is not using geolocation yet, but if it does before
244    // we get a call to resume, we should start it up in the paused state.
245    if (should_pause) {
246      pending_paused_geolocation_renderers_.insert(render_view_id);
247    } else {
248      pending_paused_geolocation_renderers_.erase(render_view_id);
249    }
250  } else {
251    RendererGeolocationOptions* opts = &(it->second);
252    if (opts->is_paused != should_pause)
253      opts->is_paused = should_pause;
254    RefreshGeolocationOptions();
255  }
256}
257
258void GeolocationDispatcherHostImpl::RefreshGeolocationOptions() {
259  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
260
261  bool needs_updates = false;
262  bool use_high_accuracy = false;
263  std::map<int, RendererGeolocationOptions>::const_iterator i =
264      geolocation_renderers_.begin();
265  for (; i != geolocation_renderers_.end(); ++i) {
266    needs_updates |= !(i->second.is_paused);
267    use_high_accuracy |= i->second.high_accuracy;
268    if (needs_updates && use_high_accuracy)
269      break;
270  }
271  if (needs_updates) {
272    if (!geolocation_provider_)
273      geolocation_provider_ = GeolocationProviderImpl::GetInstance();
274    // Re-add to re-establish our options, in case they changed.
275    geolocation_provider_->AddLocationUpdateCallback(
276        callback_, use_high_accuracy);
277  } else {
278    if (geolocation_provider_)
279      geolocation_provider_->RemoveLocationUpdateCallback(callback_);
280    geolocation_provider_ = NULL;
281  }
282}
283
284}  // namespace
285
286
287// GeolocationDispatcherHost --------------------------------------------------
288
289// static
290GeolocationDispatcherHost* GeolocationDispatcherHost::New(
291    int render_process_id,
292    GeolocationPermissionContext* geolocation_permission_context) {
293  return new GeolocationDispatcherHostImpl(
294      render_process_id,
295      geolocation_permission_context);
296}
297
298GeolocationDispatcherHost::GeolocationDispatcherHost() {
299}
300
301GeolocationDispatcherHost::~GeolocationDispatcherHost() {
302}
303
304}  // namespace content
305