// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/geolocation/geolocation_dispatcher_host.h"
#include <map>
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/metrics/histogram.h"
#include "content/browser/geolocation/geolocation_provider_impl.h"
#include "content/browser/renderer_host/render_message_filter.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/public/browser/geolocation_permission_context.h"
#include "content/public/common/geoposition.h"
#include "content/common/geolocation_messages.h"
namespace content {
namespace {
void NotifyGeolocationProviderPermissionGranted() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
GeolocationProviderImpl::GetInstance()->UserDidOptIntoLocationServices();
}
void SendGeolocationPermissionResponse(int render_process_id,
int render_view_id,
int bridge_id,
bool allowed) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
RenderViewHostImpl* render_view_host =
RenderViewHostImpl::FromID(render_process_id, render_view_id);
if (!render_view_host)
return;
render_view_host->Send(
new GeolocationMsg_PermissionSet(render_view_id, bridge_id, allowed));
if (allowed) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&NotifyGeolocationProviderPermissionGranted));
}
}
class GeolocationDispatcherHostImpl : public GeolocationDispatcherHost {
public:
GeolocationDispatcherHostImpl(
int render_process_id,
GeolocationPermissionContext* geolocation_permission_context);
// GeolocationDispatcherHost
virtual bool OnMessageReceived(const IPC::Message& msg,
bool* msg_was_ok) OVERRIDE;
private:
virtual ~GeolocationDispatcherHostImpl();
void OnRequestPermission(int render_view_id,
int bridge_id,
const GURL& requesting_frame);
void OnCancelPermissionRequest(int render_view_id,
int bridge_id,
const GURL& requesting_frame);
void OnStartUpdating(int render_view_id,
const GURL& requesting_frame,
bool enable_high_accuracy);
void OnStopUpdating(int render_view_id);
virtual void PauseOrResume(int render_view_id, bool should_pause) OVERRIDE;
// Updates the |geolocation_provider_| with the currently required update
// options.
void RefreshGeolocationOptions();
void OnLocationUpdate(const Geoposition& position);
int render_process_id_;
scoped_refptr<GeolocationPermissionContext> geolocation_permission_context_;
struct RendererGeolocationOptions {
bool high_accuracy;
bool is_paused;
};
// Used to keep track of the renderers in this process that are using
// geolocation and the options associated with them. The map is iterated
// when a location update is available and the fan out to individual bridge
// IDs happens renderer side, in order to minimize context switches.
// Only used on the IO thread.
std::map<int, RendererGeolocationOptions> geolocation_renderers_;
// Used by Android WebView to support that case that a renderer is in the
// 'paused' state but not yet using geolocation. If the renderer does start
// using geolocation while paused, we move from this set into
// |geolocation_renderers_|. If the renderer doesn't end up wanting to use
// geolocation while 'paused' then we remove from this set. A renderer id
// can exist only in this set or |geolocation_renderers_|, never both.
std::set<int> pending_paused_geolocation_renderers_;
// Only set whilst we are registered with the geolocation provider.
GeolocationProviderImpl* geolocation_provider_;
GeolocationProviderImpl::LocationUpdateCallback callback_;
DISALLOW_COPY_AND_ASSIGN(GeolocationDispatcherHostImpl);
};
GeolocationDispatcherHostImpl::GeolocationDispatcherHostImpl(
int render_process_id,
GeolocationPermissionContext* geolocation_permission_context)
: render_process_id_(render_process_id),
geolocation_permission_context_(geolocation_permission_context),
geolocation_provider_(NULL) {
callback_ = base::Bind(
&GeolocationDispatcherHostImpl::OnLocationUpdate, base::Unretained(this));
// This is initialized by ResourceMessageFilter. Do not add any non-trivial
// initialization here, defer to OnRegisterBridge which is triggered whenever
// a javascript geolocation object is actually initialized.
}
GeolocationDispatcherHostImpl::~GeolocationDispatcherHostImpl() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (geolocation_provider_)
geolocation_provider_->RemoveLocationUpdateCallback(callback_);
}
bool GeolocationDispatcherHostImpl::OnMessageReceived(
const IPC::Message& msg, bool* msg_was_ok) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
*msg_was_ok = true;
bool handled = true;
IPC_BEGIN_MESSAGE_MAP_EX(GeolocationDispatcherHostImpl, msg, *msg_was_ok)
IPC_MESSAGE_HANDLER(GeolocationHostMsg_CancelPermissionRequest,
OnCancelPermissionRequest)
IPC_MESSAGE_HANDLER(GeolocationHostMsg_RequestPermission,
OnRequestPermission)
IPC_MESSAGE_HANDLER(GeolocationHostMsg_StartUpdating, OnStartUpdating)
IPC_MESSAGE_HANDLER(GeolocationHostMsg_StopUpdating, OnStopUpdating)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void GeolocationDispatcherHostImpl::OnLocationUpdate(
const Geoposition& geoposition) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
for (std::map<int, RendererGeolocationOptions>::iterator it =
geolocation_renderers_.begin();
it != geolocation_renderers_.end(); ++it) {
if (!(it->second.is_paused))
Send(new GeolocationMsg_PositionUpdated(it->first, geoposition));
}
}
void GeolocationDispatcherHostImpl::OnRequestPermission(
int render_view_id,
int bridge_id,
const GURL& requesting_frame) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DVLOG(1) << __FUNCTION__ << " " << render_process_id_ << ":"
<< render_view_id << ":" << bridge_id;
if (geolocation_permission_context_.get()) {
geolocation_permission_context_->RequestGeolocationPermission(
render_process_id_,
render_view_id,
bridge_id,
requesting_frame,
base::Bind(&SendGeolocationPermissionResponse,
render_process_id_,
render_view_id,
bridge_id));
} else {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&SendGeolocationPermissionResponse, render_process_id_,
render_view_id, bridge_id, true));
}
}
void GeolocationDispatcherHostImpl::OnCancelPermissionRequest(
int render_view_id,
int bridge_id,
const GURL& requesting_frame) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DVLOG(1) << __FUNCTION__ << " " << render_process_id_ << ":"
<< render_view_id << ":" << bridge_id;
if (geolocation_permission_context_.get()) {
geolocation_permission_context_->CancelGeolocationPermissionRequest(
render_process_id_, render_view_id, bridge_id, requesting_frame);
}
}
void GeolocationDispatcherHostImpl::OnStartUpdating(
int render_view_id,
const GURL& requesting_frame,
bool enable_high_accuracy) {
// StartUpdating() can be invoked as a result of high-accuracy mode
// being enabled / disabled. No need to record the dispatcher again.
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DVLOG(1) << __FUNCTION__ << " " << render_process_id_ << ":"
<< render_view_id;
UMA_HISTOGRAM_BOOLEAN(
"Geolocation.GeolocationDispatcherHostImpl.EnableHighAccuracy",
enable_high_accuracy);
std::map<int, RendererGeolocationOptions>::iterator it =
geolocation_renderers_.find(render_view_id);
if (it == geolocation_renderers_.end()) {
bool should_start_paused = false;
if (pending_paused_geolocation_renderers_.erase(render_view_id) == 1) {
should_start_paused = true;
}
RendererGeolocationOptions opts = {
enable_high_accuracy,
should_start_paused
};
geolocation_renderers_[render_view_id] = opts;
} else {
it->second.high_accuracy = enable_high_accuracy;
}
RefreshGeolocationOptions();
}
void GeolocationDispatcherHostImpl::OnStopUpdating(int render_view_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DVLOG(1) << __FUNCTION__ << " " << render_process_id_ << ":"
<< render_view_id;
DCHECK_EQ(1U, geolocation_renderers_.count(render_view_id));
geolocation_renderers_.erase(render_view_id);
RefreshGeolocationOptions();
}
void GeolocationDispatcherHostImpl::PauseOrResume(int render_view_id,
bool should_pause) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
std::map<int, RendererGeolocationOptions>::iterator it =
geolocation_renderers_.find(render_view_id);
if (it == geolocation_renderers_.end()) {
// This renderer is not using geolocation yet, but if it does before
// we get a call to resume, we should start it up in the paused state.
if (should_pause) {
pending_paused_geolocation_renderers_.insert(render_view_id);
} else {
pending_paused_geolocation_renderers_.erase(render_view_id);
}
} else {
RendererGeolocationOptions* opts = &(it->second);
if (opts->is_paused != should_pause)
opts->is_paused = should_pause;
RefreshGeolocationOptions();
}
}
void GeolocationDispatcherHostImpl::RefreshGeolocationOptions() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
bool needs_updates = false;
bool use_high_accuracy = false;
std::map<int, RendererGeolocationOptions>::const_iterator i =
geolocation_renderers_.begin();
for (; i != geolocation_renderers_.end(); ++i) {
needs_updates |= !(i->second.is_paused);
use_high_accuracy |= i->second.high_accuracy;
if (needs_updates && use_high_accuracy)
break;
}
if (needs_updates) {
if (!geolocation_provider_)
geolocation_provider_ = GeolocationProviderImpl::GetInstance();
// Re-add to re-establish our options, in case they changed.
geolocation_provider_->AddLocationUpdateCallback(
callback_, use_high_accuracy);
} else {
if (geolocation_provider_)
geolocation_provider_->RemoveLocationUpdateCallback(callback_);
geolocation_provider_ = NULL;
}
}
} // namespace
// GeolocationDispatcherHost --------------------------------------------------
// static
GeolocationDispatcherHost* GeolocationDispatcherHost::New(
int render_process_id,
GeolocationPermissionContext* geolocation_permission_context) {
return new GeolocationDispatcherHostImpl(
render_process_id,
geolocation_permission_context);
}
GeolocationDispatcherHost::GeolocationDispatcherHost() {
}
GeolocationDispatcherHost::~GeolocationDispatcherHost() {
}
} // namespace content