// 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