| 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(
|
|
|