This source file includes following definitions.
- HasAvailableDevicesForRequest
- IsInKioskMode
- requested_device_id
- MediaStreamTypeSettings
- MediaStreamTypeSettings
- callback_
- RegisterProfilePrefs
- DismissInfoBarAndTakeActionOnSettings
- HasAudio
- HasVideo
- GetSecurityOriginSpec
- Accept
- Deny
- GetIconID
- GetMessageText
- GetMessageTextFragment
- HasUserGesture
- GetRequestingHostname
- PermissionGranted
- PermissionDenied
- Cancelled
- RequestFinished
- GetDevicePolicy
- IsRequestAllowedByDefault
- FilterBlockedByDefaultDevices
- IsDefaultMediaAccessBlocked
- IsSchemeSecure
- ShouldAlwaysAllowOrigin
- SetPermission
- NotifyUIRequestAccepted
- NotifyUIRequestDenied
- IsDeviceAudioCaptureRequestedAndAllowed
- IsDeviceVideoCaptureRequestedAndAllowed
#include "chrome/browser/media/media_stream_devices_controller.h"
#include "base/command_line.h"
#include "base/metrics/histogram.h"
#include "base/prefs/pref_service.h"
#include "base/prefs/scoped_user_pref_update.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/content_settings/content_settings_provider.h"
#include "chrome/browser/content_settings/host_content_settings_map.h"
#include "chrome/browser/content_settings/tab_specific_content_settings.h"
#include "chrome/browser/media/media_capture_devices_dispatcher.h"
#include "chrome/browser/media/media_stream_capture_indicator.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/content_settings.h"
#include "chrome/common/content_settings_pattern.h"
#include "chrome/common/pref_names.h"
#include "components/user_prefs/pref_registry_syncable.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/media_stream_request.h"
#include "extensions/common/constants.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/login/user_manager.h"
#endif
using content::BrowserThread;
namespace {
bool HasAvailableDevicesForRequest(const content::MediaStreamRequest& request) {
bool has_audio_device =
request.audio_type == content::MEDIA_NO_SERVICE ||
!MediaCaptureDevicesDispatcher::GetInstance()->GetAudioCaptureDevices()
.empty();
bool has_video_device =
request.video_type == content::MEDIA_NO_SERVICE ||
!MediaCaptureDevicesDispatcher::GetInstance()->GetVideoCaptureDevices()
.empty();
return has_audio_device && has_video_device;
}
bool IsInKioskMode() {
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode))
return true;
#if defined(OS_CHROMEOS)
const chromeos::UserManager* user_manager = chromeos::UserManager::Get();
return user_manager && user_manager->IsLoggedInAsKioskApp();
#else
return false;
#endif
}
enum DevicePermissionActions {
kAllowHttps = 0,
kAllowHttp,
kDeny,
kCancel,
kPermissionActionsMax
};
}
MediaStreamDevicesController::MediaStreamTypeSettings::MediaStreamTypeSettings(
Permission permission, const std::string& requested_device_id):
permission(permission), requested_device_id(requested_device_id) {}
MediaStreamDevicesController::MediaStreamTypeSettings::
MediaStreamTypeSettings(): permission(MEDIA_NONE) {}
MediaStreamDevicesController::MediaStreamTypeSettings::
~MediaStreamTypeSettings() {}
MediaStreamDevicesController::MediaStreamDevicesController(
content::WebContents* web_contents,
const content::MediaStreamRequest& request,
const content::MediaResponseCallback& callback)
: web_contents_(web_contents),
request_(request),
callback_(callback) {
profile_ = Profile::FromBrowserContext(web_contents->GetBrowserContext());
content_settings_ = TabSpecificContentSettings::FromWebContents(web_contents);
if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE ||
request.request_type == content::MEDIA_OPEN_DEVICE) {
if (GetDevicePolicy(prefs::kAudioCaptureAllowed,
prefs::kAudioCaptureAllowedUrls) == ALWAYS_DENY) {
request_permissions_.insert(std::make_pair(
content::MEDIA_DEVICE_AUDIO_CAPTURE,
MediaStreamTypeSettings(MEDIA_BLOCKED_BY_POLICY,
request.requested_audio_device_id)));
} else {
request_permissions_.insert(std::make_pair(
content::MEDIA_DEVICE_AUDIO_CAPTURE,
MediaStreamTypeSettings(MEDIA_ALLOWED,
request.requested_audio_device_id)));
}
}
if (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE ||
request.request_type == content::MEDIA_OPEN_DEVICE) {
if (GetDevicePolicy(prefs::kVideoCaptureAllowed,
prefs::kVideoCaptureAllowedUrls) == ALWAYS_DENY) {
request_permissions_.insert(std::make_pair(
content::MEDIA_DEVICE_VIDEO_CAPTURE,
MediaStreamTypeSettings(MEDIA_BLOCKED_BY_POLICY,
request.requested_video_device_id)));
} else {
request_permissions_.insert(std::make_pair(
content::MEDIA_DEVICE_VIDEO_CAPTURE,
MediaStreamTypeSettings(MEDIA_ALLOWED,
request.requested_video_device_id)));
}
}
}
MediaStreamDevicesController::~MediaStreamDevicesController() {
if (!callback_.is_null()) {
callback_.Run(content::MediaStreamDevices(),
content::MEDIA_DEVICE_INVALID_STATE,
scoped_ptr<content::MediaStreamUI>());
}
}
void MediaStreamDevicesController::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* prefs) {
prefs->RegisterBooleanPref(prefs::kVideoCaptureAllowed,
true,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
prefs->RegisterBooleanPref(prefs::kAudioCaptureAllowed,
true,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
prefs->RegisterListPref(prefs::kVideoCaptureAllowedUrls,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
prefs->RegisterListPref(prefs::kAudioCaptureAllowedUrls,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
}
bool MediaStreamDevicesController::DismissInfoBarAndTakeActionOnSettings() {
if (request_.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE ||
request_.video_type == content::MEDIA_TAB_VIDEO_CAPTURE) {
Deny(false, content::MEDIA_DEVICE_INVALID_STATE);
return true;
}
if (request_.security_origin.is_empty()) {
Deny(false, content::MEDIA_DEVICE_INVALID_SECURITY_ORIGIN);
return true;
}
if (!HasAvailableDevicesForRequest(request_)) {
Deny(false, content::MEDIA_DEVICE_NO_HARDWARE);
return true;
}
if (IsRequestAllowedByDefault()) {
Accept(false);
return true;
}
if (FilterBlockedByDefaultDevices() == 0) {
Deny(false, content::MEDIA_DEVICE_PERMISSION_DENIED);
return true;
}
if (IsDefaultMediaAccessBlocked()) {
Deny(false, content::MEDIA_DEVICE_PERMISSION_DENIED);
return true;
}
if (request_.request_type == content::MEDIA_OPEN_DEVICE) {
bool no_matched_audio_device =
(request_.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE &&
!request_.requested_audio_device_id.empty() &&
MediaCaptureDevicesDispatcher::GetInstance()->GetRequestedAudioDevice(
request_.requested_audio_device_id) == NULL);
bool no_matched_video_device =
(request_.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE &&
!request_.requested_video_device_id.empty() &&
MediaCaptureDevicesDispatcher::GetInstance()->GetRequestedVideoDevice(
request_.requested_video_device_id) == NULL);
if (no_matched_audio_device || no_matched_video_device) {
Deny(false, content::MEDIA_DEVICE_PERMISSION_DENIED);
return true;
}
}
return false;
}
bool MediaStreamDevicesController::HasAudio() const {
return IsDeviceAudioCaptureRequestedAndAllowed();
}
bool MediaStreamDevicesController::HasVideo() const {
return IsDeviceVideoCaptureRequestedAndAllowed();
}
const std::string& MediaStreamDevicesController::GetSecurityOriginSpec() const {
return request_.security_origin.spec();
}
void MediaStreamDevicesController::Accept(bool update_content_setting) {
NotifyUIRequestAccepted();
content::MediaStreamDevices devices;
bool audio_allowed = IsDeviceAudioCaptureRequestedAndAllowed();
bool video_allowed = IsDeviceVideoCaptureRequestedAndAllowed();
if (audio_allowed || video_allowed) {
switch (request_.request_type) {
case content::MEDIA_OPEN_DEVICE: {
const content::MediaStreamDevice* device = NULL;
if (audio_allowed &&
request_.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE) {
if (!request_.requested_audio_device_id.empty()) {
device = MediaCaptureDevicesDispatcher::GetInstance()->
GetRequestedAudioDevice(request_.requested_audio_device_id);
} else {
device = MediaCaptureDevicesDispatcher::GetInstance()->
GetFirstAvailableAudioDevice();
}
} else if (video_allowed &&
request_.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) {
if (!request_.requested_video_device_id.empty()) {
device = MediaCaptureDevicesDispatcher::GetInstance()->
GetRequestedVideoDevice(request_.requested_video_device_id);
} else {
device = MediaCaptureDevicesDispatcher::GetInstance()->
GetFirstAvailableVideoDevice();
}
}
if (device)
devices.push_back(*device);
break;
}
case content::MEDIA_GENERATE_STREAM: {
bool get_default_audio_device = audio_allowed;
bool get_default_video_device = video_allowed;
if (audio_allowed && !request_.requested_audio_device_id.empty()) {
const content::MediaStreamDevice* audio_device =
MediaCaptureDevicesDispatcher::GetInstance()->
GetRequestedAudioDevice(request_.requested_audio_device_id);
if (audio_device) {
devices.push_back(*audio_device);
get_default_audio_device = false;
}
}
if (video_allowed && !request_.requested_video_device_id.empty()) {
const content::MediaStreamDevice* video_device =
MediaCaptureDevicesDispatcher::GetInstance()->
GetRequestedVideoDevice(request_.requested_video_device_id);
if (video_device) {
devices.push_back(*video_device);
get_default_video_device = false;
}
}
if (get_default_audio_device || get_default_video_device) {
MediaCaptureDevicesDispatcher::GetInstance()->
GetDefaultDevicesForProfile(profile_,
get_default_audio_device,
get_default_video_device,
&devices);
}
break;
}
case content::MEDIA_DEVICE_ACCESS: {
MediaCaptureDevicesDispatcher::GetInstance()->
GetDefaultDevicesForProfile(profile_,
audio_allowed,
video_allowed,
&devices);
break;
}
case content::MEDIA_ENUMERATE_DEVICES: {
NOTREACHED();
break;
}
}
if (update_content_setting) {
if ((IsSchemeSecure() && !devices.empty()) ||
request_.request_type == content::MEDIA_OPEN_DEVICE) {
SetPermission(true);
}
}
}
scoped_ptr<content::MediaStreamUI> ui;
if (!devices.empty()) {
ui = MediaCaptureDevicesDispatcher::GetInstance()->
GetMediaStreamCaptureIndicator()->RegisterMediaStream(
web_contents_, devices);
}
content::MediaResponseCallback cb = callback_;
callback_.Reset();
cb.Run(devices,
devices.empty() ?
content::MEDIA_DEVICE_NO_HARDWARE : content::MEDIA_DEVICE_OK,
ui.Pass());
}
void MediaStreamDevicesController::Deny(
bool update_content_setting,
content::MediaStreamRequestResult result) {
DLOG(WARNING) << "MediaStreamDevicesController::Deny: " << result;
NotifyUIRequestDenied();
if (update_content_setting)
SetPermission(false);
content::MediaResponseCallback cb = callback_;
callback_.Reset();
cb.Run(content::MediaStreamDevices(),
result,
scoped_ptr<content::MediaStreamUI>());
}
int MediaStreamDevicesController::GetIconID() const {
if (HasVideo())
return IDR_INFOBAR_MEDIA_STREAM_CAMERA;
return IDR_INFOBAR_MEDIA_STREAM_MIC;
}
base::string16 MediaStreamDevicesController::GetMessageText() const {
int message_id = IDS_MEDIA_CAPTURE_AUDIO_AND_VIDEO;
if (!HasAudio())
message_id = IDS_MEDIA_CAPTURE_VIDEO_ONLY;
else if (!HasVideo())
message_id = IDS_MEDIA_CAPTURE_AUDIO_ONLY;
return l10n_util::GetStringFUTF16(
message_id, base::UTF8ToUTF16(GetSecurityOriginSpec()));
}
base::string16 MediaStreamDevicesController::GetMessageTextFragment() const {
int message_id = IDS_MEDIA_CAPTURE_AUDIO_AND_VIDEO_PERMISSION_FRAGMENT;
if (!HasAudio())
message_id = IDS_MEDIA_CAPTURE_VIDEO_ONLY_PERMISSION_FRAGMENT;
else if (!HasVideo())
message_id = IDS_MEDIA_CAPTURE_AUDIO_ONLY_PERMISSION_FRAGMENT;
return l10n_util::GetStringUTF16(message_id);
}
bool MediaStreamDevicesController::HasUserGesture() const {
return request_.user_gesture;
}
GURL MediaStreamDevicesController::GetRequestingHostname() const {
return request_.security_origin;
}
void MediaStreamDevicesController::PermissionGranted() {
GURL origin(GetSecurityOriginSpec());
if (origin.SchemeIsSecure()) {
UMA_HISTOGRAM_ENUMERATION("Media.DevicePermissionActions",
kAllowHttps, kPermissionActionsMax);
} else {
UMA_HISTOGRAM_ENUMERATION("Media.DevicePermissionActions",
kAllowHttp, kPermissionActionsMax);
}
Accept(true);
}
void MediaStreamDevicesController::PermissionDenied() {
UMA_HISTOGRAM_ENUMERATION("Media.DevicePermissionActions",
kDeny, kPermissionActionsMax);
Deny(true, content::MEDIA_DEVICE_PERMISSION_DENIED);
}
void MediaStreamDevicesController::Cancelled() {
UMA_HISTOGRAM_ENUMERATION("Media.DevicePermissionActions",
kCancel, kPermissionActionsMax);
Deny(true, content::MEDIA_DEVICE_PERMISSION_DISMISSED);
}
void MediaStreamDevicesController::RequestFinished() {
delete this;
}
MediaStreamDevicesController::DevicePolicy
MediaStreamDevicesController::GetDevicePolicy(
const char* policy_name,
const char* whitelist_policy_name) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
PrefService* prefs = profile_->GetPrefs();
if (IsInKioskMode()) {
const base::ListValue* list = prefs->GetList(whitelist_policy_name);
std::string value;
for (size_t i = 0; i < list->GetSize(); ++i) {
if (list->GetString(i, &value)) {
ContentSettingsPattern pattern =
ContentSettingsPattern::FromString(value);
if (pattern == ContentSettingsPattern::Wildcard()) {
DLOG(WARNING) << "Ignoring wildcard URL pattern: " << value;
continue;
}
DLOG_IF(ERROR, !pattern.IsValid()) << "Invalid URL pattern: " << value;
if (pattern.IsValid() && pattern.Matches(request_.security_origin))
return ALWAYS_ALLOW;
}
}
}
if (!prefs->GetBoolean(policy_name))
return ALWAYS_DENY;
return POLICY_NOT_SET;
}
bool MediaStreamDevicesController::IsRequestAllowedByDefault() const {
if (ShouldAlwaysAllowOrigin())
return true;
struct {
bool has_capability;
const char* policy_name;
const char* list_policy_name;
ContentSettingsType settings_type;
} device_checks[] = {
{ IsDeviceAudioCaptureRequestedAndAllowed(), prefs::kAudioCaptureAllowed,
prefs::kAudioCaptureAllowedUrls, CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC },
{ IsDeviceVideoCaptureRequestedAndAllowed(), prefs::kVideoCaptureAllowed,
prefs::kVideoCaptureAllowedUrls,
CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA },
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(device_checks); ++i) {
if (!device_checks[i].has_capability)
continue;
DevicePolicy policy = GetDevicePolicy(device_checks[i].policy_name,
device_checks[i].list_policy_name);
if (policy == ALWAYS_DENY)
return false;
if (policy == POLICY_NOT_SET) {
if (!IsSchemeSecure() &&
request_.request_type != content::MEDIA_OPEN_DEVICE) {
return false;
}
if (profile_->GetHostContentSettingsMap()->GetContentSetting(
request_.security_origin, request_.security_origin,
device_checks[i].settings_type, NO_RESOURCE_IDENTIFIER) !=
CONTENT_SETTING_ALLOW) {
return false;
}
}
}
return true;
}
int MediaStreamDevicesController::FilterBlockedByDefaultDevices() {
int requested_devices = 0;
if (IsDeviceAudioCaptureRequestedAndAllowed()) {
if (profile_->GetHostContentSettingsMap()->GetContentSetting(
request_.security_origin,
request_.security_origin,
CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_BLOCK) {
request_permissions_[content::MEDIA_DEVICE_AUDIO_CAPTURE].permission =
MEDIA_BLOCKED_BY_USER_SETTING;
} else {
++requested_devices;
}
}
if (IsDeviceVideoCaptureRequestedAndAllowed()) {
if (profile_->GetHostContentSettingsMap()->GetContentSetting(
request_.security_origin,
request_.security_origin,
CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_BLOCK) {
request_permissions_[content::MEDIA_DEVICE_VIDEO_CAPTURE].permission =
MEDIA_BLOCKED_BY_USER_SETTING;
} else {
++requested_devices;
}
}
return requested_devices;
}
bool MediaStreamDevicesController::IsDefaultMediaAccessBlocked() const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ContentSetting current_setting =
profile_->GetHostContentSettingsMap()->GetDefaultContentSetting(
CONTENT_SETTINGS_TYPE_MEDIASTREAM, NULL);
return (current_setting == CONTENT_SETTING_BLOCK);
}
bool MediaStreamDevicesController::IsSchemeSecure() const {
return request_.security_origin.SchemeIsSecure() ||
request_.security_origin.SchemeIs(extensions::kExtensionScheme) ||
CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableUserMediaSecurity);
}
bool MediaStreamDevicesController::ShouldAlwaysAllowOrigin() const {
return profile_->GetHostContentSettingsMap()->ShouldAllowAllContent(
request_.security_origin, request_.security_origin,
CONTENT_SETTINGS_TYPE_MEDIASTREAM);
}
void MediaStreamDevicesController::SetPermission(bool allowed) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ContentSettingsPattern primary_pattern =
ContentSettingsPattern::FromURLNoWildcard(request_.security_origin);
if (!primary_pattern.IsValid())
return;
ContentSetting content_setting = allowed ?
CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
if (request_permissions_.find(content::MEDIA_DEVICE_AUDIO_CAPTURE) !=
request_permissions_.end()) {
profile_->GetHostContentSettingsMap()->SetContentSetting(
primary_pattern,
ContentSettingsPattern::Wildcard(),
CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
std::string(),
content_setting);
}
if (request_permissions_.find(content::MEDIA_DEVICE_VIDEO_CAPTURE) !=
request_permissions_.end()) {
profile_->GetHostContentSettingsMap()->SetContentSetting(
primary_pattern,
ContentSettingsPattern::Wildcard(),
CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
std::string(),
content_setting);
}
}
void MediaStreamDevicesController::NotifyUIRequestAccepted() const {
if (!content_settings_)
return;
content_settings_->OnMediaStreamPermissionSet(request_.security_origin,
request_permissions_);
}
void MediaStreamDevicesController::NotifyUIRequestDenied() {
if (!content_settings_)
return;
if (IsDeviceAudioCaptureRequestedAndAllowed()) {
request_permissions_[content::MEDIA_DEVICE_AUDIO_CAPTURE].permission =
MEDIA_BLOCKED_BY_USER;
}
if (IsDeviceVideoCaptureRequestedAndAllowed()) {
request_permissions_[content::MEDIA_DEVICE_VIDEO_CAPTURE].permission =
MEDIA_BLOCKED_BY_USER;
}
content_settings_->OnMediaStreamPermissionSet(request_.security_origin,
request_permissions_);
}
bool MediaStreamDevicesController::IsDeviceAudioCaptureRequestedAndAllowed()
const {
MediaStreamTypeSettingsMap::const_iterator it =
request_permissions_.find(content::MEDIA_DEVICE_AUDIO_CAPTURE);
return (it != request_permissions_.end() &&
it->second.permission == MEDIA_ALLOWED);
}
bool MediaStreamDevicesController::IsDeviceVideoCaptureRequestedAndAllowed()
const {
MediaStreamTypeSettingsMap::const_iterator it =
request_permissions_.find(content::MEDIA_DEVICE_VIDEO_CAPTURE);
return (it != request_permissions_.end() &&
it->second.permission == MEDIA_ALLOWED);
}