// Copyright (c) 2011 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_permission_context.h"
#include <functional>
#include <string>
#include <vector>
#include "base/utf_string_conversions.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/geolocation/geolocation_content_settings_map.h"
#include "chrome/browser/google/google_util.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tab_contents/confirm_infobar_delegate.h"
#include "chrome/browser/tab_contents/tab_util.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/pref_names.h"
#include "content/browser/browser_thread.h"
#include "content/browser/geolocation/geolocation_provider.h"
#include "content/browser/renderer_host/render_process_host.h"
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/common/geolocation_messages.h"
#include "content/common/notification_registrar.h"
#include "content/common/notification_source.h"
#include "content/common/notification_type.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
#include "grit/theme_resources.h"
#include "net/base/net_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
// GeolocationInfoBarQueueController ------------------------------------------
// This class controls the geolocation infobar queue per profile, and it's an
// internal class to GeolocationPermissionContext.
// An alternate approach would be to have this queue per tab, and use
// notifications to broadcast when permission is set / listen to notification to
// cancel pending requests. This may be specially useful if there are other
// things listening for such notifications.
// For the time being this class is self-contained and it doesn't seem pulling
// the notification infrastructure would simplify.
class GeolocationInfoBarQueueController : NotificationObserver {
public:
GeolocationInfoBarQueueController(
GeolocationPermissionContext* geolocation_permission_context,
Profile* profile);
~GeolocationInfoBarQueueController();
// The InfoBar will be displayed immediately if the tab is not already
// displaying one, otherwise it'll be queued.
void CreateInfoBarRequest(int render_process_id,
int render_view_id,
int bridge_id,
const GURL& requesting_frame,
const GURL& emebedder);
// Cancels a specific infobar request.
void CancelInfoBarRequest(int render_process_id,
int render_view_id,
int bridge_id);
// Called by the InfoBarDelegate to notify it's closed. It'll display a new
// InfoBar if there's any request pending for this tab.
void OnInfoBarClosed(int render_process_id,
int render_view_id,
int bridge_id);
// Called by the InfoBarDelegate to notify permission has been set.
// It'll notify and dismiss any other pending InfoBar request for the same
// |requesting_frame| and embedder.
void OnPermissionSet(int render_process_id,
int render_view_id,
int bridge_id,
const GURL& requesting_frame,
const GURL& embedder,
bool allowed);
// NotificationObserver
virtual void Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details);
private:
struct PendingInfoBarRequest;
class RequestEquals;
typedef std::vector<PendingInfoBarRequest> PendingInfoBarRequests;
// Shows the first pending infobar for this tab.
void ShowQueuedInfoBar(int render_process_id, int render_view_id);
// Cancels an InfoBar request and returns the next iterator position.
PendingInfoBarRequests::iterator CancelInfoBarRequestInternal(
PendingInfoBarRequests::iterator i);
NotificationRegistrar registrar_;
GeolocationPermissionContext* const geolocation_permission_context_;
Profile* const profile_;
PendingInfoBarRequests pending_infobar_requests_;
};
// GeolocationConfirmInfoBarDelegate ------------------------------------------
namespace {
class GeolocationConfirmInfoBarDelegate : public ConfirmInfoBarDelegate {
public:
GeolocationConfirmInfoBarDelegate(
TabContents* tab_contents,
GeolocationInfoBarQueueController* controller,
int render_process_id,
int render_view_id,
int bridge_id,
const GURL& requesting_frame_url,
const std::string& display_languages);
private:
virtual ~GeolocationConfirmInfoBarDelegate();
// ConfirmInfoBarDelegate:
virtual void InfoBarClosed();
virtual SkBitmap* GetIcon() const;
virtual Type GetInfoBarType() const;
virtual string16 GetMessageText() const;
virtual string16 GetButtonLabel(InfoBarButton button) const;
virtual bool Accept();
virtual bool Cancel();
virtual string16 GetLinkText();
virtual bool LinkClicked(WindowOpenDisposition disposition);
TabContents* tab_contents_;
GeolocationInfoBarQueueController* controller_;
int render_process_id_;
int render_view_id_;
int bridge_id_;
GURL requesting_frame_url_;
std::string display_languages_;
DISALLOW_IMPLICIT_CONSTRUCTORS(GeolocationConfirmInfoBarDelegate);
};
GeolocationConfirmInfoBarDelegate::GeolocationConfirmInfoBarDelegate(
TabContents* tab_contents,
GeolocationInfoBarQueueController* controller,
int render_process_id,
int render_view_id,
int bridge_id,
const GURL& requesting_frame_url,
const std::string& display_languages)
: ConfirmInfoBarDelegate(tab_contents),
tab_contents_(tab_contents),
controller_(controller),
render_process_id_(render_process_id),
render_view_id_(render_view_id),
bridge_id_(bridge_id),
requesting_frame_url_(requesting_frame_url),
display_languages_(display_languages) {
}
GeolocationConfirmInfoBarDelegate::~GeolocationConfirmInfoBarDelegate() {
}
void GeolocationConfirmInfoBarDelegate::InfoBarClosed() {
controller_->OnInfoBarClosed(render_process_id_, render_view_id_,
bridge_id_);
delete this;
}
SkBitmap* GeolocationConfirmInfoBarDelegate::GetIcon() const {
return ResourceBundle::GetSharedInstance().GetBitmapNamed(
IDR_GEOLOCATION_INFOBAR_ICON);
}
InfoBarDelegate::Type
GeolocationConfirmInfoBarDelegate::GetInfoBarType() const {
return PAGE_ACTION_TYPE;
}
string16 GeolocationConfirmInfoBarDelegate::GetMessageText() const {
return l10n_util::GetStringFUTF16(IDS_GEOLOCATION_INFOBAR_QUESTION,
net::FormatUrl(requesting_frame_url_.GetOrigin(), display_languages_));
}
string16 GeolocationConfirmInfoBarDelegate::GetButtonLabel(
InfoBarButton button) const {
return l10n_util::GetStringUTF16((button == BUTTON_OK) ?
IDS_GEOLOCATION_ALLOW_BUTTON : IDS_GEOLOCATION_DENY_BUTTON);
}
bool GeolocationConfirmInfoBarDelegate::Accept() {
controller_->OnPermissionSet(render_process_id_, render_view_id_, bridge_id_,
requesting_frame_url_, tab_contents_->GetURL(), true);
return true;
}
bool GeolocationConfirmInfoBarDelegate::Cancel() {
controller_->OnPermissionSet(render_process_id_, render_view_id_, bridge_id_,
requesting_frame_url_, tab_contents_->GetURL(), false);
return true;
}
string16 GeolocationConfirmInfoBarDelegate::GetLinkText() {
return l10n_util::GetStringUTF16(IDS_LEARN_MORE);
}
bool GeolocationConfirmInfoBarDelegate::LinkClicked(
WindowOpenDisposition disposition) {
const char kGeolocationLearnMoreUrl[] =
#if defined(OS_CHROMEOS)
"https://www.google.com/support/chromeos/bin/answer.py?answer=142065";
#else
"https://www.google.com/support/chrome/bin/answer.py?answer=142065";
#endif
// Ignore the click disposition and always open in a new top level tab.
tab_contents_->OpenURL(
google_util::AppendGoogleLocaleParam(GURL(kGeolocationLearnMoreUrl)),
GURL(), NEW_FOREGROUND_TAB, PageTransition::LINK);
return false; // Do not dismiss the info bar.
}
} // namespace
// GeolocationInfoBarQueueController::PendingInfoBarRequest -------------------
struct GeolocationInfoBarQueueController::PendingInfoBarRequest {
public:
PendingInfoBarRequest(int render_process_id,
int render_view_id,
int bridge_id,
const GURL& requesting_frame,
const GURL& embedder);
bool IsForTab(int p_render_process_id, int p_render_view_id) const;
bool IsForPair(const GURL& p_requesting_frame,
const GURL& p_embedder) const;
bool Equals(int p_render_process_id,
int p_render_view_id,
int p_bridge_id) const;
int render_process_id;
int render_view_id;
int bridge_id;
GURL requesting_frame;
GURL embedder;
InfoBarDelegate* infobar_delegate;
};
GeolocationInfoBarQueueController::PendingInfoBarRequest::PendingInfoBarRequest(
int render_process_id,
int render_view_id,
int bridge_id,
const GURL& requesting_frame,
const GURL& embedder)
: render_process_id(render_process_id),
render_view_id(render_view_id),
bridge_id(bridge_id),
requesting_frame(requesting_frame),
embedder(embedder),
infobar_delegate(NULL) {
}
bool GeolocationInfoBarQueueController::PendingInfoBarRequest::IsForTab(
int p_render_process_id,
int p_render_view_id) const {
return (render_process_id == p_render_process_id) &&
(render_view_id == p_render_view_id);
}
bool GeolocationInfoBarQueueController::PendingInfoBarRequest::IsForPair(
const GURL& p_requesting_frame,
const GURL& p_embedder) const {
return (requesting_frame == p_requesting_frame) && (embedder == p_embedder);
}
bool GeolocationInfoBarQueueController::PendingInfoBarRequest::Equals(
int p_render_process_id,
int p_render_view_id,
int p_bridge_id) const {
return IsForTab(p_render_process_id, p_render_view_id) &&
(bridge_id == p_bridge_id);
}
// GeolocationInfoBarQueueController::RequestEquals ---------------------------
// Useful predicate for checking PendingInfoBarRequest equality.
class GeolocationInfoBarQueueController::RequestEquals
: public std::unary_function<PendingInfoBarRequest, bool> {
public:
RequestEquals(int render_process_id, int render_view_id, int bridge_id);
bool operator()(const PendingInfoBarRequest& request) const;
private:
int render_process_id_;
int render_view_id_;
int bridge_id_;
};
GeolocationInfoBarQueueController::RequestEquals::RequestEquals(
int render_process_id,
int render_view_id,
int bridge_id)
: render_process_id_(render_process_id),
render_view_id_(render_view_id),
bridge_id_(bridge_id) {
}
bool GeolocationInfoBarQueueController::RequestEquals::operator()(
const PendingInfoBarRequest& request) const {
return request.Equals(render_process_id_, render_view_id_, bridge_id_);
}
// GeolocationInfoBarQueueController ------------------------------------------
GeolocationInfoBarQueueController::GeolocationInfoBarQueueController(
GeolocationPermissionContext* geolocation_permission_context,
Profile* profile)
: geolocation_permission_context_(geolocation_permission_context),
profile_(profile) {
}
GeolocationInfoBarQueueController::~GeolocationInfoBarQueueController() {
}
void GeolocationInfoBarQueueController::CreateInfoBarRequest(
int render_process_id,
int render_view_id,
int bridge_id,
const GURL& requesting_frame,
const GURL& embedder) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// We shouldn't get duplicate requests.
DCHECK(std::find_if(pending_infobar_requests_.begin(),
pending_infobar_requests_.end(),
RequestEquals(render_process_id, render_view_id, bridge_id)) ==
pending_infobar_requests_.end());
pending_infobar_requests_.push_back(PendingInfoBarRequest(render_process_id,
render_view_id, bridge_id, requesting_frame, embedder));
ShowQueuedInfoBar(render_process_id, render_view_id);
}
void GeolocationInfoBarQueueController::CancelInfoBarRequest(
int render_process_id,
int render_view_id,
int bridge_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
PendingInfoBarRequests::iterator i = std::find_if(
pending_infobar_requests_.begin(), pending_infobar_requests_.end(),
RequestEquals(render_process_id, render_view_id, bridge_id));
// TODO(pkasting): Can this conditional become a DCHECK()?
if (i != pending_infobar_requests_.end())
CancelInfoBarRequestInternal(i);
}
void GeolocationInfoBarQueueController::OnInfoBarClosed(int render_process_id,
int render_view_id,
int bridge_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
PendingInfoBarRequests::iterator i = std::find_if(
pending_infobar_requests_.begin(), pending_infobar_requests_.end(),
RequestEquals(render_process_id, render_view_id, bridge_id));
if (i != pending_infobar_requests_.end())
pending_infobar_requests_.erase(i);
ShowQueuedInfoBar(render_process_id, render_view_id);
}
void GeolocationInfoBarQueueController::OnPermissionSet(
int render_process_id,
int render_view_id,
int bridge_id,
const GURL& requesting_frame,
const GURL& embedder,
bool allowed) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ContentSetting content_setting =
allowed ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
profile_->GetGeolocationContentSettingsMap()->SetContentSetting(
requesting_frame.GetOrigin(), embedder.GetOrigin(), content_setting);
for (PendingInfoBarRequests::iterator i = pending_infobar_requests_.begin();
i != pending_infobar_requests_.end(); ) {
if (i->IsForPair(requesting_frame, embedder)) {
// Cancel this request first, then notify listeners. TODO(pkasting): Why
// is this order important?
// NOTE: If the pending request had an infobar, TabContents will close it
// either synchronously or asynchronously, which will then pump the queue
// via OnInfoBarClosed().
PendingInfoBarRequest copied_request = *i;
// Don't let CancelInfoBarRequestInternal() call RemoveInfoBar() on the
// delegate that's currently calling us. That delegate is in either
// Accept() or Cancel(), so its owning InfoBar will call RemoveInfoBar()
// later on in this callstack anyway; and if we do it here, and it causes
// the delegate to be deleted, our GURL& args will point to garbage and we
// may also cause other problems during stack unwinding.
if (i->Equals(render_process_id, render_view_id, bridge_id))
i->infobar_delegate = NULL;
i = CancelInfoBarRequestInternal(i);
geolocation_permission_context_->NotifyPermissionSet(
copied_request.render_process_id, copied_request.render_view_id,
copied_request.bridge_id, copied_request.requesting_frame, allowed);
} else {
++i;
}
}
}
void GeolocationInfoBarQueueController::Observe(
NotificationType type, const NotificationSource& source,
const NotificationDetails& details) {
registrar_.Remove(this, NotificationType::TAB_CONTENTS_DESTROYED,
source);
TabContents* tab_contents = Source<TabContents>(source).ptr();
for (PendingInfoBarRequests::iterator i = pending_infobar_requests_.begin();
i != pending_infobar_requests_.end();) {
if (i->infobar_delegate == NULL &&
tab_contents == tab_util::GetTabContentsByID(i->render_process_id,
i->render_view_id)) {
i = pending_infobar_requests_.erase(i);
} else {
++i;
}
}
}
void GeolocationInfoBarQueueController::ShowQueuedInfoBar(int render_process_id,
int render_view_id) {
TabContents* tab_contents =
tab_util::GetTabContentsByID(render_process_id, render_view_id);
for (PendingInfoBarRequests::iterator i = pending_infobar_requests_.begin();
i != pending_infobar_requests_.end(); ) {
if (i->IsForTab(render_process_id, render_view_id)) {
if (!tab_contents) {
i = pending_infobar_requests_.erase(i);
continue;
}
if (!i->infobar_delegate) {
if (!registrar_.IsRegistered(this,
NotificationType::TAB_CONTENTS_DESTROYED,
Source<TabContents>(tab_contents))) {
registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
Source<TabContents>(tab_contents));
}
i->infobar_delegate = new GeolocationConfirmInfoBarDelegate(
tab_contents, this, render_process_id, render_view_id, i->bridge_id,
i->requesting_frame,
profile_->GetPrefs()->GetString(prefs::kAcceptLanguages));
tab_contents->AddInfoBar(i->infobar_delegate);
}
break;
}
++i;
}
}
GeolocationInfoBarQueueController::PendingInfoBarRequests::iterator
GeolocationInfoBarQueueController::CancelInfoBarRequestInternal(
PendingInfoBarRequests::iterator i) {
InfoBarDelegate* delegate = i->infobar_delegate;
if (!delegate)
return pending_infobar_requests_.erase(i);
TabContents* tab_contents =
tab_util::GetTabContentsByID(i->render_process_id, i->render_view_id);
if (!tab_contents)
return pending_infobar_requests_.erase(i);
// TabContents will destroy the InfoBar, which will remove from our vector
// asynchronously.
tab_contents->RemoveInfoBar(i->infobar_delegate);
return ++i;
}
// GeolocationPermissionContext -----------------------------------------------
GeolocationPermissionContext::GeolocationPermissionContext(
Profile* profile)
: profile_(profile),
ALLOW_THIS_IN_INITIALIZER_LIST(geolocation_infobar_queue_controller_(
new GeolocationInfoBarQueueController(this, profile))) {
}
GeolocationPermissionContext::~GeolocationPermissionContext() {
}
void GeolocationPermissionContext::RequestGeolocationPermission(
int render_process_id, int render_view_id, int bridge_id,
const GURL& requesting_frame) {
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, NewRunnableMethod(
this, &GeolocationPermissionContext::RequestGeolocationPermission,
render_process_id, render_view_id, bridge_id, requesting_frame));
return;
}
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ExtensionService* extensions = profile_->GetExtensionService();
if (extensions) {
const Extension* ext = extensions->GetExtensionByURL(requesting_frame);
if (!ext)
ext = extensions->GetExtensionByWebExtent(requesting_frame);
if (ext && ext->HasApiPermission(Extension::kGeolocationPermission)) {
ExtensionProcessManager* epm = profile_->GetExtensionProcessManager();
RenderProcessHost* process = epm->GetExtensionProcess(requesting_frame);
if (process && process->id() == render_process_id) {
NotifyPermissionSet(render_process_id, render_view_id, bridge_id,
requesting_frame, true);
return;
}
}
}
TabContents* tab_contents =
tab_util::GetTabContentsByID(render_process_id, render_view_id);
if (!tab_contents) {
// The tab may have gone away, or the request may not be from a tab at all.
LOG(WARNING) << "Attempt to use geolocation tabless renderer: "
<< render_process_id << "," << render_view_id << ","
<< bridge_id << " (can't prompt user without a visible tab)";
NotifyPermissionSet(render_process_id, render_view_id, bridge_id,
requesting_frame, false);
return;
}
GURL embedder = tab_contents->GetURL();
if (!requesting_frame.is_valid() || !embedder.is_valid()) {
LOG(WARNING) << "Attempt to use geolocation from an invalid URL: "
<< requesting_frame << "," << embedder
<< " (geolocation is not supported in popups)";
NotifyPermissionSet(render_process_id, render_view_id, bridge_id,
requesting_frame, false);
return;
}
ContentSetting content_setting =
profile_->GetGeolocationContentSettingsMap()->GetContentSetting(
requesting_frame, embedder);
if (content_setting == CONTENT_SETTING_BLOCK) {
NotifyPermissionSet(render_process_id, render_view_id, bridge_id,
requesting_frame, false);
} else if (content_setting == CONTENT_SETTING_ALLOW) {
NotifyPermissionSet(render_process_id, render_view_id, bridge_id,
requesting_frame, true);
} else { // setting == ask. Prompt the user.
geolocation_infobar_queue_controller_->CreateInfoBarRequest(
render_process_id, render_view_id, bridge_id, requesting_frame,
embedder);
}
}
void GeolocationPermissionContext::CancelGeolocationPermissionRequest(
int render_process_id,
int render_view_id,
int bridge_id,
const GURL& requesting_frame) {
CancelPendingInfoBarRequest(render_process_id, render_view_id, bridge_id);
}
void GeolocationPermissionContext::NotifyPermissionSet(
int render_process_id,
int render_view_id,
int bridge_id,
const GURL& requesting_frame,
bool allowed) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
TabContents* tab_contents =
tab_util::GetTabContentsByID(render_process_id, render_view_id);
// TabContents may have gone away (or not exists for extension).
if (tab_contents) {
TabSpecificContentSettings* content_settings =
tab_contents->GetTabSpecificContentSettings();
content_settings->OnGeolocationPermissionSet(requesting_frame.GetOrigin(),
allowed);
}
RenderViewHost* r = RenderViewHost::FromID(render_process_id, render_view_id);
if (r) {
r->Send(new GeolocationMsg_PermissionSet(
render_view_id, bridge_id, allowed));
}
if (allowed) {
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, NewRunnableMethod(
this, &GeolocationPermissionContext::NotifyArbitratorPermissionGranted,
requesting_frame));
}
}
void GeolocationPermissionContext::NotifyArbitratorPermissionGranted(
const GURL& requesting_frame) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
GeolocationProvider::GetInstance()->OnPermissionGranted(requesting_frame);
}
void GeolocationPermissionContext::CancelPendingInfoBarRequest(
int render_process_id,
int render_view_id,
int bridge_id) {
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, NewRunnableMethod(
this, &GeolocationPermissionContext::CancelPendingInfoBarRequest,
render_process_id, render_view_id, bridge_id));
return;
}
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
geolocation_infobar_queue_controller_->CancelInfoBarRequest(render_process_id,
render_view_id, bridge_id);
}