1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/media/media_capture_devices_dispatcher.h"
7 #include "base/command_line.h"
8 #include "base/logging.h"
9 #include "base/metrics/field_trial.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/prefs/scoped_user_pref_update.h"
12 #include "base/sha1.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "chrome/browser/media/desktop_streams_registry.h"
17 #include "chrome/browser/media/media_stream_capture_indicator.h"
18 #include "chrome/browser/media/media_stream_device_permissions.h"
19 #include "chrome/browser/media/media_stream_infobar_delegate.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/ui/browser.h"
22 #include "chrome/browser/ui/browser_finder.h"
23 #include "chrome/browser/ui/browser_window.h"
24 #include "chrome/browser/ui/screen_capture_notification_ui.h"
25 #include "chrome/browser/ui/simple_message_box.h"
26 #include "chrome/browser/ui/website_settings/permission_bubble_manager.h"
27 #include "chrome/common/chrome_switches.h"
28 #include "chrome/common/chrome_version_info.h"
29 #include "chrome/common/pref_names.h"
30 #include "chrome/grit/generated_resources.h"
31 #include "components/content_settings/core/browser/host_content_settings_map.h"
32 #include "components/pref_registry/pref_registry_syncable.h"
33 #include "content/public/browser/browser_thread.h"
34 #include "content/public/browser/desktop_media_id.h"
35 #include "content/public/browser/media_capture_devices.h"
36 #include "content/public/browser/notification_service.h"
37 #include "content/public/browser/notification_source.h"
38 #include "content/public/browser/notification_types.h"
39 #include "content/public/browser/render_frame_host.h"
40 #include "content/public/browser/render_process_host.h"
41 #include "content/public/browser/web_contents.h"
42 #include "content/public/common/media_stream_request.h"
43 #include "extensions/common/constants.h"
44 #include "media/audio/audio_manager_base.h"
45 #include "media/base/media_switches.h"
46 #include "net/base/net_util.h"
47 #include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
48 #include "ui/base/l10n/l10n_util.h"
50 #if defined(OS_CHROMEOS)
51 #include "ash/shell.h"
52 #endif // defined(OS_CHROMEOS)
54 #if defined(ENABLE_EXTENSIONS)
55 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
56 #include "extensions/browser/app_window/app_window.h"
57 #include "extensions/browser/app_window/app_window_registry.h"
58 #include "extensions/browser/extension_registry.h"
59 #include "extensions/common/extension.h"
60 #include "extensions/common/permissions/permissions_data.h"
63 using content::BrowserThread
;
64 using content::MediaCaptureDevices
;
65 using content::MediaStreamDevices
;
69 // A finch experiment to enable the permission bubble for media requests only.
70 bool MediaStreamPermissionBubbleExperimentEnabled() {
71 const std::string group
=
72 base::FieldTrialList::FindFullName("MediaStreamPermissionBubble");
73 if (group
== "enabled")
79 // Finds a device in |devices| that has |device_id|, or NULL if not found.
80 const content::MediaStreamDevice
* FindDeviceWithId(
81 const content::MediaStreamDevices
& devices
,
82 const std::string
& device_id
) {
83 content::MediaStreamDevices::const_iterator iter
= devices
.begin();
84 for (; iter
!= devices
.end(); ++iter
) {
85 if (iter
->id
== device_id
) {
92 #if defined(ENABLE_EXTENSIONS)
93 // This is a short-term solution to grant camera and/or microphone access to
95 // 1. Virtual keyboard extension.
96 // 2. Flutter gesture recognition extension.
97 // 3. TODO(smus): Airbender experiment 1.
98 // 4. TODO(smus): Airbender experiment 2.
99 // 5. Hotwording component extension.
100 // 6. XKB input method component extension.
101 // 7. M17n/T13n/CJK input method component extension.
102 // Once http://crbug.com/292856 is fixed, remove this whitelist.
103 bool IsMediaRequestWhitelistedForExtension(
104 const extensions::Extension
* extension
) {
105 return extension
->id() == "mppnpdlheglhdfmldimlhpnegondlapf" ||
106 extension
->id() == "jokbpnebhdcladagohdnfgjcpejggllo" ||
107 extension
->id() == "clffjmdilanldobdnedchkdbofoimcgb" ||
108 extension
->id() == "nnckehldicaciogcbchegobnafnjkcne" ||
109 extension
->id() == "nbpagnldghgfoolbancepceaanlmhfmd" ||
110 extension
->id() == "jkghodnilhceideoidjikpgommlajknk" ||
111 extension
->id() == "gjaehgfemfahhmlgpdfknkhdnemmolop";
114 bool IsBuiltInExtension(const GURL
& origin
) {
116 // Feedback Extension.
117 origin
.spec() == "chrome-extension://gfdkimpbcpahaombhbimeihdjnejgicl/";
120 // Returns true of the security origin is associated with casting.
121 bool IsOriginForCasting(const GURL
& origin
) {
122 // Whitelisted tab casting extensions.
125 origin
.spec() == "chrome-extension://enhhojjnijigcajfphajepfemndkmdlo/" ||
127 origin
.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" ||
129 origin
.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" ||
131 origin
.spec() == "chrome-extension://dliochdbjfkdbacpmhlcpmleaejidimm/" ||
132 // Google Cast Stable
133 origin
.spec() == "chrome-extension://boadgeojelhgndaghljhdicfkmllpafd/" ||
134 // http://crbug.com/457908
135 origin
.spec() == "chrome-extension://ekpaaapppgpmolpcldedioblbkmijaca/" ||
136 origin
.spec() == "chrome-extension://fjhoaacokmgbjemoflkofnenfaiekifl/";
139 bool IsExtensionWhitelistedForScreenCapture(
140 const extensions::Extension
* extension
) {
141 #if defined(OS_CHROMEOS)
142 std::string hash
= base::SHA1HashString(extension
->id());
143 std::string hex_hash
= base::HexEncode(hash
.c_str(), hash
.length());
146 return hex_hash
== "4F25792AF1AA7483936DE29C07806F203C7170A0" ||
147 hex_hash
== "BD8781D757D830FC2E85470A1B6E8A718B7EE0D9" ||
148 hex_hash
== "4AC2B6C63C6480D150DFDA13E4A5956EB1D0DDBB" ||
149 hex_hash
== "81986D4F846CEDDDB962643FA501D1780DD441BB";
152 #endif // defined(OS_CHROMEOS)
154 #endif // defined(ENABLE_EXTENSIONS)
156 // Helper to get title of the calling application shown in the screen capture
158 base::string16
GetApplicationTitle(content::WebContents
* web_contents
,
159 const extensions::Extension
* extension
) {
160 // Use extension name as title for extensions and host/origin for drive-by
163 #if defined(ENABLE_EXTENSIONS)
165 title
= extension
->name();
166 return base::UTF8ToUTF16(title
);
169 GURL url
= web_contents
->GetURL();
170 title
= url
.SchemeIsSecure() ? net::GetHostAndOptionalPort(url
)
171 : url
.GetOrigin().spec();
172 return base::UTF8ToUTF16(title
);
175 // Helper to get list of media stream devices for desktop capture in |devices|.
176 // Registers to display notification if |display_notification| is true.
177 // Returns an instance of MediaStreamUI to be passed to content layer.
178 scoped_ptr
<content::MediaStreamUI
> GetDevicesForDesktopCapture(
179 content::MediaStreamDevices
* devices
,
180 content::DesktopMediaID media_id
,
182 bool display_notification
,
183 const base::string16
& application_title
,
184 const base::string16
& registered_extension_name
) {
185 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
186 scoped_ptr
<content::MediaStreamUI
> ui
;
188 // Add selected desktop source to the list.
189 devices
->push_back(content::MediaStreamDevice(
190 content::MEDIA_DESKTOP_VIDEO_CAPTURE
, media_id
.ToString(), "Screen"));
192 // Use the special loopback device ID for system audio capture.
193 devices
->push_back(content::MediaStreamDevice(
194 content::MEDIA_DESKTOP_AUDIO_CAPTURE
,
195 media::AudioManagerBase::kLoopbackInputDeviceId
, "System Audio"));
198 // If required, register to display the notification for stream capture.
199 if (display_notification
) {
200 if (application_title
== registered_extension_name
) {
201 ui
= ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
202 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT
,
205 ui
= ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
206 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT_DELEGATED
,
207 registered_extension_name
,
215 #if !defined(OS_ANDROID)
216 // Find browser or app window from a given |web_contents|.
217 gfx::NativeWindow
FindParentWindowForWebContents(
218 content::WebContents
* web_contents
) {
219 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents
);
220 if (browser
&& browser
->window())
221 return browser
->window()->GetNativeWindow();
223 const extensions::AppWindowRegistry::AppWindowList
& window_list
=
224 extensions::AppWindowRegistry::Get(
225 web_contents
->GetBrowserContext())->app_windows();
226 for (extensions::AppWindowRegistry::AppWindowList::const_iterator iter
=
228 iter
!= window_list
.end(); ++iter
) {
229 if ((*iter
)->web_contents() == web_contents
)
230 return (*iter
)->GetNativeWindow();
237 #if defined(ENABLE_EXTENSIONS)
238 const extensions::Extension
* GetExtensionForOrigin(
240 const GURL
& security_origin
) {
241 if (!security_origin
.SchemeIs(extensions::kExtensionScheme
))
244 const extensions::Extension
* extension
=
245 extensions::ExtensionRegistry::Get(profile
)->enabled_extensions().GetByID(
246 security_origin
.host());
254 MediaCaptureDevicesDispatcher::PendingAccessRequest::PendingAccessRequest(
255 const content::MediaStreamRequest
& request
,
256 const content::MediaResponseCallback
& callback
)
261 MediaCaptureDevicesDispatcher::PendingAccessRequest::~PendingAccessRequest() {}
263 MediaCaptureDevicesDispatcher
* MediaCaptureDevicesDispatcher::GetInstance() {
264 return Singleton
<MediaCaptureDevicesDispatcher
>::get();
267 MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher()
268 : is_device_enumeration_disabled_(false),
269 media_stream_capture_indicator_(new MediaStreamCaptureIndicator()) {
270 // MediaCaptureDevicesDispatcher is a singleton. It should be created on
271 // UI thread. Otherwise, it will not receive
272 // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in
273 // possible use after free.
274 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
275 notifications_registrar_
.Add(
276 this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
277 content::NotificationService::AllSources());
279 #if defined(OS_MACOSX)
280 // AVFoundation is used for video/audio device monitoring and video capture.
281 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
282 switches::kForceQTKit
)) {
283 base::CommandLine::ForCurrentProcess()->AppendSwitch(
284 switches::kEnableAVFoundation
);
289 MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {}
291 void MediaCaptureDevicesDispatcher::RegisterProfilePrefs(
292 user_prefs::PrefRegistrySyncable
* registry
) {
293 registry
->RegisterStringPref(
294 prefs::kDefaultAudioCaptureDevice
,
296 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
297 registry
->RegisterStringPref(
298 prefs::kDefaultVideoCaptureDevice
,
300 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
303 void MediaCaptureDevicesDispatcher::AddObserver(Observer
* observer
) {
304 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
305 if (!observers_
.HasObserver(observer
))
306 observers_
.AddObserver(observer
);
309 void MediaCaptureDevicesDispatcher::RemoveObserver(Observer
* observer
) {
310 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
311 observers_
.RemoveObserver(observer
);
314 const MediaStreamDevices
&
315 MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() {
316 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
317 if (is_device_enumeration_disabled_
|| !test_audio_devices_
.empty())
318 return test_audio_devices_
;
320 return MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices();
323 const MediaStreamDevices
&
324 MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() {
325 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
326 if (is_device_enumeration_disabled_
|| !test_video_devices_
.empty())
327 return test_video_devices_
;
329 return MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices();
332 void MediaCaptureDevicesDispatcher::Observe(
334 const content::NotificationSource
& source
,
335 const content::NotificationDetails
& details
) {
336 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
337 if (type
== content::NOTIFICATION_WEB_CONTENTS_DESTROYED
) {
338 content::WebContents
* web_contents
=
339 content::Source
<content::WebContents
>(source
).ptr();
340 pending_requests_
.erase(web_contents
);
344 void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequest(
345 content::WebContents
* web_contents
,
346 const content::MediaStreamRequest
& request
,
347 const content::MediaResponseCallback
& callback
,
348 const extensions::Extension
* extension
) {
349 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
351 if (request
.video_type
== content::MEDIA_DESKTOP_VIDEO_CAPTURE
||
352 request
.audio_type
== content::MEDIA_DESKTOP_AUDIO_CAPTURE
) {
353 ProcessDesktopCaptureAccessRequest(
354 web_contents
, request
, callback
, extension
);
355 } else if (request
.video_type
== content::MEDIA_TAB_VIDEO_CAPTURE
||
356 request
.audio_type
== content::MEDIA_TAB_AUDIO_CAPTURE
) {
357 ProcessTabCaptureAccessRequest(
358 web_contents
, request
, callback
, extension
);
360 #if defined(ENABLE_EXTENSIONS)
361 bool is_whitelisted
=
362 extension
&& (extension
->is_platform_app() ||
363 IsMediaRequestWhitelistedForExtension(extension
));
364 if (is_whitelisted
) {
365 // For extensions access is approved based on extension permissions.
366 ProcessMediaAccessRequestFromPlatformAppOrExtension(
367 web_contents
, request
, callback
, extension
);
371 ProcessRegularMediaAccessRequest(web_contents
, request
, callback
);
375 bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
376 content::BrowserContext
* browser_context
,
377 const GURL
& security_origin
,
378 content::MediaStreamType type
) {
379 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
380 DCHECK(type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
||
381 type
== content::MEDIA_DEVICE_VIDEO_CAPTURE
);
383 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
384 #if defined(ENABLE_EXTENSIONS)
385 const extensions::Extension
* extension
=
386 GetExtensionForOrigin(profile
, security_origin
);
388 if (extension
&& (extension
->is_platform_app() ||
389 IsMediaRequestWhitelistedForExtension(extension
))) {
390 return extension
->permissions_data()->HasAPIPermission(
391 type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
392 ? extensions::APIPermission::kAudioCapture
393 : extensions::APIPermission::kVideoCapture
);
397 ContentSettingsType contentSettingsType
=
398 type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
399 ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC
400 : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA
;
402 if (CheckAllowAllMediaStreamContentForOrigin(
403 profile
, security_origin
, contentSettingsType
)) {
407 const char* policy_name
= type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
408 ? prefs::kAudioCaptureAllowed
409 : prefs::kVideoCaptureAllowed
;
410 const char* list_policy_name
= type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
411 ? prefs::kAudioCaptureAllowedUrls
412 : prefs::kVideoCaptureAllowedUrls
;
414 profile
, security_origin
, policy_name
, list_policy_name
) ==
419 // There's no secondary URL for these content types, hence duplicating
420 // |security_origin|.
421 if (profile
->GetHostContentSettingsMap()->GetContentSetting(
425 content_settings::ResourceIdentifier()) == CONTENT_SETTING_ALLOW
) {
432 bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
433 content::WebContents
* web_contents
,
434 const GURL
& security_origin
,
435 content::MediaStreamType type
) {
436 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
437 DCHECK(type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
||
438 type
== content::MEDIA_DEVICE_VIDEO_CAPTURE
);
441 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
443 ContentSettingsType contentSettingsType
=
444 type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
445 ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC
446 : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA
;
448 if (CheckAllowAllMediaStreamContentForOrigin(
449 profile
, security_origin
, contentSettingsType
)) {
453 const char* policy_name
= type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
454 ? prefs::kAudioCaptureAllowed
455 : prefs::kVideoCaptureAllowed
;
456 const char* list_policy_name
= type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
457 ? prefs::kAudioCaptureAllowedUrls
458 : prefs::kVideoCaptureAllowedUrls
;
460 profile
, security_origin
, policy_name
, list_policy_name
) ==
465 // There's no secondary URL for these content types, hence duplicating
466 // |security_origin|.
467 if (profile
->GetHostContentSettingsMap()->GetContentSetting(
471 content_settings::ResourceIdentifier()) == CONTENT_SETTING_ALLOW
) {
478 #if defined(ENABLE_EXTENSIONS)
479 bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
480 content::WebContents
* web_contents
,
481 const GURL
& security_origin
,
482 content::MediaStreamType type
,
483 const extensions::Extension
* extension
) {
484 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
485 DCHECK(type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
||
486 type
== content::MEDIA_DEVICE_VIDEO_CAPTURE
);
488 if (extension
->is_platform_app() ||
489 IsMediaRequestWhitelistedForExtension(extension
)) {
490 return extension
->permissions_data()->HasAPIPermission(
491 type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
492 ? extensions::APIPermission::kAudioCapture
493 : extensions::APIPermission::kVideoCapture
);
496 return CheckMediaAccessPermission(web_contents
, security_origin
, type
);
500 void MediaCaptureDevicesDispatcher::ProcessDesktopCaptureAccessRequest(
501 content::WebContents
* web_contents
,
502 const content::MediaStreamRequest
& request
,
503 const content::MediaResponseCallback
& callback
,
504 const extensions::Extension
* extension
) {
505 content::MediaStreamDevices devices
;
506 scoped_ptr
<content::MediaStreamUI
> ui
;
508 if (request
.video_type
!= content::MEDIA_DESKTOP_VIDEO_CAPTURE
) {
509 callback
.Run(devices
, content::MEDIA_DEVICE_INVALID_STATE
, ui
.Pass());
513 // If the device id wasn't specified then this is a screen capture request
514 // (i.e. chooseDesktopMedia() API wasn't used to generate device id).
515 if (request
.requested_video_device_id
.empty()) {
516 ProcessScreenCaptureAccessRequest(
517 web_contents
, request
, callback
, extension
);
521 // The extension name that the stream is registered with.
522 std::string original_extension_name
;
523 // Resolve DesktopMediaID for the specified device id.
524 content::DesktopMediaID media_id
;
525 // TODO(miu): Replace "main RenderFrame" IDs with the request's actual
526 // RenderFrame IDs once the desktop capture extension API implementation is
527 // fixed. http://crbug.com/304341
528 content::WebContents
* const web_contents_for_stream
=
529 content::WebContents::FromRenderFrameHost(
530 content::RenderFrameHost::FromID(request
.render_process_id
,
531 request
.render_frame_id
));
532 content::RenderFrameHost
* const main_frame
= web_contents_for_stream
?
533 web_contents_for_stream
->GetMainFrame() : NULL
;
535 media_id
= GetDesktopStreamsRegistry()->RequestMediaForStreamId(
536 request
.requested_video_device_id
,
537 main_frame
->GetProcess()->GetID(),
538 main_frame
->GetRoutingID(),
539 request
.security_origin
,
540 &original_extension_name
);
543 // Received invalid device id.
544 if (media_id
.type
== content::DesktopMediaID::TYPE_NONE
) {
545 callback
.Run(devices
, content::MEDIA_DEVICE_INVALID_STATE
, ui
.Pass());
549 bool loopback_audio_supported
= false;
550 #if defined(USE_CRAS) || defined(OS_WIN)
551 // Currently loopback audio capture is supported only on Windows and ChromeOS.
552 loopback_audio_supported
= true;
555 // Audio is only supported for screen capture streams.
557 (media_id
.type
== content::DesktopMediaID::TYPE_SCREEN
&&
558 request
.audio_type
== content::MEDIA_DESKTOP_AUDIO_CAPTURE
&&
559 loopback_audio_supported
);
561 ui
= GetDevicesForDesktopCapture(
562 &devices
, media_id
, capture_audio
, true,
563 GetApplicationTitle(web_contents
, extension
),
564 base::UTF8ToUTF16(original_extension_name
));
566 callback
.Run(devices
, content::MEDIA_DEVICE_OK
, ui
.Pass());
569 void MediaCaptureDevicesDispatcher::ProcessScreenCaptureAccessRequest(
570 content::WebContents
* web_contents
,
571 const content::MediaStreamRequest
& request
,
572 const content::MediaResponseCallback
& callback
,
573 const extensions::Extension
* extension
) {
574 content::MediaStreamDevices devices
;
575 scoped_ptr
<content::MediaStreamUI
> ui
;
577 DCHECK_EQ(request
.video_type
, content::MEDIA_DESKTOP_VIDEO_CAPTURE
);
579 bool loopback_audio_supported
= false;
580 #if defined(USE_CRAS) || defined(OS_WIN)
581 // Currently loopback audio capture is supported only on Windows and ChromeOS.
582 loopback_audio_supported
= true;
585 bool component_extension
= false;
586 #if defined(ENABLE_EXTENSIONS)
587 component_extension
=
588 extension
&& extension
->location() == extensions::Manifest::COMPONENT
;
591 bool screen_capture_enabled
=
592 base::CommandLine::ForCurrentProcess()->HasSwitch(
593 switches::kEnableUserMediaScreenCapturing
);
594 #if defined(ENABLE_EXTENSIONS)
595 screen_capture_enabled
|=
596 IsOriginForCasting(request
.security_origin
) ||
597 IsExtensionWhitelistedForScreenCapture(extension
) ||
598 IsBuiltInExtension(request
.security_origin
);
601 const bool origin_is_secure
=
602 request
.security_origin
.SchemeIsSecure() ||
603 request
.security_origin
.SchemeIs(extensions::kExtensionScheme
) ||
604 base::CommandLine::ForCurrentProcess()->HasSwitch(
605 switches::kAllowHttpScreenCapture
);
607 // If basic conditions (screen capturing is enabled and origin is secure)
608 // aren't fulfilled, we'll use "invalid state" as result. Otherwise, we set
609 // it after checking permission.
610 // TODO(grunell): It would be good to change this result for something else,
611 // probably a new one.
612 content::MediaStreamRequestResult result
=
613 content::MEDIA_DEVICE_INVALID_STATE
;
615 // Approve request only when the following conditions are met:
616 // 1. Screen capturing is enabled via command line switch or white-listed for
618 // 2. Request comes from a page with a secure origin or from an extension.
619 if (screen_capture_enabled
&& origin_is_secure
) {
620 // Get title of the calling application prior to showing the message box.
621 // chrome::ShowMessageBox() starts a nested message loop which may allow
622 // |web_contents| to be destroyed on the UI thread before the message box
623 // is closed. See http://crbug.com/326690.
624 base::string16 application_title
=
625 GetApplicationTitle(web_contents
, extension
);
626 #if !defined(OS_ANDROID)
627 gfx::NativeWindow parent_window
=
628 FindParentWindowForWebContents(web_contents
);
630 gfx::NativeWindow parent_window
= NULL
;
634 bool whitelisted_extension
= false;
635 #if defined(ENABLE_EXTENSIONS)
636 whitelisted_extension
= IsExtensionWhitelistedForScreenCapture(
640 // For whitelisted or component extensions, bypass message box.
641 bool user_approved
= false;
642 if (!whitelisted_extension
&& !component_extension
) {
643 base::string16 application_name
=
644 base::UTF8ToUTF16(request
.security_origin
.spec());
645 #if defined(ENABLE_EXTENSIONS)
647 application_name
= base::UTF8ToUTF16(extension
->name());
649 base::string16 confirmation_text
= l10n_util::GetStringFUTF16(
650 request
.audio_type
== content::MEDIA_NO_SERVICE
?
651 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT
:
652 IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT
,
654 chrome::MessageBoxResult result
= chrome::ShowMessageBox(
656 l10n_util::GetStringFUTF16(
657 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE
, application_name
),
659 chrome::MESSAGE_BOX_TYPE_QUESTION
);
660 user_approved
= (result
== chrome::MESSAGE_BOX_RESULT_YES
);
663 if (user_approved
|| component_extension
|| whitelisted_extension
) {
664 content::DesktopMediaID screen_id
;
665 #if defined(OS_CHROMEOS)
666 screen_id
= content::DesktopMediaID::RegisterAuraWindow(
667 ash::Shell::GetInstance()->GetPrimaryRootWindow());
668 #else // defined(OS_CHROMEOS)
670 content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN
,
671 webrtc::kFullDesktopScreenId
);
672 #endif // !defined(OS_CHROMEOS)
675 (request
.audio_type
== content::MEDIA_DESKTOP_AUDIO_CAPTURE
&&
676 loopback_audio_supported
);
678 // Unless we're being invoked from a component extension, register to
679 // display the notification for stream capture.
680 bool display_notification
= !component_extension
;
682 ui
= GetDevicesForDesktopCapture(&devices
, screen_id
, capture_audio
,
683 display_notification
, application_title
,
685 DCHECK(!devices
.empty());
688 // The only case when devices can be empty is if the user has denied
690 result
= devices
.empty() ? content::MEDIA_DEVICE_PERMISSION_DENIED
691 : content::MEDIA_DEVICE_OK
;
694 callback
.Run(devices
, result
, ui
.Pass());
697 void MediaCaptureDevicesDispatcher::ProcessTabCaptureAccessRequest(
698 content::WebContents
* web_contents
,
699 const content::MediaStreamRequest
& request
,
700 const content::MediaResponseCallback
& callback
,
701 const extensions::Extension
* extension
) {
702 content::MediaStreamDevices devices
;
703 scoped_ptr
<content::MediaStreamUI
> ui
;
705 #if defined(ENABLE_EXTENSIONS)
707 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
708 extensions::TabCaptureRegistry
* tab_capture_registry
=
709 extensions::TabCaptureRegistry::Get(profile
);
710 if (!tab_capture_registry
) {
712 callback
.Run(devices
, content::MEDIA_DEVICE_INVALID_STATE
, ui
.Pass());
715 const bool tab_capture_allowed
= tab_capture_registry
->VerifyRequest(
716 request
.render_process_id
, request
.render_frame_id
, extension
->id());
718 if (request
.audio_type
== content::MEDIA_TAB_AUDIO_CAPTURE
&&
719 tab_capture_allowed
&&
720 extension
->permissions_data()->HasAPIPermission(
721 extensions::APIPermission::kTabCapture
)) {
722 devices
.push_back(content::MediaStreamDevice(
723 content::MEDIA_TAB_AUDIO_CAPTURE
, std::string(), std::string()));
726 if (request
.video_type
== content::MEDIA_TAB_VIDEO_CAPTURE
&&
727 tab_capture_allowed
&&
728 extension
->permissions_data()->HasAPIPermission(
729 extensions::APIPermission::kTabCapture
)) {
730 devices
.push_back(content::MediaStreamDevice(
731 content::MEDIA_TAB_VIDEO_CAPTURE
, std::string(), std::string()));
734 if (!devices
.empty()) {
735 ui
= media_stream_capture_indicator_
->RegisterMediaStream(
736 web_contents
, devices
);
740 devices
.empty() ? content::MEDIA_DEVICE_INVALID_STATE
:
741 content::MEDIA_DEVICE_OK
,
743 #else // defined(ENABLE_EXTENSIONS)
744 callback
.Run(devices
, content::MEDIA_DEVICE_TAB_CAPTURE_FAILURE
, ui
.Pass());
745 #endif // defined(ENABLE_EXTENSIONS)
748 #if defined(ENABLE_EXTENSIONS)
749 void MediaCaptureDevicesDispatcher::
750 ProcessMediaAccessRequestFromPlatformAppOrExtension(
751 content::WebContents
* web_contents
,
752 const content::MediaStreamRequest
& request
,
753 const content::MediaResponseCallback
& callback
,
754 const extensions::Extension
* extension
) {
755 // TODO(vrk): This code is largely duplicated in
756 // MediaStreamDevicesController::Accept(). Move this code into a shared method
757 // between the two classes.
760 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
763 request
.audio_type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
&&
764 extension
->permissions_data()->HasAPIPermission(
765 extensions::APIPermission::kAudioCapture
) &&
766 GetDevicePolicy(profile
, extension
->url(),
767 prefs::kAudioCaptureAllowed
,
768 prefs::kAudioCaptureAllowedUrls
) != ALWAYS_DENY
;
770 request
.video_type
== content::MEDIA_DEVICE_VIDEO_CAPTURE
&&
771 extension
->permissions_data()->HasAPIPermission(
772 extensions::APIPermission::kVideoCapture
) &&
773 GetDevicePolicy(profile
, extension
->url(),
774 prefs::kVideoCaptureAllowed
,
775 prefs::kVideoCaptureAllowedUrls
) != ALWAYS_DENY
;
777 bool get_default_audio_device
= audio_allowed
;
778 bool get_default_video_device
= video_allowed
;
780 content::MediaStreamDevices devices
;
782 // Set an initial error result. If neither audio or video is allowed, we'll
783 // never try to get any device below but will just create |ui| and return an
784 // empty list with "invalid state" result. If at least one is allowed, we'll
785 // try to get device(s), and if failure, we want to return "no hardware"
787 // TODO(grunell): The invalid state result should be changed to a new denied
788 // result + a dcheck to ensure at least one of audio or video types is
790 content::MediaStreamRequestResult result
=
791 (audio_allowed
|| video_allowed
) ? content::MEDIA_DEVICE_NO_HARDWARE
792 : content::MEDIA_DEVICE_INVALID_STATE
;
794 // Get the exact audio or video device if an id is specified.
795 // We only set any error result here and before running the callback change
796 // it to OK if we have any device.
797 if (audio_allowed
&& !request
.requested_audio_device_id
.empty()) {
798 const content::MediaStreamDevice
* audio_device
=
799 GetRequestedAudioDevice(request
.requested_audio_device_id
);
801 devices
.push_back(*audio_device
);
802 get_default_audio_device
= false;
805 if (video_allowed
&& !request
.requested_video_device_id
.empty()) {
806 const content::MediaStreamDevice
* video_device
=
807 GetRequestedVideoDevice(request
.requested_video_device_id
);
809 devices
.push_back(*video_device
);
810 get_default_video_device
= false;
814 // If either or both audio and video devices were requested but not
815 // specified by id, get the default devices.
816 if (get_default_audio_device
|| get_default_video_device
) {
817 GetDefaultDevicesForProfile(profile
,
818 get_default_audio_device
,
819 get_default_video_device
,
823 scoped_ptr
<content::MediaStreamUI
> ui
;
824 if (!devices
.empty()) {
825 result
= content::MEDIA_DEVICE_OK
;
826 ui
= media_stream_capture_indicator_
->RegisterMediaStream(
827 web_contents
, devices
);
830 callback
.Run(devices
, result
, ui
.Pass());
834 void MediaCaptureDevicesDispatcher::ProcessRegularMediaAccessRequest(
835 content::WebContents
* web_contents
,
836 const content::MediaStreamRequest
& request
,
837 const content::MediaResponseCallback
& callback
) {
838 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
840 RequestsQueue
& queue
= pending_requests_
[web_contents
];
841 queue
.push_back(PendingAccessRequest(request
, callback
));
843 // If this is the only request then show the infobar.
844 if (queue
.size() == 1)
845 ProcessQueuedAccessRequest(web_contents
);
848 void MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest(
849 content::WebContents
* web_contents
) {
850 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
852 std::map
<content::WebContents
*, RequestsQueue
>::iterator it
=
853 pending_requests_
.find(web_contents
);
855 if (it
== pending_requests_
.end() || it
->second
.empty()) {
856 // Don't do anything if the tab was closed.
860 DCHECK(!it
->second
.empty());
862 if (PermissionBubbleManager::Enabled() ||
863 MediaStreamPermissionBubbleExperimentEnabled()) {
864 scoped_ptr
<MediaStreamDevicesController
> controller(
865 new MediaStreamDevicesController(web_contents
,
866 it
->second
.front().request
,
867 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse
,
868 base::Unretained(this), web_contents
)));
869 if (controller
->DismissInfoBarAndTakeActionOnSettings())
871 PermissionBubbleManager
* bubble_manager
=
872 PermissionBubbleManager::FromWebContents(web_contents
);
874 bubble_manager
->AddRequest(controller
.release());
878 // TODO(gbillock): delete this block and the MediaStreamInfoBarDelegate
879 // when we've transitioned to bubbles. (crbug/337458)
880 MediaStreamInfoBarDelegate::Create(
881 web_contents
, it
->second
.front().request
,
882 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse
,
883 base::Unretained(this), web_contents
));
886 void MediaCaptureDevicesDispatcher::OnAccessRequestResponse(
887 content::WebContents
* web_contents
,
888 const content::MediaStreamDevices
& devices
,
889 content::MediaStreamRequestResult result
,
890 scoped_ptr
<content::MediaStreamUI
> ui
) {
891 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
893 std::map
<content::WebContents
*, RequestsQueue
>::iterator it
=
894 pending_requests_
.find(web_contents
);
895 if (it
== pending_requests_
.end()) {
896 // WebContents has been destroyed. Don't need to do anything.
900 RequestsQueue
& queue(it
->second
);
904 content::MediaResponseCallback callback
= queue
.front().callback
;
907 if (!queue
.empty()) {
908 // Post a task to process next queued request. It has to be done
909 // asynchronously to make sure that calling infobar is not destroyed until
910 // after this function returns.
911 BrowserThread::PostTask(
912 BrowserThread::UI
, FROM_HERE
,
913 base::Bind(&MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest
,
914 base::Unretained(this), web_contents
));
917 callback
.Run(devices
, result
, ui
.Pass());
920 void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile(
924 content::MediaStreamDevices
* devices
) {
925 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
926 DCHECK(audio
|| video
);
928 PrefService
* prefs
= profile
->GetPrefs();
929 std::string default_device
;
931 default_device
= prefs
->GetString(prefs::kDefaultAudioCaptureDevice
);
932 const content::MediaStreamDevice
* device
=
933 GetRequestedAudioDevice(default_device
);
935 device
= GetFirstAvailableAudioDevice();
937 devices
->push_back(*device
);
941 default_device
= prefs
->GetString(prefs::kDefaultVideoCaptureDevice
);
942 const content::MediaStreamDevice
* device
=
943 GetRequestedVideoDevice(default_device
);
945 device
= GetFirstAvailableVideoDevice();
947 devices
->push_back(*device
);
951 const content::MediaStreamDevice
*
952 MediaCaptureDevicesDispatcher::GetRequestedAudioDevice(
953 const std::string
& requested_audio_device_id
) {
954 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
955 const content::MediaStreamDevices
& audio_devices
= GetAudioCaptureDevices();
956 const content::MediaStreamDevice
* const device
=
957 FindDeviceWithId(audio_devices
, requested_audio_device_id
);
961 const content::MediaStreamDevice
*
962 MediaCaptureDevicesDispatcher::GetFirstAvailableAudioDevice() {
963 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
964 const content::MediaStreamDevices
& audio_devices
= GetAudioCaptureDevices();
965 if (audio_devices
.empty())
967 return &(*audio_devices
.begin());
970 const content::MediaStreamDevice
*
971 MediaCaptureDevicesDispatcher::GetRequestedVideoDevice(
972 const std::string
& requested_video_device_id
) {
973 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
974 const content::MediaStreamDevices
& video_devices
= GetVideoCaptureDevices();
975 const content::MediaStreamDevice
* const device
=
976 FindDeviceWithId(video_devices
, requested_video_device_id
);
980 const content::MediaStreamDevice
*
981 MediaCaptureDevicesDispatcher::GetFirstAvailableVideoDevice() {
982 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
983 const content::MediaStreamDevices
& video_devices
= GetVideoCaptureDevices();
984 if (video_devices
.empty())
986 return &(*video_devices
.begin());
989 void MediaCaptureDevicesDispatcher::DisableDeviceEnumerationForTesting() {
990 is_device_enumeration_disabled_
= true;
993 scoped_refptr
<MediaStreamCaptureIndicator
>
994 MediaCaptureDevicesDispatcher::GetMediaStreamCaptureIndicator() {
995 return media_stream_capture_indicator_
;
998 DesktopStreamsRegistry
*
999 MediaCaptureDevicesDispatcher::GetDesktopStreamsRegistry() {
1000 if (!desktop_streams_registry_
)
1001 desktop_streams_registry_
.reset(new DesktopStreamsRegistry());
1002 return desktop_streams_registry_
.get();
1005 void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged() {
1006 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
1007 BrowserThread::PostTask(
1008 BrowserThread::UI
, FROM_HERE
,
1010 &MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread
,
1011 base::Unretained(this)));
1014 void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged() {
1015 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
1016 BrowserThread::PostTask(
1017 BrowserThread::UI
, FROM_HERE
,
1019 &MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread
,
1020 base::Unretained(this)));
1023 void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged(
1024 int render_process_id
,
1025 int render_frame_id
,
1026 int page_request_id
,
1027 const GURL
& security_origin
,
1028 content::MediaStreamType stream_type
,
1029 content::MediaRequestState state
) {
1030 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
1031 BrowserThread::PostTask(
1032 BrowserThread::UI
, FROM_HERE
,
1034 &MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread
,
1035 base::Unretained(this), render_process_id
, render_frame_id
,
1036 page_request_id
, security_origin
, stream_type
, state
));
1039 void MediaCaptureDevicesDispatcher::OnCreatingAudioStream(
1040 int render_process_id
,
1041 int render_frame_id
) {
1042 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
1043 BrowserThread::PostTask(
1044 BrowserThread::UI
, FROM_HERE
,
1046 &MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread
,
1047 base::Unretained(this), render_process_id
, render_frame_id
));
1050 void MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread() {
1051 MediaStreamDevices devices
= GetAudioCaptureDevices();
1052 FOR_EACH_OBSERVER(Observer
, observers_
,
1053 OnUpdateAudioDevices(devices
));
1056 void MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread() {
1057 MediaStreamDevices devices
= GetVideoCaptureDevices();
1058 FOR_EACH_OBSERVER(Observer
, observers_
,
1059 OnUpdateVideoDevices(devices
));
1062 void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread(
1063 int render_process_id
,
1064 int render_frame_id
,
1065 int page_request_id
,
1066 const GURL
& security_origin
,
1067 content::MediaStreamType stream_type
,
1068 content::MediaRequestState state
) {
1069 // Track desktop capture sessions. Tracking is necessary to avoid unbalanced
1070 // session counts since not all requests will reach MEDIA_REQUEST_STATE_DONE,
1071 // but they will all reach MEDIA_REQUEST_STATE_CLOSING.
1072 if (stream_type
== content::MEDIA_DESKTOP_VIDEO_CAPTURE
) {
1073 if (state
== content::MEDIA_REQUEST_STATE_DONE
) {
1074 DesktopCaptureSession session
= { render_process_id
, render_frame_id
,
1076 desktop_capture_sessions_
.push_back(session
);
1077 } else if (state
== content::MEDIA_REQUEST_STATE_CLOSING
) {
1078 for (DesktopCaptureSessions::iterator it
=
1079 desktop_capture_sessions_
.begin();
1080 it
!= desktop_capture_sessions_
.end();
1082 if (it
->render_process_id
== render_process_id
&&
1083 it
->render_frame_id
== render_frame_id
&&
1084 it
->page_request_id
== page_request_id
) {
1085 desktop_capture_sessions_
.erase(it
);
1092 // Cancel the request.
1093 if (state
== content::MEDIA_REQUEST_STATE_CLOSING
) {
1095 for (RequestsQueues::iterator rqs_it
= pending_requests_
.begin();
1096 rqs_it
!= pending_requests_
.end(); ++rqs_it
) {
1097 RequestsQueue
& queue
= rqs_it
->second
;
1098 for (RequestsQueue::iterator it
= queue
.begin();
1099 it
!= queue
.end(); ++it
) {
1100 if (it
->request
.render_process_id
== render_process_id
&&
1101 it
->request
.render_frame_id
== render_frame_id
&&
1102 it
->request
.page_request_id
== page_request_id
) {
1113 #if defined(OS_CHROMEOS)
1114 if (IsOriginForCasting(security_origin
) && IsVideoMediaType(stream_type
)) {
1115 // Notify ash that casting state has changed.
1116 if (state
== content::MEDIA_REQUEST_STATE_DONE
) {
1117 ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(true);
1118 } else if (state
== content::MEDIA_REQUEST_STATE_CLOSING
) {
1119 ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(false);
1124 FOR_EACH_OBSERVER(Observer
, observers_
,
1125 OnRequestUpdate(render_process_id
,
1131 void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread(
1132 int render_process_id
,
1133 int render_frame_id
) {
1134 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
1135 FOR_EACH_OBSERVER(Observer
, observers_
,
1136 OnCreatingAudioStream(render_process_id
, render_frame_id
));
1139 bool MediaCaptureDevicesDispatcher::IsDesktopCaptureInProgress() {
1140 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
1141 return desktop_capture_sessions_
.size() > 0;
1144 void MediaCaptureDevicesDispatcher::SetTestAudioCaptureDevices(
1145 const MediaStreamDevices
& devices
) {
1146 test_audio_devices_
= devices
;
1149 void MediaCaptureDevicesDispatcher::SetTestVideoCaptureDevices(
1150 const MediaStreamDevices
& devices
) {
1151 test_video_devices_
= devices
;