blob: 4e62dce86d4d5ea3487b5b0d562e91d400d34526 [file] [log] [blame]
// Copyright 2013 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 "chrome/browser/permissions/permission_queue_controller.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/geolocation/geolocation_infobar_delegate_android.h"
#include "chrome/browser/infobars/infobar_service.h"
#include "chrome/browser/media/midi_permission_infobar_delegate_android.h"
#include "chrome/browser/media/protected_media_identifier_infobar_delegate_android.h"
#include "chrome/browser/notifications/notification_permission_infobar_delegate.h"
#include "chrome/browser/permissions/permission_request_id.h"
#include "chrome/browser/permissions/permission_uma_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tab_contents/tab_util.h"
#include "chrome/common/pref_names.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/infobars/core/infobar.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
namespace {
InfoBarService* GetInfoBarService(const PermissionRequestID& id) {
content::WebContents* web_contents = tab_util::GetWebContentsByFrameID(
id.render_process_id(), id.render_frame_id());
return web_contents ? InfoBarService::FromWebContents(web_contents) : NULL;
}
bool ArePermissionRequestsForSameTab(
const PermissionRequestID& request,
const PermissionRequestID& other_request) {
content::WebContents* web_contents = tab_util::GetWebContentsByFrameID(
request.render_process_id(), request.render_frame_id());
content::WebContents* other_web_contents = tab_util::GetWebContentsByFrameID(
other_request.render_process_id(), other_request.render_frame_id());
return web_contents == other_web_contents;
}
} // anonymous namespace
class PermissionQueueController::PendingInfobarRequest {
public:
PendingInfobarRequest(content::PermissionType type,
const PermissionRequestID& id,
const GURL& requesting_frame,
const GURL& embedder,
const PermissionDecidedCallback& callback);
~PendingInfobarRequest();
bool IsForPair(const GURL& requesting_frame,
const GURL& embedder) const;
const PermissionRequestID& id() const { return id_; }
const GURL& requesting_frame() const { return requesting_frame_; }
bool has_infobar() const { return !!infobar_; }
infobars::InfoBar* infobar() { return infobar_; }
void RunCallback(ContentSetting content_setting);
void CreateInfoBar(PermissionQueueController* controller,
const std::string& display_languages);
private:
content::PermissionType type_;
PermissionRequestID id_;
GURL requesting_frame_;
GURL embedder_;
PermissionDecidedCallback callback_;
infobars::InfoBar* infobar_;
// Purposefully do not disable copying, as this is stored in STL containers.
};
PermissionQueueController::PendingInfobarRequest::PendingInfobarRequest(
content::PermissionType type,
const PermissionRequestID& id,
const GURL& requesting_frame,
const GURL& embedder,
const PermissionDecidedCallback& callback)
: type_(type),
id_(id),
requesting_frame_(requesting_frame),
embedder_(embedder),
callback_(callback),
infobar_(NULL) {}
PermissionQueueController::PendingInfobarRequest::~PendingInfobarRequest() {
}
bool PermissionQueueController::PendingInfobarRequest::IsForPair(
const GURL& requesting_frame,
const GURL& embedder) const {
return (requesting_frame_ == requesting_frame) && (embedder_ == embedder);
}
void PermissionQueueController::PendingInfobarRequest::RunCallback(
ContentSetting content_setting) {
callback_.Run(content_setting);
}
void PermissionQueueController::PendingInfobarRequest::CreateInfoBar(
PermissionQueueController* controller,
const std::string& display_languages) {
// Controller can be Unretained because the lifetime of the infobar
// is tied to that of the queue controller. Before QueueController
// is destroyed, all requests will be cancelled and so all delegates
// will be destroyed.
PermissionInfobarDelegate::PermissionSetCallback callback =
base::Bind(&PermissionQueueController::OnPermissionSet,
base::Unretained(controller),
id_,
requesting_frame_,
embedder_);
switch (type_) {
case content::PermissionType::GEOLOCATION:
infobar_ = GeolocationInfoBarDelegateAndroid::Create(
GetInfoBarService(id_), requesting_frame_, display_languages,
callback);
break;
#if defined(ENABLE_NOTIFICATIONS)
case content::PermissionType::NOTIFICATIONS:
infobar_ = NotificationPermissionInfobarDelegate::Create(
GetInfoBarService(id_), requesting_frame_,
display_languages, callback);
break;
#endif // ENABLE_NOTIFICATIONS
case content::PermissionType::MIDI_SYSEX:
infobar_ = MidiPermissionInfoBarDelegateAndroid::Create(
GetInfoBarService(id_), requesting_frame_, display_languages,
callback);
break;
case content::PermissionType::PROTECTED_MEDIA_IDENTIFIER:
infobar_ = ProtectedMediaIdentifierInfoBarDelegateAndroid::Create(
GetInfoBarService(id_), requesting_frame_, display_languages,
callback);
break;
default:
NOTREACHED();
break;
}
}
PermissionQueueController::PermissionQueueController(
Profile* profile,
content::PermissionType permission_type,
ContentSettingsType content_settings_type)
: profile_(profile),
permission_type_(permission_type),
content_settings_type_(content_settings_type),
in_shutdown_(false) {}
PermissionQueueController::~PermissionQueueController() {
// Cancel all outstanding requests.
in_shutdown_ = true;
while (!pending_infobar_requests_.empty())
CancelInfoBarRequest(pending_infobar_requests_.front().id());
}
void PermissionQueueController::CreateInfoBarRequest(
const PermissionRequestID& id,
const GURL& requesting_frame,
const GURL& embedder,
const PermissionDecidedCallback& callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (requesting_frame.SchemeIs(content::kChromeUIScheme) ||
embedder.SchemeIs(content::kChromeUIScheme))
return;
pending_infobar_requests_.push_back(PendingInfobarRequest(
permission_type_, id, requesting_frame, embedder, callback));
if (!AlreadyShowingInfoBarForTab(id))
ShowQueuedInfoBarForTab(id);
}
void PermissionQueueController::CancelInfoBarRequest(
const PermissionRequestID& id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
for (PendingInfobarRequests::iterator i(pending_infobar_requests_.begin());
i != pending_infobar_requests_.end(); ++i) {
if (id != i->id())
continue;
InfoBarService* infobar_service = GetInfoBarService(id);
if (infobar_service && i->has_infobar())
infobar_service->RemoveInfoBar(i->infobar());
else
pending_infobar_requests_.erase(i);
return;
}
}
void PermissionQueueController::OnPermissionSet(
const PermissionRequestID& id,
const GURL& requesting_frame,
const GURL& embedder,
bool update_content_setting,
bool allowed) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// TODO(miguelg): move the permission persistence to
// PermissionContextBase once all the types are moved there.
if (update_content_setting) {
UpdateContentSetting(requesting_frame, embedder, allowed);
if (allowed)
PermissionUmaUtil::PermissionGranted(permission_type_, requesting_frame);
else
PermissionUmaUtil::PermissionDenied(permission_type_, requesting_frame);
} else {
PermissionUmaUtil::PermissionDismissed(permission_type_, requesting_frame);
}
// Cancel this request first, then notify listeners. TODO(pkasting): Why
// is this order important?
PendingInfobarRequests requests_to_notify;
PendingInfobarRequests infobars_to_remove;
std::vector<PendingInfobarRequests::iterator> pending_requests_to_remove;
for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin();
i != pending_infobar_requests_.end(); ++i) {
if (!i->IsForPair(requesting_frame, embedder))
continue;
requests_to_notify.push_back(*i);
if (!i->has_infobar()) {
// We haven't created an infobar yet, just record the pending request
// index and remove it later.
pending_requests_to_remove.push_back(i);
continue;
}
if (id == i->id()) {
// The infobar that called us is i->infobar(), and its delegate is
// currently in either Accept() or Cancel(). This means that
// RemoveInfoBar() will be called later on, and that will trigger a
// notification we're observing.
continue;
}
// This infobar is for the same frame/embedder pair, but in a different
// tab. We should remove it now that we've got an answer for it.
infobars_to_remove.push_back(*i);
}
// Remove all infobars for the same |requesting_frame| and |embedder|.
for (PendingInfobarRequests::iterator i = infobars_to_remove.begin();
i != infobars_to_remove.end(); ++i)
GetInfoBarService(i->id())->RemoveInfoBar(i->infobar());
// PermissionContextBase needs to know about the new ContentSetting value,
// CONTENT_SETTING_DEFAULT being the value for nothing happened. The callers
// of ::OnPermissionSet passes { true, true } for allow, { true, false } for
// block and { false, * } for dismissed. The tuple being
// { update_content_setting, allowed }.
ContentSetting content_setting = CONTENT_SETTING_DEFAULT;
if (update_content_setting) {
content_setting = allowed ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
}
// Send out the permission notifications.
for (PendingInfobarRequests::iterator i = requests_to_notify.begin();
i != requests_to_notify.end(); ++i)
i->RunCallback(content_setting);
// Remove the pending requests in reverse order.
for (int i = pending_requests_to_remove.size() - 1; i >= 0; --i)
pending_infobar_requests_.erase(pending_requests_to_remove[i]);
}
void PermissionQueueController::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, type);
// We will receive this notification for all infobar closures, so we need to
// check whether this is the geolocation infobar we're tracking. Note that the
// InfoBarContainer (if any) may have received this notification before us and
// caused the infobar to be deleted, so it's not safe to dereference the
// contents of the infobar. The address of the infobar, however, is OK to
// use to find the PendingInfobarRequest to remove because
// pending_infobar_requests_ will not have received any new entries between
// the NotificationService's call to InfoBarContainer::Observe and this
// method.
infobars::InfoBar* infobar =
content::Details<infobars::InfoBar::RemovedDetails>(details)->first;
for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin();
i != pending_infobar_requests_.end(); ++i) {
if (i->infobar() == infobar) {
PermissionRequestID id(i->id());
pending_infobar_requests_.erase(i);
ShowQueuedInfoBarForTab(id);
return;
}
}
}
bool PermissionQueueController::AlreadyShowingInfoBarForTab(
const PermissionRequestID& id) const {
for (PendingInfobarRequests::const_iterator i(
pending_infobar_requests_.begin());
i != pending_infobar_requests_.end(); ++i) {
if (ArePermissionRequestsForSameTab(i->id(), id) && i->has_infobar())
return true;
}
return false;
}
void PermissionQueueController::ShowQueuedInfoBarForTab(
const PermissionRequestID& id) {
DCHECK(!AlreadyShowingInfoBarForTab(id));
// We can get here for example during tab shutdown, when the InfoBarService is
// removing all existing infobars, thus calling back to Observe(). In this
// case the service still exists, and is supplied as the source of the
// notification we observed, but is no longer accessible from its WebContents.
// In this case we should just go ahead and cancel further infobars for this
// tab instead of trying to access the service.
//
// Similarly, if we're being destroyed, we should also avoid showing further
// infobars.
InfoBarService* infobar_service = GetInfoBarService(id);
if (!infobar_service || in_shutdown_) {
ClearPendingInfobarRequestsForTab(id);
return;
}
for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin();
i != pending_infobar_requests_.end(); ++i) {
if (ArePermissionRequestsForSameTab(i->id(), id) && !i->has_infobar()) {
RegisterForInfoBarNotifications(infobar_service);
i->CreateInfoBar(
this, profile_->GetPrefs()->GetString(prefs::kAcceptLanguages));
return;
}
}
UnregisterForInfoBarNotifications(infobar_service);
}
void PermissionQueueController::ClearPendingInfobarRequestsForTab(
const PermissionRequestID& id) {
for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin();
i != pending_infobar_requests_.end(); ) {
if (ArePermissionRequestsForSameTab(i->id(), id)) {
DCHECK(!i->has_infobar());
i = pending_infobar_requests_.erase(i);
} else {
++i;
}
}
}
void PermissionQueueController::RegisterForInfoBarNotifications(
InfoBarService* infobar_service) {
if (!registrar_.IsRegistered(
this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
content::Source<InfoBarService>(infobar_service))) {
registrar_.Add(this,
chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
content::Source<InfoBarService>(infobar_service));
}
}
void PermissionQueueController::UnregisterForInfoBarNotifications(
InfoBarService* infobar_service) {
if (registrar_.IsRegistered(
this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
content::Source<InfoBarService>(infobar_service))) {
registrar_.Remove(this,
chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
content::Source<InfoBarService>(infobar_service));
}
}
void PermissionQueueController::UpdateContentSetting(
const GURL& requesting_frame,
const GURL& embedder,
bool allowed) {
if (requesting_frame.GetOrigin().SchemeIsFile()) {
// Chrome can be launched with --disable-web-security which allows
// geolocation requests from file:// URLs. We don't want to store these
// in the host content settings map.
return;
}
ContentSetting content_setting =
allowed ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
HostContentSettingsMapFactory::GetForProfile(profile_)
->SetContentSettingDefaultScope(
requesting_frame.GetOrigin(), embedder.GetOrigin(),
content_settings_type_, std::string(), content_setting);
}