#include "base/command_line.h"
#include "base/logging.h"
+#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/extensions/api/tab_capture/tab_capture_registry.h"
-#include "chrome/browser/media/audio_stream_indicator.h"
#include "chrome/browser/media/desktop_streams_registry.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/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/extensions/extension.h"
+#include "chrome/common/chrome_version_info.h"
#include "chrome/common/pref_names.h"
-#include "components/user_prefs/pref_registry_syncable.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/content_settings/core/browser/content_settings_provider.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/media_devices_monitor.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/desktop_media_id.h"
#include "content/public/common/media_stream_request.h"
#include "extensions/common/constants.h"
-#include "grit/generated_resources.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 "chrome/browser/extensions/extension_service.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/permissions/permissions_data.h"
+#endif
+
using content::BrowserThread;
+using content::MediaCaptureDevices;
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,
}
}
return NULL;
-};
+}
-// This is a short-term solution to grant microphone access to the
-// virtual keyboard extension and the Google Voice Search Hotword
-// extension for voice input. Once http://crbug.com/292856 is fixed,
-// remove this whitelist.
+#if defined(ENABLE_EXTENSIONS)
+// This is a short-term solution to grant camera and/or microphone access to
+// extensions:
+// 1. Virtual keyboard extension.
+// 2. Google Voice Search Hotword extension.
+// 3. Flutter gesture recognition extension.
+// 4. TODO(smus): Airbender experiment 1.
+// 5. TODO(smus): Airbender experiment 2.
+// 6. Hotwording component extension.
+// Once http://crbug.com/292856 is fixed, remove this whitelist.
bool IsMediaRequestWhitelistedForExtension(
const extensions::Extension* extension) {
return extension->id() == "mppnpdlheglhdfmldimlhpnegondlapf" ||
- extension->id() == "bepbmhgboaologfdajaanbcjmnhjmhfn";
-}
-
-// This is a short-term solution to allow testing of the the Screen Capture API
-// with Google Hangouts in M27.
-// TODO(sergeyu): Remove this whitelist as soon as possible.
-bool IsOriginWhitelistedForScreenCapture(const GURL& origin) {
-#if defined(OFFICIAL_BUILD)
- if (// Google Hangouts.
- (origin.SchemeIs("https") &&
- EndsWith(origin.spec(), ".talkgadget.google.com/", true)) ||
- origin.spec() == "https://plus.google.com/" ||
- origin.spec() == "chrome-extension://pkedcjkdefgpdelpbcmbmeomcjbeemfm/" ||
- origin.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" ||
- origin.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" ||
- origin.spec() == "chrome-extension://gfdkimpbcpahaombhbimeihdjnejgicl/") {
- return true;
- }
- // Check against hashed origins.
- const std::string origin_hash = base::SHA1HashString(origin.spec());
- DCHECK_EQ(origin_hash.length(), base::kSHA1Length);
- const std::string hexencoded_origin_hash =
- base::HexEncode(origin_hash.data(), origin_hash.length());
+ extension->id() == "bepbmhgboaologfdajaanbcjmnhjmhfn" ||
+ extension->id() == "jokbpnebhdcladagohdnfgjcpejggllo" ||
+ extension->id() == "clffjmdilanldobdnedchkdbofoimcgb" ||
+ extension->id() == "nnckehldicaciogcbchegobnafnjkcne" ||
+ extension->id() == "nbpagnldghgfoolbancepceaanlmhfmd";
+}
+
+bool IsBuiltInExtension(const GURL& origin) {
return
- hexencoded_origin_hash == "3C2705BC432E7C51CA8553FDC5BEE873FF2468EE" ||
- hexencoded_origin_hash == "50F02B8A668CAB274527D58356F07C2143080FCC";
-#else
- return false;
-#endif
+ // 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/";
+}
+#endif // defined(ENABLE_EXTENSIONS)
+
// Helper to get title of the calling application shown in the screen capture
// notification.
-string16 GetApplicationTitle(content::WebContents* web_contents,
- const extensions::Extension* extension) {
- // Use extension name as title for extensions and origin for drive-by web.
+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();
- } else {
- title = web_contents->GetURL().GetOrigin().spec();
+ return base::UTF8ToUTF16(title);
}
- return 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(BrowserThread::CurrentlyOn(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_LOOPBACK_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;
+}
+#endif
+
+#if defined(ENABLE_EXTENSIONS)
+const extensions::Extension* GetExtensionForOrigin(
+ Profile* profile,
+ const GURL& security_origin) {
+ if (!security_origin.SchemeIs(extensions::kExtensionScheme))
+ return NULL;
+
+ ExtensionService* extensions_service =
+ extensions::ExtensionSystem::Get(profile)->extension_service();
+ const extensions::Extension* extension =
+ extensions_service->extensions()->GetByID(security_origin.host());
+ DCHECK(extension);
+ return extension;
+}
+#endif
+
} // namespace
MediaCaptureDevicesDispatcher::PendingAccessRequest::PendingAccessRequest(
}
MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher()
- : devices_enumerated_(false),
- is_device_enumeration_disabled_(false),
- media_stream_capture_indicator_(new MediaStreamCaptureIndicator()),
- audio_stream_indicator_(new AudioStreamIndicator()) {
+ : 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
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.
+ if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kForceQTKit)) {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableAVFoundation);
+ }
+#endif
}
MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {}
const MediaStreamDevices&
MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- if (!is_device_enumeration_disabled_ && !devices_enumerated_) {
- content::EnsureMonitorCaptureDevices();
- devices_enumerated_ = true;
- }
- return audio_devices_;
+ if (is_device_enumeration_disabled_ || !test_audio_devices_.empty())
+ return test_audio_devices_;
+
+ return MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices();
}
const MediaStreamDevices&
MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- if (!is_device_enumeration_disabled_ && !devices_enumerated_) {
- content::EnsureMonitorCaptureDevices();
- devices_enumerated_ = true;
- }
- return video_devices_;
+ if (is_device_enumeration_disabled_ || !test_video_devices_.empty())
+ return test_video_devices_;
+
+ return MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices();
}
void MediaCaptureDevicesDispatcher::Observe(
request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE) {
ProcessTabCaptureAccessRequest(
web_contents, request, callback, extension);
- } else if (extension && (extension->is_platform_app() ||
- IsMediaRequestWhitelistedForExtension(extension))) {
- // For extensions access is approved based on extension permissions.
- ProcessMediaAccessRequestFromPlatformAppOrExtension(
- 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);
+ return;
+ }
+#endif
ProcessRegularMediaAccessRequest(web_contents, request, callback);
}
}
+bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
+ content::BrowserContext* browser_context,
+ const GURL& security_origin,
+ content::MediaStreamType type) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(type == content::MEDIA_DEVICE_AUDIO_CAPTURE ||
+ type == content::MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ Profile* profile = Profile::FromBrowserContext(browser_context);
+#if defined(ENABLE_EXTENSIONS)
+ const extensions::Extension* extension =
+ GetExtensionForOrigin(profile, security_origin);
+
+ if (extension && (extension->is_platform_app() ||
+ IsMediaRequestWhitelistedForExtension(extension))) {
+ return extension->permissions_data()->HasAPIPermission(
+ type == content::MEDIA_DEVICE_AUDIO_CAPTURE
+ ? extensions::APIPermission::kAudioCapture
+ : extensions::APIPermission::kVideoCapture);
+ }
+#endif
+
+ if (CheckAllowAllMediaStreamContentForOrigin(profile, security_origin))
+ 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,
+ type == content::MEDIA_DEVICE_AUDIO_CAPTURE
+ ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC
+ : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
+ NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_ALLOW) {
+ return true;
+ }
+
+ return false;
+}
+
+bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
+ content::WebContents* web_contents,
+ const GURL& security_origin,
+ content::MediaStreamType type) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(type == content::MEDIA_DEVICE_AUDIO_CAPTURE ||
+ type == content::MEDIA_DEVICE_VIDEO_CAPTURE);
+
+ Profile* profile =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+
+ if (CheckAllowAllMediaStreamContentForOrigin(profile, security_origin))
+ 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,
+ type == content::MEDIA_DEVICE_AUDIO_CAPTURE
+ ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC
+ : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
+ NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_ALLOW) {
+ return true;
+ }
+
+ return false;
+}
+
+#if defined(ENABLE_EXTENSIONS)
+bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
+ content::WebContents* web_contents,
+ const GURL& security_origin,
+ content::MediaStreamType type,
+ const extensions::Extension* extension) {
+ DCHECK(BrowserThread::CurrentlyOn(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,
scoped_ptr<content::MediaStreamUI> ui;
if (request.video_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE) {
- callback.Run(devices, ui.Pass());
+ callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass());
return;
}
- // First check if Desktop Capture API (i.e.
- // chrome.desktopCapture.chooseDesktopMedia()) was used to generate device Id.
- content::DesktopMediaID media_id =
- GetDesktopStreamsRegistry()->RequestMediaForStreamId(
- request.requested_video_device_id, request.render_process_id,
- request.render_view_id, request.security_origin);
-
- // If the id wasn't generated using Desktop Capture API then process it as a
- // screen capture request.
- if (media_id.type == content::DesktopMediaID::TYPE_NONE) {
+ // 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;
}
- // Add selected desktop source to the list.
- devices.push_back(content::MediaStreamDevice(
- content::MEDIA_DESKTOP_VIDEO_CAPTURE, media_id.ToString(),
- std::string()));
+ // 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)
#endif
// Audio is only supported for screen capture streams.
- if (media_id.type == content::DesktopMediaID::TYPE_SCREEN &&
- request.audio_type == content::MEDIA_LOOPBACK_AUDIO_CAPTURE &&
- loopback_audio_supported) {
- devices.push_back(content::MediaStreamDevice(
- content::MEDIA_LOOPBACK_AUDIO_CAPTURE,
- media::AudioManagerBase::kLoopbackInputDeviceId, "System Audio"));
- }
+ bool capture_audio =
+ (media_id.type == content::DesktopMediaID::TYPE_SCREEN &&
+ request.audio_type == content::MEDIA_LOOPBACK_AUDIO_CAPTURE &&
+ loopback_audio_supported);
- ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
- IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT,
- GetApplicationTitle(web_contents, extension)));
+ ui = GetDevicesForDesktopCapture(
+ &devices, media_id, capture_audio, true,
+ GetApplicationTitle(web_contents, extension),
+ base::UTF8ToUTF16(original_extension_name));
- callback.Run(devices, ui.Pass());
+ callback.Run(devices, content::MEDIA_DEVICE_OK, ui.Pass());
}
void MediaCaptureDevicesDispatcher::ProcessScreenCaptureAccessRequest(
DCHECK_EQ(request.video_type, content::MEDIA_DESKTOP_VIDEO_CAPTURE);
- content::DesktopMediaID media_id =
- content::DesktopMediaID::Parse(request.requested_video_device_id);
- if (media_id.is_null()) {
- LOG(ERROR) << "Invalid desktop media ID: "
- << request.requested_video_device_id;
- callback.Run(devices, ui.Pass());
- return;
- }
-
- // Only screen capture can be requested without using desktop media picker.
- if (media_id.type != content::DesktopMediaID::TYPE_SCREEN) {
- LOG(ERROR) << "Unsupported desktop media ID: "
- << request.requested_video_device_id;
- callback.Run(devices, 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
- const bool component_extension =
+ bool component_extension = false;
+#if defined(ENABLE_EXTENSIONS)
+ component_extension =
extension && extension->location() == extensions::Manifest::COMPONENT;
+#endif
- const bool screen_capture_enabled =
- CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kEnableUserMediaScreenCapturing) ||
- IsOriginWhitelistedForScreenCapture(request.security_origin);
+ bool screen_capture_enabled =
+ base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableUserMediaScreenCapturing);
+#if defined(ENABLE_EXTENSIONS)
+ screen_capture_enabled |=
+ IsOriginForCasting(request.security_origin) ||
+ IsBuiltInExtension(request.security_origin);
+#endif
const bool origin_is_secure =
request.security_origin.SchemeIsSecure() ||
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;
+
// For component extensions, bypass message box.
bool user_approved = false;
if (!component_extension) {
- string16 application_name = UTF8ToUTF16(
- extension ? extension->name() : request.security_origin.spec());
- string16 confirmation_text = l10n_util::GetStringFUTF16(
+ 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(
- NULL,
+ parent_window,
l10n_util::GetStringFUTF16(
IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name),
confirmation_text,
}
if (user_approved || component_extension) {
- devices.push_back(content::MediaStreamDevice(
- content::MEDIA_DESKTOP_VIDEO_CAPTURE, media_id.ToString(), "Screen"));
- if (request.audio_type == content::MEDIA_LOOPBACK_AUDIO_CAPTURE &&
- loopback_audio_supported) {
- // Use the special loopback device ID for system audio capture.
- devices.push_back(content::MediaStreamDevice(
- content::MEDIA_LOOPBACK_AUDIO_CAPTURE,
- media::AudioManagerBase::kLoopbackInputDeviceId, "System Audio"));
- }
+ 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_LOOPBACK_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());
}
- }
- // Unless we're being invoked from a component extension, register to display
- // the notification for stream capture.
- if (!devices.empty() && !component_extension) {
- ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
- IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT,
- GetApplicationTitle(web_contents, extension)));
+ // 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, ui.Pass());
+ callback.Run(devices, result, ui.Pass());
}
void MediaCaptureDevicesDispatcher::ProcessTabCaptureAccessRequest(
content::MediaStreamDevices devices;
scoped_ptr<content::MediaStreamUI> ui;
-#if defined(OS_ANDROID)
- // Tab capture is not supported on Android.
- callback.Run(devices, ui.Pass());
-#else // defined(OS_ANDROID)
+#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, ui.Pass());
+ callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass());
return;
}
- bool tab_capture_allowed =
- tab_capture_registry->VerifyRequest(request.render_process_id,
- request.render_view_id);
+ 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->HasAPIPermission(extensions::APIPermission::kTabCapture)) {
+ 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->HasAPIPermission(extensions::APIPermission::kTabCapture)) {
+ extension->permissions_data()->HasAPIPermission(
+ extensions::APIPermission::kTabCapture)) {
devices.push_back(content::MediaStreamDevice(
content::MEDIA_TAB_VIDEO_CAPTURE, std::string(), std::string()));
}
ui = media_stream_capture_indicator_->RegisterMediaStream(
web_contents, devices);
}
- callback.Run(devices, ui.Pass());
-#endif // !defined(OS_ANDROID)
+ 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) {
- content::MediaStreamDevices devices;
+ // 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());
- if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE &&
- extension->HasAPIPermission(extensions::APIPermission::kAudioCapture)) {
- GetDefaultDevicesForProfile(profile, true, false, &devices);
+ 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;
+ }
+ }
+ 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 (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE &&
- extension->HasAPIPermission(extensions::APIPermission::kVideoCapture)) {
- GetDefaultDevicesForProfile(profile, false, true, &devices);
+ // 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, ui.Pass());
+
+ callback.Run(devices, result, ui.Pass());
}
+#endif
void MediaCaptureDevicesDispatcher::ProcessRegularMediaAccessRequest(
content::WebContents* web_contents,
pending_requests_.find(web_contents);
if (it == pending_requests_.end() || it->second.empty()) {
- // Don't do anything if the tab was was closed.
+ // 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,
void MediaCaptureDevicesDispatcher::OnAccessRequestResponse(
content::WebContents* web_contents,
const content::MediaStreamDevices& devices,
+ content::MediaStreamRequestResult result,
scoped_ptr<content::MediaStreamUI> ui) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::Unretained(this), web_contents));
}
- callback.Run(devices, ui.Pass());
+ callback.Run(devices, result, ui.Pass());
}
void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile(
return media_stream_capture_indicator_;
}
-scoped_refptr<AudioStreamIndicator>
-MediaCaptureDevicesDispatcher::GetAudioStreamIndicator() {
- return audio_stream_indicator_;
-}
-
DesktopStreamsRegistry*
MediaCaptureDevicesDispatcher::GetDesktopStreamsRegistry() {
if (!desktop_streams_registry_)
return desktop_streams_registry_.get();
}
-void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged(
- const content::MediaStreamDevices& devices) {
+void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
- base::Bind(&MediaCaptureDevicesDispatcher::UpdateAudioDevicesOnUIThread,
- base::Unretained(this), devices));
+ base::Bind(
+ &MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread,
+ base::Unretained(this)));
}
-void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged(
- const content::MediaStreamDevices& devices) {
+void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
- base::Bind(&MediaCaptureDevicesDispatcher::UpdateVideoDevicesOnUIThread,
- base::Unretained(this), devices));
+ base::Bind(
+ &MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread,
+ base::Unretained(this)));
}
void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged(
int render_process_id,
- int render_view_id,
+ int render_frame_id,
int page_request_id,
- const content::MediaStreamDevice& device,
+ const GURL& security_origin,
+ content::MediaStreamType stream_type,
content::MediaRequestState state) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(
&MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread,
- base::Unretained(this), render_process_id, render_view_id,
- page_request_id, device, state));
-}
-
-void MediaCaptureDevicesDispatcher::OnAudioStreamPlayingChanged(
- int render_process_id, int render_view_id, int stream_id,
- bool is_playing, float power_dbfs, bool clipped) {
- audio_stream_indicator_->UpdateWebContentsStatus(
- render_process_id, render_view_id, stream_id,
- is_playing, power_dbfs, clipped);
+ base::Unretained(this), render_process_id, render_frame_id,
+ page_request_id, security_origin, stream_type, state));
}
void MediaCaptureDevicesDispatcher::OnCreatingAudioStream(
int render_process_id,
- int render_view_id) {
+ int render_frame_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(
&MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread,
- base::Unretained(this), render_process_id, render_view_id));
+ base::Unretained(this), render_process_id, render_frame_id));
}
-void MediaCaptureDevicesDispatcher::UpdateAudioDevicesOnUIThread(
- const content::MediaStreamDevices& devices) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- devices_enumerated_ = true;
- audio_devices_ = devices;
+void MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread() {
+ MediaStreamDevices devices = GetAudioCaptureDevices();
FOR_EACH_OBSERVER(Observer, observers_,
- OnUpdateAudioDevices(audio_devices_));
+ OnUpdateAudioDevices(devices));
}
-void MediaCaptureDevicesDispatcher::UpdateVideoDevicesOnUIThread(
- const content::MediaStreamDevices& devices) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- devices_enumerated_ = true;
- video_devices_ = devices;
+void MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread() {
+ MediaStreamDevices devices = GetVideoCaptureDevices();
FOR_EACH_OBSERVER(Observer, observers_,
- OnUpdateVideoDevices(video_devices_));
+ OnUpdateVideoDevices(devices));
}
void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread(
int render_process_id,
- int render_view_id,
+ int render_frame_id,
int page_request_id,
- const content::MediaStreamDevice& device,
+ 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 (RequestsQueue::iterator it = queue.begin();
it != queue.end(); ++it) {
if (it->request.render_process_id == render_process_id &&
- it->request.render_view_id == render_view_id &&
+ it->request.render_frame_id == render_frame_id &&
it->request.page_request_id == page_request_id) {
queue.erase(it);
found = true;
}
}
+#if defined(OS_CHROMEOS)
+ if (IsOriginForCasting(security_origin) && IsVideoMediaType(stream_type)) {
+ // Notify ash that casting state has changed.
+ if (state == content::MEDIA_REQUEST_STATE_DONE) {
+ ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(true);
+ } else if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
+ ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(false);
+ }
+ }
+#endif
+
FOR_EACH_OBSERVER(Observer, observers_,
OnRequestUpdate(render_process_id,
- render_view_id,
- device,
+ render_frame_id,
+ stream_type,
state));
}
void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread(
int render_process_id,
- int render_view_id) {
+ int render_frame_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
FOR_EACH_OBSERVER(Observer, observers_,
- OnCreatingAudioStream(render_process_id, render_view_id));
+ OnCreatingAudioStream(render_process_id, render_frame_id));
+}
+
+bool MediaCaptureDevicesDispatcher::IsDesktopCaptureInProgress() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ return desktop_capture_sessions_.size() > 0;
+}
+
+void MediaCaptureDevicesDispatcher::SetTestAudioCaptureDevices(
+ const MediaStreamDevices& devices) {
+ test_audio_devices_ = devices;
+}
+
+void MediaCaptureDevicesDispatcher::SetTestVideoCaptureDevices(
+ const MediaStreamDevices& devices) {
+ test_video_devices_ = devices;
}