Index: chrome/browser/media/media_capture_devices_dispatcher.cc |
diff --git a/chrome/browser/media/media_capture_devices_dispatcher.cc b/chrome/browser/media/media_capture_devices_dispatcher.cc |
index 79c93f5c1124def7b7aa119c90259682a0ae0532..b64a2c7ce6c9d7f0dbb63414b01feb1ceef63059 100644 |
--- a/chrome/browser/media/media_capture_devices_dispatcher.cc |
+++ b/chrome/browser/media/media_capture_devices_dispatcher.cc |
@@ -9,52 +9,38 @@ |
#include "base/metrics/field_trial.h" |
#include "base/prefs/pref_service.h" |
#include "base/prefs/scoped_user_pref_update.h" |
-#include "base/sha1.h" |
#include "base/strings/string_number_conversions.h" |
#include "base/strings/string_util.h" |
#include "base/strings/utf_string_conversions.h" |
#include "chrome/browser/media/desktop_streams_registry.h" |
+#include "chrome/browser/media/media_access_handler.h" |
#include "chrome/browser/media/media_stream_capture_indicator.h" |
-#include "chrome/browser/media/media_stream_device_permissions.h" |
-#include "chrome/browser/media/media_stream_infobar_delegate.h" |
-#include "chrome/browser/profiles/profile.h" |
+#include "chrome/browser/media/permission_bubble_media_access_handler.h" |
#include "chrome/browser/ui/browser.h" |
-#include "chrome/browser/ui/browser_finder.h" |
#include "chrome/browser/ui/browser_window.h" |
-#include "chrome/browser/ui/screen_capture_notification_ui.h" |
-#include "chrome/browser/ui/simple_message_box.h" |
-#include "chrome/browser/ui/website_settings/permission_bubble_manager.h" |
#include "chrome/common/chrome_switches.h" |
#include "chrome/common/chrome_version_info.h" |
#include "chrome/common/pref_names.h" |
-#include "chrome/grit/generated_resources.h" |
-#include "components/content_settings/core/browser/host_content_settings_map.h" |
#include "components/pref_registry/pref_registry_syncable.h" |
#include "content/public/browser/browser_thread.h" |
-#include "content/public/browser/desktop_media_id.h" |
#include "content/public/browser/media_capture_devices.h" |
-#include "content/public/browser/notification_service.h" |
#include "content/public/browser/notification_source.h" |
-#include "content/public/browser/notification_types.h" |
#include "content/public/browser/render_frame_host.h" |
#include "content/public/browser/render_process_host.h" |
#include "content/public/browser/web_contents.h" |
#include "content/public/common/media_stream_request.h" |
#include "extensions/common/constants.h" |
-#include "media/audio/audio_manager_base.h" |
#include "media/base/media_switches.h" |
#include "net/base/net_util.h" |
-#include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h" |
-#include "ui/base/l10n/l10n_util.h" |
#if defined(OS_CHROMEOS) |
#include "ash/shell.h" |
#endif // defined(OS_CHROMEOS) |
#if defined(ENABLE_EXTENSIONS) |
-#include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h" |
-#include "extensions/browser/app_window/app_window.h" |
-#include "extensions/browser/app_window/app_window_registry.h" |
+#include "chrome/browser/media/desktop_capture_access_handler.h" |
+#include "chrome/browser/media/extension_media_access_handler.h" |
+#include "chrome/browser/media/tab_capture_access_handler.h" |
#include "extensions/browser/extension_registry.h" |
#include "extensions/common/extension.h" |
#include "extensions/common/permissions/permissions_data.h" |
@@ -66,16 +52,6 @@ using content::MediaStreamDevices; |
namespace { |
-// A finch experiment to enable the permission bubble for media requests only. |
-bool MediaStreamPermissionBubbleExperimentEnabled() { |
- const std::string group = |
- base::FieldTrialList::FindFullName("MediaStreamPermissionBubble"); |
- if (group == "enabled") |
- return true; |
- |
- return false; |
-} |
- |
// Finds a device in |devices| that has |device_id|, or NULL if not found. |
const content::MediaStreamDevice* FindDeviceWithId( |
const content::MediaStreamDevices& devices, |
@@ -90,161 +66,13 @@ const content::MediaStreamDevice* FindDeviceWithId( |
} |
#if defined(ENABLE_EXTENSIONS) |
-// This is a short-term solution to grant camera and/or microphone access to |
-// extensions: |
-// 1. Virtual keyboard extension. |
-// 2. Flutter gesture recognition extension. |
-// 3. TODO(smus): Airbender experiment 1. |
-// 4. TODO(smus): Airbender experiment 2. |
-// 5. Hotwording component extension. |
-// 6. XKB input method component extension. |
-// 7. M17n/T13n/CJK input method component extension. |
-// Once http://crbug.com/292856 is fixed, remove this whitelist. |
-bool IsMediaRequestWhitelistedForExtension( |
- const extensions::Extension* extension) { |
- return extension->id() == "mppnpdlheglhdfmldimlhpnegondlapf" || |
- extension->id() == "jokbpnebhdcladagohdnfgjcpejggllo" || |
- extension->id() == "clffjmdilanldobdnedchkdbofoimcgb" || |
- extension->id() == "nnckehldicaciogcbchegobnafnjkcne" || |
- extension->id() == "nbpagnldghgfoolbancepceaanlmhfmd" || |
- extension->id() == "jkghodnilhceideoidjikpgommlajknk" || |
- extension->id() == "gjaehgfemfahhmlgpdfknkhdnemmolop"; |
-} |
- |
-bool IsBuiltInExtension(const GURL& origin) { |
- return |
- // Feedback Extension. |
- origin.spec() == "chrome-extension://gfdkimpbcpahaombhbimeihdjnejgicl/"; |
-} |
- |
-// Returns true of the security origin is associated with casting. |
-bool IsOriginForCasting(const GURL& origin) { |
- // Whitelisted tab casting extensions. |
- return |
- // Dev |
- origin.spec() == "chrome-extension://enhhojjnijigcajfphajepfemndkmdlo/" || |
- // Canary |
- origin.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" || |
- // Beta (internal) |
- origin.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" || |
- // Google Cast Beta |
- origin.spec() == "chrome-extension://dliochdbjfkdbacpmhlcpmleaejidimm/" || |
- // Google Cast Stable |
- origin.spec() == "chrome-extension://boadgeojelhgndaghljhdicfkmllpafd/" || |
- // http://crbug.com/457908 |
- origin.spec() == "chrome-extension://ekpaaapppgpmolpcldedioblbkmijaca/" || |
- origin.spec() == "chrome-extension://fjhoaacokmgbjemoflkofnenfaiekifl/"; |
-} |
- |
-bool IsExtensionWhitelistedForScreenCapture( |
- const extensions::Extension* extension) { |
-#if defined(OS_CHROMEOS) |
- std::string hash = base::SHA1HashString(extension->id()); |
- std::string hex_hash = base::HexEncode(hash.c_str(), hash.length()); |
- |
- // crbug.com/446688 |
- return hex_hash == "4F25792AF1AA7483936DE29C07806F203C7170A0" || |
- hex_hash == "BD8781D757D830FC2E85470A1B6E8A718B7EE0D9" || |
- hex_hash == "4AC2B6C63C6480D150DFDA13E4A5956EB1D0DDBB" || |
- hex_hash == "81986D4F846CEDDDB962643FA501D1780DD441BB"; |
-#else |
- return false; |
-#endif // defined(OS_CHROMEOS) |
-} |
-#endif // defined(ENABLE_EXTENSIONS) |
- |
-// Helper to get title of the calling application shown in the screen capture |
-// notification. |
-base::string16 GetApplicationTitle(content::WebContents* web_contents, |
- const extensions::Extension* extension) { |
- // Use extension name as title for extensions and host/origin for drive-by |
- // web. |
- std::string title; |
-#if defined(ENABLE_EXTENSIONS) |
- if (extension) { |
- title = extension->name(); |
- return base::UTF8ToUTF16(title); |
- } |
-#endif |
- GURL url = web_contents->GetURL(); |
- title = url.SchemeIsSecure() ? net::GetHostAndOptionalPort(url) |
- : url.GetOrigin().spec(); |
- return base::UTF8ToUTF16(title); |
-} |
- |
-// Helper to get list of media stream devices for desktop capture in |devices|. |
-// Registers to display notification if |display_notification| is true. |
-// Returns an instance of MediaStreamUI to be passed to content layer. |
-scoped_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture( |
- content::MediaStreamDevices* devices, |
- content::DesktopMediaID media_id, |
- bool capture_audio, |
- bool display_notification, |
- const base::string16& application_title, |
- const base::string16& registered_extension_name) { |
- DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- scoped_ptr<content::MediaStreamUI> ui; |
- |
- // Add selected desktop source to the list. |
- devices->push_back(content::MediaStreamDevice( |
- content::MEDIA_DESKTOP_VIDEO_CAPTURE, media_id.ToString(), "Screen")); |
- if (capture_audio) { |
- // Use the special loopback device ID for system audio capture. |
- devices->push_back(content::MediaStreamDevice( |
- content::MEDIA_DESKTOP_AUDIO_CAPTURE, |
- media::AudioManagerBase::kLoopbackInputDeviceId, "System Audio")); |
- } |
- |
- // If required, register to display the notification for stream capture. |
- if (display_notification) { |
- if (application_title == registered_extension_name) { |
- ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16( |
- IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT, |
- application_title)); |
- } else { |
- ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16( |
- IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT_DELEGATED, |
- registered_extension_name, |
- application_title)); |
- } |
- } |
- |
- return ui.Pass(); |
-} |
- |
-#if !defined(OS_ANDROID) |
-// Find browser or app window from a given |web_contents|. |
-gfx::NativeWindow FindParentWindowForWebContents( |
- content::WebContents* web_contents) { |
- Browser* browser = chrome::FindBrowserWithWebContents(web_contents); |
- if (browser && browser->window()) |
- return browser->window()->GetNativeWindow(); |
- |
- const extensions::AppWindowRegistry::AppWindowList& window_list = |
- extensions::AppWindowRegistry::Get( |
- web_contents->GetBrowserContext())->app_windows(); |
- for (extensions::AppWindowRegistry::AppWindowList::const_iterator iter = |
- window_list.begin(); |
- iter != window_list.end(); ++iter) { |
- if ((*iter)->web_contents() == web_contents) |
- return (*iter)->GetNativeWindow(); |
- } |
- |
- return NULL; |
+inline DesktopCaptureAccessHandler* ToDesktopCaptureAccessHandler( |
+ MediaAccessHandler* handler) { |
+ return static_cast<DesktopCaptureAccessHandler*>(handler); |
} |
#endif |
- |
} // namespace |
-MediaCaptureDevicesDispatcher::PendingAccessRequest::PendingAccessRequest( |
- const content::MediaStreamRequest& request, |
- const content::MediaResponseCallback& callback) |
- : request(request), |
- callback(callback) { |
-} |
- |
-MediaCaptureDevicesDispatcher::PendingAccessRequest::~PendingAccessRequest() {} |
- |
MediaCaptureDevicesDispatcher* MediaCaptureDevicesDispatcher::GetInstance() { |
return Singleton<MediaCaptureDevicesDispatcher>::get(); |
} |
@@ -252,14 +80,7 @@ MediaCaptureDevicesDispatcher* MediaCaptureDevicesDispatcher::GetInstance() { |
MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher() |
: is_device_enumeration_disabled_(false), |
media_stream_capture_indicator_(new MediaStreamCaptureIndicator()) { |
- // MediaCaptureDevicesDispatcher is a singleton. It should be created on |
- // UI thread. Otherwise, it will not receive |
- // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in |
- // possible use after free. |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- notifications_registrar_.Add( |
- this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, |
- content::NotificationService::AllSources()); |
#if defined(OS_MACOSX) |
// AVFoundation is used for video/audio device monitoring and video capture. |
@@ -269,6 +90,13 @@ MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher() |
switches::kEnableAVFoundation); |
} |
#endif |
+ |
+#if defined(ENABLE_EXTENSIONS) |
+ media_access_handlers_.push_back(new ExtensionMediaAccessHandler()); |
+ media_access_handlers_.push_back(new DesktopCaptureAccessHandler()); |
+ media_access_handlers_.push_back(new TabCaptureAccessHandler()); |
+#endif |
+ media_access_handlers_.push_back(new PermissionBubbleMediaAccessHandler()); |
} |
MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {} |
@@ -281,6 +109,24 @@ void MediaCaptureDevicesDispatcher::RegisterProfilePrefs( |
std::string()); |
} |
+bool MediaCaptureDevicesDispatcher::IsOriginForCasting(const GURL& origin) { |
+ // Whitelisted tab casting extensions. |
+ return |
+ // Dev |
+ origin.spec() == "chrome-extension://enhhojjnijigcajfphajepfemndkmdlo/" || |
+ // Canary |
+ origin.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" || |
+ // Beta (internal) |
+ origin.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" || |
+ // Google Cast Beta |
+ origin.spec() == "chrome-extension://dliochdbjfkdbacpmhlcpmleaejidimm/" || |
+ // Google Cast Stable |
+ origin.spec() == "chrome-extension://boadgeojelhgndaghljhdicfkmllpafd/" || |
+ // http://crbug.com/457908 |
+ origin.spec() == "chrome-extension://ekpaaapppgpmolpcldedioblbkmijaca/" || |
+ origin.spec() == "chrome-extension://fjhoaacokmgbjemoflkofnenfaiekifl/"; |
+} |
+ |
void MediaCaptureDevicesDispatcher::AddObserver(Observer* observer) { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
if (!observers_.HasObserver(observer)) |
@@ -310,18 +156,6 @@ MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() { |
return MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices(); |
} |
-void MediaCaptureDevicesDispatcher::Observe( |
- int type, |
- const content::NotificationSource& source, |
- const content::NotificationDetails& details) { |
- DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) { |
- content::WebContents* web_contents = |
- content::Source<content::WebContents>(source).ptr(); |
- pending_requests_.erase(web_contents); |
- } |
-} |
- |
void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequest( |
content::WebContents* web_contents, |
const content::MediaStreamRequest& request, |
@@ -329,28 +163,15 @@ void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequest( |
const extensions::Extension* extension) { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- if (request.video_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE || |
- request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE) { |
- ProcessDesktopCaptureAccessRequest( |
- web_contents, request, callback, extension); |
- } else if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE || |
- request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE) { |
- ProcessTabCaptureAccessRequest( |
- web_contents, request, callback, extension); |
- } else { |
-#if defined(ENABLE_EXTENSIONS) |
- bool is_whitelisted = |
- extension && (extension->is_platform_app() || |
- IsMediaRequestWhitelistedForExtension(extension)); |
- if (is_whitelisted) { |
- // For extensions access is approved based on extension permissions. |
- ProcessMediaAccessRequestFromPlatformAppOrExtension( |
- web_contents, request, callback, extension); |
+ for (MediaAccessHandler* handler : media_access_handlers_) { |
+ if (handler->SupportsStreamType(request.video_type, extension) || |
+ handler->SupportsStreamType(request.audio_type, extension)) { |
+ handler->HandleRequest(web_contents, request, callback, extension); |
return; |
} |
-#endif |
- ProcessRegularMediaAccessRequest(web_contents, request, callback); |
} |
+ callback.Run(content::MediaStreamDevices(), |
+ content::MEDIA_DEVICE_NOT_SUPPORTED, nullptr); |
} |
bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission( |
@@ -358,487 +179,23 @@ bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission( |
const GURL& security_origin, |
content::MediaStreamType type) { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- DCHECK(type == content::MEDIA_DEVICE_AUDIO_CAPTURE || |
- type == content::MEDIA_DEVICE_VIDEO_CAPTURE); |
- |
- Profile* profile = |
- Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
- |
- ContentSettingsType contentSettingsType = |
- type == content::MEDIA_DEVICE_AUDIO_CAPTURE |
- ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC |
- : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA; |
- |
- if (CheckAllowAllMediaStreamContentForOrigin( |
- profile, security_origin, contentSettingsType)) { |
- return true; |
- } |
- |
- const char* policy_name = type == content::MEDIA_DEVICE_AUDIO_CAPTURE |
- ? prefs::kAudioCaptureAllowed |
- : prefs::kVideoCaptureAllowed; |
- const char* list_policy_name = type == content::MEDIA_DEVICE_AUDIO_CAPTURE |
- ? prefs::kAudioCaptureAllowedUrls |
- : prefs::kVideoCaptureAllowedUrls; |
- if (GetDevicePolicy( |
- profile, security_origin, policy_name, list_policy_name) == |
- ALWAYS_ALLOW) { |
- return true; |
- } |
- |
- // There's no secondary URL for these content types, hence duplicating |
- // |security_origin|. |
- if (profile->GetHostContentSettingsMap()->GetContentSetting( |
- security_origin, |
- security_origin, |
- contentSettingsType, |
- content_settings::ResourceIdentifier()) == CONTENT_SETTING_ALLOW) { |
- return true; |
- } |
- |
- return false; |
+ return CheckMediaAccessPermission(web_contents, security_origin, type, |
+ nullptr); |
} |
-#if defined(ENABLE_EXTENSIONS) |
bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission( |
content::WebContents* web_contents, |
const GURL& security_origin, |
content::MediaStreamType type, |
const extensions::Extension* extension) { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- DCHECK(type == content::MEDIA_DEVICE_AUDIO_CAPTURE || |
- type == content::MEDIA_DEVICE_VIDEO_CAPTURE); |
- |
- if (extension->is_platform_app() || |
- IsMediaRequestWhitelistedForExtension(extension)) { |
- return extension->permissions_data()->HasAPIPermission( |
- type == content::MEDIA_DEVICE_AUDIO_CAPTURE |
- ? extensions::APIPermission::kAudioCapture |
- : extensions::APIPermission::kVideoCapture); |
- } |
- |
- return CheckMediaAccessPermission(web_contents, security_origin, type); |
-} |
-#endif |
- |
-void MediaCaptureDevicesDispatcher::ProcessDesktopCaptureAccessRequest( |
- content::WebContents* web_contents, |
- const content::MediaStreamRequest& request, |
- const content::MediaResponseCallback& callback, |
- const extensions::Extension* extension) { |
- content::MediaStreamDevices devices; |
- scoped_ptr<content::MediaStreamUI> ui; |
- |
- if (request.video_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE) { |
- callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass()); |
- return; |
- } |
- |
- // If the device id wasn't specified then this is a screen capture request |
- // (i.e. chooseDesktopMedia() API wasn't used to generate device id). |
- if (request.requested_video_device_id.empty()) { |
- ProcessScreenCaptureAccessRequest( |
- web_contents, request, callback, extension); |
- return; |
- } |
- |
- // The extension name that the stream is registered with. |
- std::string original_extension_name; |
- // Resolve DesktopMediaID for the specified device id. |
- content::DesktopMediaID media_id; |
- // TODO(miu): Replace "main RenderFrame" IDs with the request's actual |
- // RenderFrame IDs once the desktop capture extension API implementation is |
- // fixed. http://crbug.com/304341 |
- content::WebContents* const web_contents_for_stream = |
- content::WebContents::FromRenderFrameHost( |
- content::RenderFrameHost::FromID(request.render_process_id, |
- request.render_frame_id)); |
- content::RenderFrameHost* const main_frame = web_contents_for_stream ? |
- web_contents_for_stream->GetMainFrame() : NULL; |
- if (main_frame) { |
- media_id = GetDesktopStreamsRegistry()->RequestMediaForStreamId( |
- request.requested_video_device_id, |
- main_frame->GetProcess()->GetID(), |
- main_frame->GetRoutingID(), |
- request.security_origin, |
- &original_extension_name); |
- } |
- |
- // Received invalid device id. |
- if (media_id.type == content::DesktopMediaID::TYPE_NONE) { |
- callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass()); |
- return; |
- } |
- |
- bool loopback_audio_supported = false; |
-#if defined(USE_CRAS) || defined(OS_WIN) |
- // Currently loopback audio capture is supported only on Windows and ChromeOS. |
- loopback_audio_supported = true; |
-#endif |
- |
- // Audio is only supported for screen capture streams. |
- bool capture_audio = |
- (media_id.type == content::DesktopMediaID::TYPE_SCREEN && |
- request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE && |
- loopback_audio_supported); |
- |
- ui = GetDevicesForDesktopCapture( |
- &devices, media_id, capture_audio, true, |
- GetApplicationTitle(web_contents, extension), |
- base::UTF8ToUTF16(original_extension_name)); |
- |
- callback.Run(devices, content::MEDIA_DEVICE_OK, ui.Pass()); |
-} |
- |
-void MediaCaptureDevicesDispatcher::ProcessScreenCaptureAccessRequest( |
- content::WebContents* web_contents, |
- const content::MediaStreamRequest& request, |
- const content::MediaResponseCallback& callback, |
- const extensions::Extension* extension) { |
- content::MediaStreamDevices devices; |
- scoped_ptr<content::MediaStreamUI> ui; |
- |
- DCHECK_EQ(request.video_type, content::MEDIA_DESKTOP_VIDEO_CAPTURE); |
- |
- bool loopback_audio_supported = false; |
-#if defined(USE_CRAS) || defined(OS_WIN) |
- // Currently loopback audio capture is supported only on Windows and ChromeOS. |
- loopback_audio_supported = true; |
-#endif |
- |
- bool component_extension = false; |
-#if defined(ENABLE_EXTENSIONS) |
- component_extension = |
- extension && extension->location() == extensions::Manifest::COMPONENT; |
-#endif |
- |
- bool screen_capture_enabled = |
- base::CommandLine::ForCurrentProcess()->HasSwitch( |
- switches::kEnableUserMediaScreenCapturing); |
-#if defined(ENABLE_EXTENSIONS) |
- screen_capture_enabled |= |
- IsOriginForCasting(request.security_origin) || |
- IsExtensionWhitelistedForScreenCapture(extension) || |
- IsBuiltInExtension(request.security_origin); |
-#endif |
- |
- const bool origin_is_secure = |
- request.security_origin.SchemeIsSecure() || |
- request.security_origin.SchemeIs(extensions::kExtensionScheme) || |
- base::CommandLine::ForCurrentProcess()->HasSwitch( |
- switches::kAllowHttpScreenCapture); |
- |
- // If basic conditions (screen capturing is enabled and origin is secure) |
- // aren't fulfilled, we'll use "invalid state" as result. Otherwise, we set |
- // it after checking permission. |
- // TODO(grunell): It would be good to change this result for something else, |
- // probably a new one. |
- content::MediaStreamRequestResult result = |
- content::MEDIA_DEVICE_INVALID_STATE; |
- |
- // Approve request only when the following conditions are met: |
- // 1. Screen capturing is enabled via command line switch or white-listed for |
- // the given origin. |
- // 2. Request comes from a page with a secure origin or from an extension. |
- if (screen_capture_enabled && origin_is_secure) { |
- // Get title of the calling application prior to showing the message box. |
- // chrome::ShowMessageBox() starts a nested message loop which may allow |
- // |web_contents| to be destroyed on the UI thread before the message box |
- // is closed. See http://crbug.com/326690. |
- base::string16 application_title = |
- GetApplicationTitle(web_contents, extension); |
-#if !defined(OS_ANDROID) |
- gfx::NativeWindow parent_window = |
- FindParentWindowForWebContents(web_contents); |
-#else |
- gfx::NativeWindow parent_window = NULL; |
-#endif |
- web_contents = NULL; |
- |
- bool whitelisted_extension = false; |
-#if defined(ENABLE_EXTENSIONS) |
- whitelisted_extension = IsExtensionWhitelistedForScreenCapture( |
- extension); |
-#endif |
- |
- // For whitelisted or component extensions, bypass message box. |
- bool user_approved = false; |
- if (!whitelisted_extension && !component_extension) { |
- base::string16 application_name = |
- base::UTF8ToUTF16(request.security_origin.spec()); |
-#if defined(ENABLE_EXTENSIONS) |
- if (extension) |
- application_name = base::UTF8ToUTF16(extension->name()); |
-#endif |
- base::string16 confirmation_text = l10n_util::GetStringFUTF16( |
- request.audio_type == content::MEDIA_NO_SERVICE ? |
- IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT : |
- IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT, |
- application_name); |
- chrome::MessageBoxResult result = chrome::ShowMessageBox( |
- parent_window, |
- l10n_util::GetStringFUTF16( |
- IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name), |
- confirmation_text, |
- chrome::MESSAGE_BOX_TYPE_QUESTION); |
- user_approved = (result == chrome::MESSAGE_BOX_RESULT_YES); |
- } |
- |
- if (user_approved || component_extension || whitelisted_extension) { |
- content::DesktopMediaID screen_id; |
-#if defined(OS_CHROMEOS) |
- screen_id = content::DesktopMediaID::RegisterAuraWindow( |
- ash::Shell::GetInstance()->GetPrimaryRootWindow()); |
-#else // defined(OS_CHROMEOS) |
- screen_id = |
- content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, |
- webrtc::kFullDesktopScreenId); |
-#endif // !defined(OS_CHROMEOS) |
- |
- bool capture_audio = |
- (request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE && |
- loopback_audio_supported); |
- |
- // Unless we're being invoked from a component extension, register to |
- // display the notification for stream capture. |
- bool display_notification = !component_extension; |
- |
- ui = GetDevicesForDesktopCapture(&devices, screen_id, capture_audio, |
- display_notification, application_title, |
- application_title); |
- DCHECK(!devices.empty()); |
- } |
- |
- // The only case when devices can be empty is if the user has denied |
- // permission. |
- result = devices.empty() ? content::MEDIA_DEVICE_PERMISSION_DENIED |
- : content::MEDIA_DEVICE_OK; |
- } |
- |
- callback.Run(devices, result, ui.Pass()); |
-} |
- |
-void MediaCaptureDevicesDispatcher::ProcessTabCaptureAccessRequest( |
- content::WebContents* web_contents, |
- const content::MediaStreamRequest& request, |
- const content::MediaResponseCallback& callback, |
- const extensions::Extension* extension) { |
- content::MediaStreamDevices devices; |
- scoped_ptr<content::MediaStreamUI> ui; |
- |
-#if defined(ENABLE_EXTENSIONS) |
- Profile* profile = |
- Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
- extensions::TabCaptureRegistry* tab_capture_registry = |
- extensions::TabCaptureRegistry::Get(profile); |
- if (!tab_capture_registry) { |
- NOTREACHED(); |
- callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass()); |
- return; |
- } |
- const bool tab_capture_allowed = tab_capture_registry->VerifyRequest( |
- request.render_process_id, request.render_frame_id, extension->id()); |
- |
- if (request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE && |
- tab_capture_allowed && |
- extension->permissions_data()->HasAPIPermission( |
- extensions::APIPermission::kTabCapture)) { |
- devices.push_back(content::MediaStreamDevice( |
- content::MEDIA_TAB_AUDIO_CAPTURE, std::string(), std::string())); |
- } |
- |
- if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE && |
- tab_capture_allowed && |
- extension->permissions_data()->HasAPIPermission( |
- extensions::APIPermission::kTabCapture)) { |
- devices.push_back(content::MediaStreamDevice( |
- content::MEDIA_TAB_VIDEO_CAPTURE, std::string(), std::string())); |
- } |
- |
- if (!devices.empty()) { |
- ui = media_stream_capture_indicator_->RegisterMediaStream( |
- web_contents, devices); |
- } |
- callback.Run( |
- devices, |
- devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE : |
- content::MEDIA_DEVICE_OK, |
- ui.Pass()); |
-#else // defined(ENABLE_EXTENSIONS) |
- callback.Run(devices, content::MEDIA_DEVICE_TAB_CAPTURE_FAILURE, ui.Pass()); |
-#endif // defined(ENABLE_EXTENSIONS) |
-} |
- |
-#if defined(ENABLE_EXTENSIONS) |
-void MediaCaptureDevicesDispatcher:: |
- ProcessMediaAccessRequestFromPlatformAppOrExtension( |
- content::WebContents* web_contents, |
- const content::MediaStreamRequest& request, |
- const content::MediaResponseCallback& callback, |
- const extensions::Extension* extension) { |
- // TODO(vrk): This code is largely duplicated in |
- // MediaStreamDevicesController::Accept(). Move this code into a shared method |
- // between the two classes. |
- |
- Profile* profile = |
- Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
- |
- bool audio_allowed = |
- request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE && |
- extension->permissions_data()->HasAPIPermission( |
- extensions::APIPermission::kAudioCapture) && |
- GetDevicePolicy(profile, extension->url(), |
- prefs::kAudioCaptureAllowed, |
- prefs::kAudioCaptureAllowedUrls) != ALWAYS_DENY; |
- bool video_allowed = |
- request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE && |
- extension->permissions_data()->HasAPIPermission( |
- extensions::APIPermission::kVideoCapture) && |
- GetDevicePolicy(profile, extension->url(), |
- prefs::kVideoCaptureAllowed, |
- prefs::kVideoCaptureAllowedUrls) != ALWAYS_DENY; |
- |
- bool get_default_audio_device = audio_allowed; |
- bool get_default_video_device = video_allowed; |
- |
- content::MediaStreamDevices devices; |
- |
- // Set an initial error result. If neither audio or video is allowed, we'll |
- // never try to get any device below but will just create |ui| and return an |
- // empty list with "invalid state" result. If at least one is allowed, we'll |
- // try to get device(s), and if failure, we want to return "no hardware" |
- // result. |
- // TODO(grunell): The invalid state result should be changed to a new denied |
- // result + a dcheck to ensure at least one of audio or video types is |
- // capture. |
- content::MediaStreamRequestResult result = |
- (audio_allowed || video_allowed) ? content::MEDIA_DEVICE_NO_HARDWARE |
- : content::MEDIA_DEVICE_INVALID_STATE; |
- |
- // Get the exact audio or video device if an id is specified. |
- // We only set any error result here and before running the callback change |
- // it to OK if we have any device. |
- if (audio_allowed && !request.requested_audio_device_id.empty()) { |
- const content::MediaStreamDevice* audio_device = |
- GetRequestedAudioDevice(request.requested_audio_device_id); |
- if (audio_device) { |
- devices.push_back(*audio_device); |
- get_default_audio_device = false; |
+ for (MediaAccessHandler* handler : media_access_handlers_) { |
+ if (handler->SupportsStreamType(type, extension)) { |
+ return handler->CheckMediaAccessPermission(web_contents, security_origin, |
+ type, extension); |
} |
} |
- if (video_allowed && !request.requested_video_device_id.empty()) { |
- const content::MediaStreamDevice* video_device = |
- GetRequestedVideoDevice(request.requested_video_device_id); |
- if (video_device) { |
- devices.push_back(*video_device); |
- get_default_video_device = false; |
- } |
- } |
- |
- // If either or both audio and video devices were requested but not |
- // specified by id, get the default devices. |
- if (get_default_audio_device || get_default_video_device) { |
- GetDefaultDevicesForProfile(profile, |
- get_default_audio_device, |
- get_default_video_device, |
- &devices); |
- } |
- |
- scoped_ptr<content::MediaStreamUI> ui; |
- if (!devices.empty()) { |
- result = content::MEDIA_DEVICE_OK; |
- ui = media_stream_capture_indicator_->RegisterMediaStream( |
- web_contents, devices); |
- } |
- |
- callback.Run(devices, result, ui.Pass()); |
-} |
-#endif |
- |
-void MediaCaptureDevicesDispatcher::ProcessRegularMediaAccessRequest( |
- content::WebContents* web_contents, |
- const content::MediaStreamRequest& request, |
- const content::MediaResponseCallback& callback) { |
- DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- |
- RequestsQueue& queue = pending_requests_[web_contents]; |
- queue.push_back(PendingAccessRequest(request, callback)); |
- |
- // If this is the only request then show the infobar. |
- if (queue.size() == 1) |
- ProcessQueuedAccessRequest(web_contents); |
-} |
- |
-void MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest( |
- content::WebContents* web_contents) { |
- DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- |
- std::map<content::WebContents*, RequestsQueue>::iterator it = |
- pending_requests_.find(web_contents); |
- |
- if (it == pending_requests_.end() || it->second.empty()) { |
- // Don't do anything if the tab was closed. |
- return; |
- } |
- |
- DCHECK(!it->second.empty()); |
- |
- if (PermissionBubbleManager::Enabled() || |
- MediaStreamPermissionBubbleExperimentEnabled()) { |
- scoped_ptr<MediaStreamDevicesController> controller( |
- new MediaStreamDevicesController(web_contents, |
- it->second.front().request, |
- base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse, |
- base::Unretained(this), web_contents))); |
- if (controller->DismissInfoBarAndTakeActionOnSettings()) |
- return; |
- PermissionBubbleManager* bubble_manager = |
- PermissionBubbleManager::FromWebContents(web_contents); |
- if (bubble_manager) |
- bubble_manager->AddRequest(controller.release()); |
- return; |
- } |
- |
- // TODO(gbillock): delete this block and the MediaStreamInfoBarDelegate |
- // when we've transitioned to bubbles. (crbug/337458) |
- MediaStreamInfoBarDelegate::Create( |
- web_contents, it->second.front().request, |
- base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse, |
- base::Unretained(this), web_contents)); |
-} |
- |
-void MediaCaptureDevicesDispatcher::OnAccessRequestResponse( |
- content::WebContents* web_contents, |
- const content::MediaStreamDevices& devices, |
- content::MediaStreamRequestResult result, |
- scoped_ptr<content::MediaStreamUI> ui) { |
- DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- |
- std::map<content::WebContents*, RequestsQueue>::iterator it = |
- pending_requests_.find(web_contents); |
- if (it == pending_requests_.end()) { |
- // WebContents has been destroyed. Don't need to do anything. |
- return; |
- } |
- |
- RequestsQueue& queue(it->second); |
- if (queue.empty()) |
- return; |
- |
- content::MediaResponseCallback callback = queue.front().callback; |
- queue.pop_front(); |
- |
- if (!queue.empty()) { |
- // Post a task to process next queued request. It has to be done |
- // asynchronously to make sure that calling infobar is not destroyed until |
- // after this function returns. |
- BrowserThread::PostTask( |
- BrowserThread::UI, FROM_HERE, |
- base::Bind(&MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest, |
- base::Unretained(this), web_contents)); |
- } |
- |
- callback.Run(devices, result, ui.Pass()); |
+ return false; |
} |
void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile( |
@@ -990,47 +347,11 @@ void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread( |
const GURL& security_origin, |
content::MediaStreamType stream_type, |
content::MediaRequestState state) { |
- // Track desktop capture sessions. Tracking is necessary to avoid unbalanced |
- // session counts since not all requests will reach MEDIA_REQUEST_STATE_DONE, |
- // but they will all reach MEDIA_REQUEST_STATE_CLOSING. |
- if (stream_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE) { |
- if (state == content::MEDIA_REQUEST_STATE_DONE) { |
- DesktopCaptureSession session = { render_process_id, render_frame_id, |
- page_request_id }; |
- desktop_capture_sessions_.push_back(session); |
- } else if (state == content::MEDIA_REQUEST_STATE_CLOSING) { |
- for (DesktopCaptureSessions::iterator it = |
- desktop_capture_sessions_.begin(); |
- it != desktop_capture_sessions_.end(); |
- ++it) { |
- if (it->render_process_id == render_process_id && |
- it->render_frame_id == render_frame_id && |
- it->page_request_id == page_request_id) { |
- desktop_capture_sessions_.erase(it); |
- break; |
- } |
- } |
- } |
- } |
- |
- // Cancel the request. |
- if (state == content::MEDIA_REQUEST_STATE_CLOSING) { |
- bool found = false; |
- for (RequestsQueues::iterator rqs_it = pending_requests_.begin(); |
- rqs_it != pending_requests_.end(); ++rqs_it) { |
- RequestsQueue& queue = rqs_it->second; |
- for (RequestsQueue::iterator it = queue.begin(); |
- it != queue.end(); ++it) { |
- if (it->request.render_process_id == render_process_id && |
- it->request.render_frame_id == render_frame_id && |
- it->request.page_request_id == page_request_id) { |
- queue.erase(it); |
- found = true; |
- break; |
- } |
- } |
- if (found) |
- break; |
+ for (MediaAccessHandler* handler : media_access_handlers_) { |
+ if (handler->SupportsStreamType(stream_type, nullptr)) { |
+ handler->UpdateMediaRequestState(render_process_id, render_frame_id, |
+ page_request_id, stream_type, state); |
+ break; |
} |
} |
@@ -1062,7 +383,15 @@ void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread( |
bool MediaCaptureDevicesDispatcher::IsDesktopCaptureInProgress() { |
DCHECK_CURRENTLY_ON(BrowserThread::UI); |
- return desktop_capture_sessions_.size() > 0; |
+#if defined(ENABLE_EXTENSIONS) |
+ for (MediaAccessHandler* handler : media_access_handlers_) { |
+ if (handler->SupportsStreamType(content::MEDIA_DESKTOP_VIDEO_CAPTURE, |
+ NULL)) { |
+ return ToDesktopCaptureAccessHandler(handler)->IsCaptureInProgress(); |
+ } |
+ } |
+#endif |
+ return false; |
} |
void MediaCaptureDevicesDispatcher::SetTestAudioCaptureDevices( |