Index: chrome/browser/media/media_stream_devices_controller.cc |
diff --git a/chrome/browser/media/media_stream_devices_controller.cc b/chrome/browser/media/media_stream_devices_controller.cc |
index 08ad71141e09351dc4cbee576a5fe5b481459875..93d614502a790f00a472e0a03c17ada4f834dc8e 100644 |
--- a/chrome/browser/media/media_stream_devices_controller.cc |
+++ b/chrome/browser/media/media_stream_devices_controller.cc |
@@ -23,6 +23,7 @@ |
#include "chrome/common/pref_names.h" |
#include "components/user_prefs/pref_registry_syncable.h" |
#include "content/public/browser/browser_thread.h" |
+#include "content/public/browser/navigation_entry.h" |
#include "content/public/common/media_stream_request.h" |
#include "extensions/common/constants.h" |
#include "grit/generated_resources.h" |
@@ -37,19 +38,127 @@ using content::BrowserThread; |
namespace { |
+// This prefix is combined with request security origins to store media access |
+// permissions that the user has granted a specific page navigation instance. |
+// The string value stored with the navigation instance will contain one or more |
+// kMediaPermissionXxx constants that indicates the permission(s) that the user |
+// has granted the page. |
+const char kMediaPermissionKeyPrefix[] = "media_permissions#"; |
+const base::char16 kMediaPermissionAudio = static_cast<base::char16>('a'); |
+const base::char16 kMediaPermissionVideo = static_cast<base::char16>('v'); |
+ |
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(); |
+ const content::MediaStreamDevices* audio_devices = |
+ request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE ? |
+ &MediaCaptureDevicesDispatcher::GetInstance() |
+ ->GetAudioCaptureDevices() : |
+ NULL; |
+ |
+ const content::MediaStreamDevices* video_devices = |
+ request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE ? |
+ &MediaCaptureDevicesDispatcher::GetInstance() |
+ ->GetVideoCaptureDevices() : |
+ NULL; |
+ |
+ // Check if we're being asked for audio and/or video and that either of those |
+ // lists is empty. If they are, we do not have devices available for the |
+ // request. |
+ // TODO(tommi): It's kind of strange to have this here since if we fail this |
+ // test, there'll be a UI shown that indicates to the user that access to |
+ // non-existing audio/video devices has been denied. The user won't have |
+ // any way to change that but there will be a UI shown which indicates that |
+ // access is blocked. |
+ if ((audio_devices != NULL && audio_devices->empty()) || |
+ (video_devices != NULL && video_devices->empty())) { |
+ return false; |
+ } |
+ |
+ // Note: we check requested_[audio|video]_device_id before dereferencing |
+ // [audio|video]_devices. If the requested device id is non-empty, then |
+ // the corresponding device list must not be NULL. |
+ |
+ if (!request.requested_audio_device_id.empty() && |
+ !audio_devices->FindById(request.requested_audio_device_id)) { |
+ return false; |
+ } |
+ |
+ if (!request.requested_video_device_id.empty() && |
+ !video_devices->FindById(request.requested_video_device_id)) { |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+base::string16 GetMediaPermissionsFromNavigationEntry( |
+ content::NavigationEntry* navigation_entry, |
+ const content::MediaStreamRequest& request) { |
+ const std::string key(kMediaPermissionKeyPrefix + |
+ request.security_origin.spec()); |
- return has_audio_device && has_video_device; |
+ base::string16 permissions; |
+ if (!navigation_entry->GetExtraData(key, &permissions)) { |
+ DCHECK(permissions.empty()); |
+ } |
+ |
+ return permissions; |
} |
+void SetMediaPermissionsForNavigationEntry( |
+ content::NavigationEntry* navigation_entry, |
+ const content::MediaStreamRequest& request, |
+ const base::string16& permissions) { |
+ const std::string key(kMediaPermissionKeyPrefix + |
+ request.security_origin.spec()); |
+ permissions.empty() ? |
+ navigation_entry->ClearExtraData(key) : |
+ navigation_entry->SetExtraData(key, permissions); |
+} |
+ |
+void SetMediaPermissionsForNavigationEntry( |
+ content::NavigationEntry* navigation_entry, |
+ const content::MediaStreamRequest& request, |
+ bool allow_audio, |
+ bool allow_video) { |
+ base::string16 permissions; |
+ if (allow_audio) |
+ permissions += kMediaPermissionAudio; |
+ if (allow_video) |
+ permissions += kMediaPermissionVideo; |
+ SetMediaPermissionsForNavigationEntry(navigation_entry, request, permissions); |
+} |
+ |
+bool IsRequestAllowedByNavigationEntry( |
+ content::NavigationEntry* navigation_entry, |
+ const content::MediaStreamRequest& request) { |
+ using content::MEDIA_NO_SERVICE; |
+ using content::MEDIA_DEVICE_AUDIO_CAPTURE; |
+ using content::MEDIA_DEVICE_VIDEO_CAPTURE; |
+ |
+ // If we aren't being asked for at least one of these two, fail right away. |
+ if (!navigation_entry || |
+ (request.audio_type != MEDIA_DEVICE_AUDIO_CAPTURE && |
+ request.video_type != MEDIA_DEVICE_VIDEO_CAPTURE)) { |
+ return false; |
+ } |
+ |
+ base::string16 permissions = |
+ GetMediaPermissionsFromNavigationEntry(navigation_entry, request); |
+ |
+ bool audio_requested_and_granted = |
+ request.audio_type == MEDIA_DEVICE_AUDIO_CAPTURE && |
+ permissions.find(kMediaPermissionAudio) != base::string16::npos; |
+ |
+ bool video_requested_and_granted = |
+ request.video_type == MEDIA_DEVICE_VIDEO_CAPTURE && |
+ permissions.find(kMediaPermissionVideo) != base::string16::npos; |
+ |
+ return |
+ (audio_requested_and_granted || request.audio_type == MEDIA_NO_SERVICE) && |
+ (video_requested_and_granted || request.video_type == MEDIA_NO_SERVICE); |
+} |
+ |
+ |
bool IsInKioskMode() { |
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode)) |
return true; |
@@ -200,21 +309,16 @@ bool MediaStreamDevicesController::DismissInfoBarAndTakeActionOnSettings() { |
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; |
- } |
+ // Check if the navigation entry has previously been granted access. |
+ // We do this after the IsDefaultMediaAccessBlocked check to handle the use |
+ // case where the user modifies the content settings to 'deny' after having |
+ // previously granted the page access and the permissions in the |
+ // NavigationEntry are out of date. |
+ content::NavigationEntry* navigation_entry = |
+ web_contents_->GetController().GetVisibleEntry(); |
+ if (IsRequestAllowedByNavigationEntry(navigation_entry, request_)) { |
+ Accept(false); |
+ return true; |
} |
// Show the infobar. |
@@ -305,6 +409,15 @@ void MediaStreamDevicesController::Accept(bool update_content_setting) { |
get_default_video_device, |
&devices); |
} |
+ |
+ // Tag this navigation entry with the granted permissions. |
+ // This avoids repeated prompts for requests accessed via http. |
+ content::NavigationEntry* navigation_entry = |
+ web_contents_->GetController().GetVisibleEntry(); |
+ if (navigation_entry) { |
+ SetMediaPermissionsForNavigationEntry( |
+ navigation_entry, request_, audio_allowed, video_allowed); |
+ } |
break; |
} |
case content::MEDIA_DEVICE_ACCESS: { |
@@ -354,6 +467,14 @@ void MediaStreamDevicesController::Deny( |
DLOG(WARNING) << "MediaStreamDevicesController::Deny: " << result; |
NotifyUIRequestDenied(); |
+ // Clear previously allowed permissions from the navigation entry if any. |
+ content::NavigationEntry* navigation_entry = |
+ web_contents_->GetController().GetVisibleEntry(); |
+ if (navigation_entry) { |
+ SetMediaPermissionsForNavigationEntry( |
+ navigation_entry, request_, false, false); |
+ } |
+ |
if (update_content_setting) { |
CHECK_EQ(content::MEDIA_DEVICE_PERMISSION_DENIED, result); |
SetPermission(false); |
@@ -591,7 +712,7 @@ void MediaStreamDevicesController::SetPermission(bool allowed) const { |
CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK; |
if (request_permissions_.find(content::MEDIA_DEVICE_AUDIO_CAPTURE) != |
request_permissions_.end()) { |
- profile_->GetHostContentSettingsMap()->SetContentSetting( |
+ profile_->GetHostContentSettingsMap()->SetContentSetting( |
primary_pattern, |
ContentSettingsPattern::Wildcard(), |
CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC, |