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/content_settings/host_content_settings_map.h"
17 #include "chrome/browser/media/desktop_streams_registry.h"
18 #include "chrome/browser/media/media_stream_capture_indicator.h"
19 #include "chrome/browser/media/media_stream_device_permissions.h"
20 #include "chrome/browser/media/media_stream_infobar_delegate.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/browser_finder.h"
24 #include "chrome/browser/ui/browser_window.h"
25 #include "chrome/browser/ui/screen_capture_notification_ui.h"
26 #include "chrome/browser/ui/simple_message_box.h"
27 #include "chrome/browser/ui/website_settings/permission_bubble_manager.h"
28 #include "chrome/common/chrome_switches.h"
29 #include "chrome/common/chrome_version_info.h"
30 #include "chrome/common/pref_names.h"
31 #include "chrome/grit/generated_resources.h"
32 #include "components/content_settings/core/browser/content_settings_provider.h"
33 #include "components/pref_registry/pref_registry_syncable.h"
34 #include "content/public/browser/browser_thread.h"
35 #include "content/public/browser/desktop_media_id.h"
36 #include "content/public/browser/media_capture_devices.h"
37 #include "content/public/browser/notification_service.h"
38 #include "content/public/browser/notification_source.h"
39 #include "content/public/browser/notification_types.h"
40 #include "content/public/browser/render_frame_host.h"
41 #include "content/public/browser/render_process_host.h"
42 #include "content/public/browser/web_contents.h"
43 #include "content/public/common/media_stream_request.h"
44 #include "extensions/common/constants.h"
45 #include "extensions/common/extension.h"
46 #include "extensions/common/permissions/permissions_data.h"
47 #include "media/audio/audio_manager_base.h"
48 #include "media/base/media_switches.h"
49 #include "net/base/net_util.h"
50 #include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
51 #include "ui/base/l10n/l10n_util.h"
53 #if defined(OS_CHROMEOS)
54 #include "ash/shell.h"
55 #endif // defined(OS_CHROMEOS)
58 #if defined(ENABLE_EXTENSIONS)
59 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
60 #include "chrome/browser/extensions/extension_service.h"
61 #include "extensions/browser/app_window/app_window.h"
62 #include "extensions/browser/app_window/app_window_registry.h"
63 #include "extensions/browser/extension_system.h"
66 using content::BrowserThread
;
67 using content::MediaCaptureDevices
;
68 using content::MediaStreamDevices
;
72 // A finch experiment to enable the permission bubble for media requests only.
73 bool MediaStreamPermissionBubbleExperimentEnabled() {
74 const std::string group
=
75 base::FieldTrialList::FindFullName("MediaStreamPermissionBubble");
76 if (group
== "enabled")
82 // Finds a device in |devices| that has |device_id|, or NULL if not found.
83 const content::MediaStreamDevice
* FindDeviceWithId(
84 const content::MediaStreamDevices
& devices
,
85 const std::string
& device_id
) {
86 content::MediaStreamDevices::const_iterator iter
= devices
.begin();
87 for (; iter
!= devices
.end(); ++iter
) {
88 if (iter
->id
== device_id
) {
95 // This is a short-term solution to grant camera and/or microphone access to
97 // 1. Virtual keyboard extension.
98 // 2. Google Voice Search Hotword extension.
99 // 3. Flutter gesture recognition extension.
100 // 4. TODO(smus): Airbender experiment 1.
101 // 5. TODO(smus): Airbender experiment 2.
102 // 6. Hotwording component extension.
103 // Once http://crbug.com/292856 is fixed, remove this whitelist.
104 bool IsMediaRequestWhitelistedForExtension(
105 const extensions::Extension
* extension
) {
106 return extension
->id() == "mppnpdlheglhdfmldimlhpnegondlapf" ||
107 extension
->id() == "bepbmhgboaologfdajaanbcjmnhjmhfn" ||
108 extension
->id() == "jokbpnebhdcladagohdnfgjcpejggllo" ||
109 extension
->id() == "clffjmdilanldobdnedchkdbofoimcgb" ||
110 extension
->id() == "nnckehldicaciogcbchegobnafnjkcne" ||
111 extension
->id() == "nbpagnldghgfoolbancepceaanlmhfmd";
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/";
136 // Helper to get title of the calling application shown in the screen capture
138 base::string16
GetApplicationTitle(content::WebContents
* web_contents
,
139 const extensions::Extension
* extension
) {
140 // Use extension name as title for extensions and host/origin for drive-by
144 title
= extension
->name();
146 GURL url
= web_contents
->GetURL();
147 title
= url
.SchemeIsSecure() ? net::GetHostAndOptionalPort(url
)
148 : url
.GetOrigin().spec();
150 return base::UTF8ToUTF16(title
);
153 // Helper to get list of media stream devices for desktop capture in |devices|.
154 // Registers to display notification if |display_notification| is true.
155 // Returns an instance of MediaStreamUI to be passed to content layer.
156 scoped_ptr
<content::MediaStreamUI
> GetDevicesForDesktopCapture(
157 content::MediaStreamDevices
& devices
,
158 content::DesktopMediaID media_id
,
160 bool display_notification
,
161 const base::string16
& application_title
,
162 const base::string16
& registered_extension_name
) {
163 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
164 scoped_ptr
<content::MediaStreamUI
> ui
;
166 // Add selected desktop source to the list.
167 devices
.push_back(content::MediaStreamDevice(
168 content::MEDIA_DESKTOP_VIDEO_CAPTURE
, media_id
.ToString(), "Screen"));
170 // Use the special loopback device ID for system audio capture.
171 devices
.push_back(content::MediaStreamDevice(
172 content::MEDIA_LOOPBACK_AUDIO_CAPTURE
,
173 media::AudioManagerBase::kLoopbackInputDeviceId
, "System Audio"));
176 // If required, register to display the notification for stream capture.
177 if (display_notification
) {
178 if (application_title
== registered_extension_name
) {
179 ui
= ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
180 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT
,
183 ui
= ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
184 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT_DELEGATED
,
185 registered_extension_name
,
193 #if !defined(OS_ANDROID)
194 // Find browser or app window from a given |web_contents|.
195 gfx::NativeWindow
FindParentWindowForWebContents(
196 content::WebContents
* web_contents
) {
197 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents
);
198 if (browser
&& browser
->window())
199 return browser
->window()->GetNativeWindow();
201 const extensions::AppWindowRegistry::AppWindowList
& window_list
=
202 extensions::AppWindowRegistry::Get(
203 web_contents
->GetBrowserContext())->app_windows();
204 for (extensions::AppWindowRegistry::AppWindowList::const_iterator iter
=
206 iter
!= window_list
.end(); ++iter
) {
207 if ((*iter
)->web_contents() == web_contents
)
208 return (*iter
)->GetNativeWindow();
215 const extensions::Extension
* GetExtensionForOrigin(
217 const GURL
& security_origin
) {
218 #if defined(ENABLE_EXTENSIONS)
219 if (!security_origin
.SchemeIs(extensions::kExtensionScheme
))
222 ExtensionService
* extensions_service
=
223 extensions::ExtensionSystem::Get(profile
)->extension_service();
224 const extensions::Extension
* extension
=
225 extensions_service
->extensions()->GetByID(security_origin
.host());
235 MediaCaptureDevicesDispatcher::PendingAccessRequest::PendingAccessRequest(
236 const content::MediaStreamRequest
& request
,
237 const content::MediaResponseCallback
& callback
)
242 MediaCaptureDevicesDispatcher::PendingAccessRequest::~PendingAccessRequest() {}
244 MediaCaptureDevicesDispatcher
* MediaCaptureDevicesDispatcher::GetInstance() {
245 return Singleton
<MediaCaptureDevicesDispatcher
>::get();
248 MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher()
249 : is_device_enumeration_disabled_(false),
250 media_stream_capture_indicator_(new MediaStreamCaptureIndicator()) {
251 // MediaCaptureDevicesDispatcher is a singleton. It should be created on
252 // UI thread. Otherwise, it will not receive
253 // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in
254 // possible use after free.
255 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
256 notifications_registrar_
.Add(
257 this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
258 content::NotificationService::AllSources());
260 // AVFoundation is used for video/audio device monitoring and video capture in
261 // Mac. Experimentally, connect it in Dev, Canary and Unknown (developer
263 #if defined(OS_MACOSX)
264 chrome::VersionInfo::Channel channel
= chrome::VersionInfo::GetChannel();
265 if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kForceQTKit
)) {
266 if (channel
== chrome::VersionInfo::CHANNEL_DEV
||
267 channel
== chrome::VersionInfo::CHANNEL_CANARY
||
268 channel
== chrome::VersionInfo::CHANNEL_UNKNOWN
) {
269 CommandLine::ForCurrentProcess()->AppendSwitch(
270 switches::kEnableAVFoundation
);
276 MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {}
278 void MediaCaptureDevicesDispatcher::RegisterProfilePrefs(
279 user_prefs::PrefRegistrySyncable
* registry
) {
280 registry
->RegisterStringPref(
281 prefs::kDefaultAudioCaptureDevice
,
283 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
284 registry
->RegisterStringPref(
285 prefs::kDefaultVideoCaptureDevice
,
287 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
290 void MediaCaptureDevicesDispatcher::AddObserver(Observer
* observer
) {
291 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
292 if (!observers_
.HasObserver(observer
))
293 observers_
.AddObserver(observer
);
296 void MediaCaptureDevicesDispatcher::RemoveObserver(Observer
* observer
) {
297 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
298 observers_
.RemoveObserver(observer
);
301 const MediaStreamDevices
&
302 MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() {
303 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
304 if (is_device_enumeration_disabled_
|| !test_audio_devices_
.empty())
305 return test_audio_devices_
;
307 return MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices();
310 const MediaStreamDevices
&
311 MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() {
312 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
313 if (is_device_enumeration_disabled_
|| !test_video_devices_
.empty())
314 return test_video_devices_
;
316 return MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices();
319 void MediaCaptureDevicesDispatcher::Observe(
321 const content::NotificationSource
& source
,
322 const content::NotificationDetails
& details
) {
323 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
324 if (type
== content::NOTIFICATION_WEB_CONTENTS_DESTROYED
) {
325 content::WebContents
* web_contents
=
326 content::Source
<content::WebContents
>(source
).ptr();
327 pending_requests_
.erase(web_contents
);
331 void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequest(
332 content::WebContents
* web_contents
,
333 const content::MediaStreamRequest
& request
,
334 const content::MediaResponseCallback
& callback
,
335 const extensions::Extension
* extension
) {
336 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
338 if (request
.video_type
== content::MEDIA_DESKTOP_VIDEO_CAPTURE
||
339 request
.audio_type
== content::MEDIA_LOOPBACK_AUDIO_CAPTURE
) {
340 ProcessDesktopCaptureAccessRequest(
341 web_contents
, request
, callback
, extension
);
342 } else if (request
.video_type
== content::MEDIA_TAB_VIDEO_CAPTURE
||
343 request
.audio_type
== content::MEDIA_TAB_AUDIO_CAPTURE
) {
344 ProcessTabCaptureAccessRequest(
345 web_contents
, request
, callback
, extension
);
346 } else if (extension
&& (extension
->is_platform_app() ||
347 IsMediaRequestWhitelistedForExtension(extension
))) {
348 // For extensions access is approved based on extension permissions.
349 ProcessMediaAccessRequestFromPlatformAppOrExtension(
350 web_contents
, request
, callback
, extension
);
352 ProcessRegularMediaAccessRequest(web_contents
, request
, callback
);
356 bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
357 content::BrowserContext
* browser_context
,
358 const GURL
& security_origin
,
359 content::MediaStreamType type
) {
360 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
361 DCHECK(type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
||
362 type
== content::MEDIA_DEVICE_VIDEO_CAPTURE
);
364 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
365 const extensions::Extension
* extension
=
366 GetExtensionForOrigin(profile
, security_origin
);
368 if (extension
&& (extension
->is_platform_app() ||
369 IsMediaRequestWhitelistedForExtension(extension
))) {
370 return extension
->permissions_data()->HasAPIPermission(
371 type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
372 ? extensions::APIPermission::kAudioCapture
373 : extensions::APIPermission::kVideoCapture
);
376 if (CheckAllowAllMediaStreamContentForOrigin(profile
, security_origin
))
379 const char* policy_name
= type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
380 ? prefs::kAudioCaptureAllowed
381 : prefs::kVideoCaptureAllowed
;
382 const char* list_policy_name
= type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
383 ? prefs::kAudioCaptureAllowedUrls
384 : prefs::kVideoCaptureAllowedUrls
;
386 profile
, security_origin
, policy_name
, list_policy_name
) ==
391 // There's no secondary URL for these content types, hence duplicating
392 // |security_origin|.
393 if (profile
->GetHostContentSettingsMap()->GetContentSetting(
396 type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
397 ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC
398 : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA
,
399 NO_RESOURCE_IDENTIFIER
) == CONTENT_SETTING_ALLOW
) {
406 bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
407 content::WebContents
* web_contents
,
408 const GURL
& security_origin
,
409 content::MediaStreamType type
) {
410 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
411 DCHECK(type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
||
412 type
== content::MEDIA_DEVICE_VIDEO_CAPTURE
);
415 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
417 if (CheckAllowAllMediaStreamContentForOrigin(profile
, security_origin
))
420 const char* policy_name
= type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
421 ? prefs::kAudioCaptureAllowed
422 : prefs::kVideoCaptureAllowed
;
423 const char* list_policy_name
= type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
424 ? prefs::kAudioCaptureAllowedUrls
425 : prefs::kVideoCaptureAllowedUrls
;
427 profile
, security_origin
, policy_name
, list_policy_name
) ==
432 // There's no secondary URL for these content types, hence duplicating
433 // |security_origin|.
434 if (profile
->GetHostContentSettingsMap()->GetContentSetting(
437 type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
438 ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC
439 : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA
,
440 NO_RESOURCE_IDENTIFIER
) == CONTENT_SETTING_ALLOW
) {
447 #if defined(ENABLE_EXTENSIONS)
448 bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
449 content::WebContents
* web_contents
,
450 const GURL
& security_origin
,
451 content::MediaStreamType type
,
452 const extensions::Extension
* extension
) {
453 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
454 DCHECK(type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
||
455 type
== content::MEDIA_DEVICE_VIDEO_CAPTURE
);
457 if (extension
->is_platform_app() ||
458 IsMediaRequestWhitelistedForExtension(extension
)) {
459 return extension
->permissions_data()->HasAPIPermission(
460 type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
461 ? extensions::APIPermission::kAudioCapture
462 : extensions::APIPermission::kVideoCapture
);
465 return CheckMediaAccessPermission(web_contents
, security_origin
, type
);
469 void MediaCaptureDevicesDispatcher::ProcessDesktopCaptureAccessRequest(
470 content::WebContents
* web_contents
,
471 const content::MediaStreamRequest
& request
,
472 const content::MediaResponseCallback
& callback
,
473 const extensions::Extension
* extension
) {
474 content::MediaStreamDevices devices
;
475 scoped_ptr
<content::MediaStreamUI
> ui
;
477 if (request
.video_type
!= content::MEDIA_DESKTOP_VIDEO_CAPTURE
) {
478 callback
.Run(devices
, content::MEDIA_DEVICE_INVALID_STATE
, ui
.Pass());
482 // If the device id wasn't specified then this is a screen capture request
483 // (i.e. chooseDesktopMedia() API wasn't used to generate device id).
484 if (request
.requested_video_device_id
.empty()) {
485 ProcessScreenCaptureAccessRequest(
486 web_contents
, request
, callback
, extension
);
490 // The extension name that the stream is registered with.
491 std::string original_extension_name
;
492 // Resolve DesktopMediaID for the specified device id.
493 content::DesktopMediaID media_id
;
494 // TODO(miu): Replace "main RenderFrame" IDs with the request's actual
495 // RenderFrame IDs once the desktop capture extension API implementation is
496 // fixed. http://crbug.com/304341
497 content::WebContents
* const web_contents_for_stream
=
498 content::WebContents::FromRenderFrameHost(
499 content::RenderFrameHost::FromID(request
.render_process_id
,
500 request
.render_frame_id
));
501 content::RenderFrameHost
* const main_frame
= web_contents_for_stream
?
502 web_contents_for_stream
->GetMainFrame() : NULL
;
504 media_id
= GetDesktopStreamsRegistry()->RequestMediaForStreamId(
505 request
.requested_video_device_id
,
506 main_frame
->GetProcess()->GetID(),
507 main_frame
->GetRoutingID(),
508 request
.security_origin
,
509 &original_extension_name
);
512 // Received invalid device id.
513 if (media_id
.type
== content::DesktopMediaID::TYPE_NONE
) {
514 callback
.Run(devices
, content::MEDIA_DEVICE_INVALID_STATE
, ui
.Pass());
518 bool loopback_audio_supported
= false;
519 #if defined(USE_CRAS) || defined(OS_WIN)
520 // Currently loopback audio capture is supported only on Windows and ChromeOS.
521 loopback_audio_supported
= true;
524 // Audio is only supported for screen capture streams.
526 (media_id
.type
== content::DesktopMediaID::TYPE_SCREEN
&&
527 request
.audio_type
== content::MEDIA_LOOPBACK_AUDIO_CAPTURE
&&
528 loopback_audio_supported
);
530 ui
= GetDevicesForDesktopCapture(
531 devices
, media_id
, capture_audio
, true,
532 GetApplicationTitle(web_contents
, extension
),
533 base::UTF8ToUTF16(original_extension_name
));
535 callback
.Run(devices
, content::MEDIA_DEVICE_OK
, ui
.Pass());
538 void MediaCaptureDevicesDispatcher::ProcessScreenCaptureAccessRequest(
539 content::WebContents
* web_contents
,
540 const content::MediaStreamRequest
& request
,
541 const content::MediaResponseCallback
& callback
,
542 const extensions::Extension
* extension
) {
543 content::MediaStreamDevices devices
;
544 scoped_ptr
<content::MediaStreamUI
> ui
;
546 DCHECK_EQ(request
.video_type
, content::MEDIA_DESKTOP_VIDEO_CAPTURE
);
548 bool loopback_audio_supported
= false;
549 #if defined(USE_CRAS) || defined(OS_WIN)
550 // Currently loopback audio capture is supported only on Windows and ChromeOS.
551 loopback_audio_supported
= true;
554 const bool component_extension
=
555 extension
&& extension
->location() == extensions::Manifest::COMPONENT
;
557 const bool screen_capture_enabled
=
558 CommandLine::ForCurrentProcess()->HasSwitch(
559 switches::kEnableUserMediaScreenCapturing
) ||
560 IsOriginForCasting(request
.security_origin
) ||
561 IsBuiltInExtension(request
.security_origin
);
563 const bool origin_is_secure
=
564 request
.security_origin
.SchemeIsSecure() ||
565 request
.security_origin
.SchemeIs(extensions::kExtensionScheme
) ||
566 CommandLine::ForCurrentProcess()->HasSwitch(
567 switches::kAllowHttpScreenCapture
);
569 // Approve request only when the following conditions are met:
570 // 1. Screen capturing is enabled via command line switch or white-listed for
572 // 2. Request comes from a page with a secure origin or from an extension.
573 if (screen_capture_enabled
&& origin_is_secure
) {
574 // Get title of the calling application prior to showing the message box.
575 // chrome::ShowMessageBox() starts a nested message loop which may allow
576 // |web_contents| to be destroyed on the UI thread before the message box
577 // is closed. See http://crbug.com/326690.
578 base::string16 application_title
=
579 GetApplicationTitle(web_contents
, extension
);
580 #if !defined(OS_ANDROID)
581 gfx::NativeWindow parent_window
=
582 FindParentWindowForWebContents(web_contents
);
584 gfx::NativeWindow parent_window
= NULL
;
588 // For component extensions, bypass message box.
589 bool user_approved
= false;
590 if (!component_extension
) {
591 base::string16 application_name
= base::UTF8ToUTF16(
592 extension
? extension
->name() : request
.security_origin
.spec());
593 base::string16 confirmation_text
= l10n_util::GetStringFUTF16(
594 request
.audio_type
== content::MEDIA_NO_SERVICE
?
595 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT
:
596 IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT
,
598 chrome::MessageBoxResult result
= chrome::ShowMessageBox(
600 l10n_util::GetStringFUTF16(
601 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE
, application_name
),
603 chrome::MESSAGE_BOX_TYPE_QUESTION
);
604 user_approved
= (result
== chrome::MESSAGE_BOX_RESULT_YES
);
607 if (user_approved
|| component_extension
) {
608 content::DesktopMediaID screen_id
;
609 #if defined(OS_CHROMEOS)
610 screen_id
= content::DesktopMediaID::RegisterAuraWindow(
611 ash::Shell::GetInstance()->GetPrimaryRootWindow());
612 #else // defined(OS_CHROMEOS)
614 content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN
,
615 webrtc::kFullDesktopScreenId
);
616 #endif // !defined(OS_CHROMEOS)
619 (request
.audio_type
== content::MEDIA_LOOPBACK_AUDIO_CAPTURE
&&
620 loopback_audio_supported
);
622 // Unless we're being invoked from a component extension, register to
623 // display the notification for stream capture.
624 bool display_notification
= !component_extension
;
626 ui
= GetDevicesForDesktopCapture(devices
, screen_id
, capture_audio
,
627 display_notification
, application_title
,
634 devices
.empty() ? content::MEDIA_DEVICE_INVALID_STATE
:
635 content::MEDIA_DEVICE_OK
,
639 void MediaCaptureDevicesDispatcher::ProcessTabCaptureAccessRequest(
640 content::WebContents
* web_contents
,
641 const content::MediaStreamRequest
& request
,
642 const content::MediaResponseCallback
& callback
,
643 const extensions::Extension
* extension
) {
644 content::MediaStreamDevices devices
;
645 scoped_ptr
<content::MediaStreamUI
> ui
;
647 #if defined(ENABLE_EXTENSIONS)
649 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
650 extensions::TabCaptureRegistry
* tab_capture_registry
=
651 extensions::TabCaptureRegistry::Get(profile
);
652 if (!tab_capture_registry
) {
654 callback
.Run(devices
, content::MEDIA_DEVICE_INVALID_STATE
, ui
.Pass());
657 const bool tab_capture_allowed
= tab_capture_registry
->VerifyRequest(
658 request
.render_process_id
, request
.render_frame_id
, extension
->id());
660 if (request
.audio_type
== content::MEDIA_TAB_AUDIO_CAPTURE
&&
661 tab_capture_allowed
&&
662 extension
->permissions_data()->HasAPIPermission(
663 extensions::APIPermission::kTabCapture
)) {
664 devices
.push_back(content::MediaStreamDevice(
665 content::MEDIA_TAB_AUDIO_CAPTURE
, std::string(), std::string()));
668 if (request
.video_type
== content::MEDIA_TAB_VIDEO_CAPTURE
&&
669 tab_capture_allowed
&&
670 extension
->permissions_data()->HasAPIPermission(
671 extensions::APIPermission::kTabCapture
)) {
672 devices
.push_back(content::MediaStreamDevice(
673 content::MEDIA_TAB_VIDEO_CAPTURE
, std::string(), std::string()));
676 if (!devices
.empty()) {
677 ui
= media_stream_capture_indicator_
->RegisterMediaStream(
678 web_contents
, devices
);
682 devices
.empty() ? content::MEDIA_DEVICE_INVALID_STATE
:
683 content::MEDIA_DEVICE_OK
,
685 #else // defined(ENABLE_EXTENSIONS)
686 callback
.Run(devices
, content::MEDIA_DEVICE_TAB_CAPTURE_FAILURE
, ui
.Pass());
687 #endif // defined(ENABLE_EXTENSIONS)
690 void MediaCaptureDevicesDispatcher::
691 ProcessMediaAccessRequestFromPlatformAppOrExtension(
692 content::WebContents
* web_contents
,
693 const content::MediaStreamRequest
& request
,
694 const content::MediaResponseCallback
& callback
,
695 const extensions::Extension
* extension
) {
696 // TODO(vrk): This code is largely duplicated in
697 // MediaStreamDevicesController::Accept(). Move this code into a shared method
698 // between the two classes.
701 request
.audio_type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
&&
702 extension
->permissions_data()->HasAPIPermission(
703 extensions::APIPermission::kAudioCapture
);
705 request
.video_type
== content::MEDIA_DEVICE_VIDEO_CAPTURE
&&
706 extension
->permissions_data()->HasAPIPermission(
707 extensions::APIPermission::kVideoCapture
);
709 bool get_default_audio_device
= audio_allowed
;
710 bool get_default_video_device
= video_allowed
;
712 content::MediaStreamDevices devices
;
714 // Get the exact audio or video device if an id is specified.
715 if (audio_allowed
&& !request
.requested_audio_device_id
.empty()) {
716 const content::MediaStreamDevice
* audio_device
=
717 GetRequestedAudioDevice(request
.requested_audio_device_id
);
719 devices
.push_back(*audio_device
);
720 get_default_audio_device
= false;
723 if (video_allowed
&& !request
.requested_video_device_id
.empty()) {
724 const content::MediaStreamDevice
* video_device
=
725 GetRequestedVideoDevice(request
.requested_video_device_id
);
727 devices
.push_back(*video_device
);
728 get_default_video_device
= false;
732 // If either or both audio and video devices were requested but not
733 // specified by id, get the default devices.
734 if (get_default_audio_device
|| get_default_video_device
) {
736 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
737 GetDefaultDevicesForProfile(profile
,
738 get_default_audio_device
,
739 get_default_video_device
,
743 scoped_ptr
<content::MediaStreamUI
> ui
;
744 if (!devices
.empty()) {
745 ui
= media_stream_capture_indicator_
->RegisterMediaStream(
746 web_contents
, devices
);
750 devices
.empty() ? content::MEDIA_DEVICE_INVALID_STATE
:
751 content::MEDIA_DEVICE_OK
,
755 void MediaCaptureDevicesDispatcher::ProcessRegularMediaAccessRequest(
756 content::WebContents
* web_contents
,
757 const content::MediaStreamRequest
& request
,
758 const content::MediaResponseCallback
& callback
) {
759 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
761 RequestsQueue
& queue
= pending_requests_
[web_contents
];
762 queue
.push_back(PendingAccessRequest(request
, callback
));
764 // If this is the only request then show the infobar.
765 if (queue
.size() == 1)
766 ProcessQueuedAccessRequest(web_contents
);
769 void MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest(
770 content::WebContents
* web_contents
) {
771 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
773 std::map
<content::WebContents
*, RequestsQueue
>::iterator it
=
774 pending_requests_
.find(web_contents
);
776 if (it
== pending_requests_
.end() || it
->second
.empty()) {
777 // Don't do anything if the tab was closed.
781 DCHECK(!it
->second
.empty());
783 if (PermissionBubbleManager::Enabled() ||
784 MediaStreamPermissionBubbleExperimentEnabled()) {
785 scoped_ptr
<MediaStreamDevicesController
> controller(
786 new MediaStreamDevicesController(web_contents
,
787 it
->second
.front().request
,
788 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse
,
789 base::Unretained(this), web_contents
)));
790 if (controller
->DismissInfoBarAndTakeActionOnSettings())
792 PermissionBubbleManager
* bubble_manager
=
793 PermissionBubbleManager::FromWebContents(web_contents
);
795 bubble_manager
->AddRequest(controller
.release());
799 // TODO(gbillock): delete this block and the MediaStreamInfoBarDelegate
800 // when we've transitioned to bubbles. (crbug/337458)
801 MediaStreamInfoBarDelegate::Create(
802 web_contents
, it
->second
.front().request
,
803 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse
,
804 base::Unretained(this), web_contents
));
807 void MediaCaptureDevicesDispatcher::OnAccessRequestResponse(
808 content::WebContents
* web_contents
,
809 const content::MediaStreamDevices
& devices
,
810 content::MediaStreamRequestResult result
,
811 scoped_ptr
<content::MediaStreamUI
> ui
) {
812 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
814 std::map
<content::WebContents
*, RequestsQueue
>::iterator it
=
815 pending_requests_
.find(web_contents
);
816 if (it
== pending_requests_
.end()) {
817 // WebContents has been destroyed. Don't need to do anything.
821 RequestsQueue
& queue(it
->second
);
825 content::MediaResponseCallback callback
= queue
.front().callback
;
828 if (!queue
.empty()) {
829 // Post a task to process next queued request. It has to be done
830 // asynchronously to make sure that calling infobar is not destroyed until
831 // after this function returns.
832 BrowserThread::PostTask(
833 BrowserThread::UI
, FROM_HERE
,
834 base::Bind(&MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest
,
835 base::Unretained(this), web_contents
));
838 callback
.Run(devices
, result
, ui
.Pass());
841 void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile(
845 content::MediaStreamDevices
* devices
) {
846 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
847 DCHECK(audio
|| video
);
849 PrefService
* prefs
= profile
->GetPrefs();
850 std::string default_device
;
852 default_device
= prefs
->GetString(prefs::kDefaultAudioCaptureDevice
);
853 const content::MediaStreamDevice
* device
=
854 GetRequestedAudioDevice(default_device
);
856 device
= GetFirstAvailableAudioDevice();
858 devices
->push_back(*device
);
862 default_device
= prefs
->GetString(prefs::kDefaultVideoCaptureDevice
);
863 const content::MediaStreamDevice
* device
=
864 GetRequestedVideoDevice(default_device
);
866 device
= GetFirstAvailableVideoDevice();
868 devices
->push_back(*device
);
872 const content::MediaStreamDevice
*
873 MediaCaptureDevicesDispatcher::GetRequestedAudioDevice(
874 const std::string
& requested_audio_device_id
) {
875 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
876 const content::MediaStreamDevices
& audio_devices
= GetAudioCaptureDevices();
877 const content::MediaStreamDevice
* const device
=
878 FindDeviceWithId(audio_devices
, requested_audio_device_id
);
882 const content::MediaStreamDevice
*
883 MediaCaptureDevicesDispatcher::GetFirstAvailableAudioDevice() {
884 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
885 const content::MediaStreamDevices
& audio_devices
= GetAudioCaptureDevices();
886 if (audio_devices
.empty())
888 return &(*audio_devices
.begin());
891 const content::MediaStreamDevice
*
892 MediaCaptureDevicesDispatcher::GetRequestedVideoDevice(
893 const std::string
& requested_video_device_id
) {
894 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
895 const content::MediaStreamDevices
& video_devices
= GetVideoCaptureDevices();
896 const content::MediaStreamDevice
* const device
=
897 FindDeviceWithId(video_devices
, requested_video_device_id
);
901 const content::MediaStreamDevice
*
902 MediaCaptureDevicesDispatcher::GetFirstAvailableVideoDevice() {
903 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
904 const content::MediaStreamDevices
& video_devices
= GetVideoCaptureDevices();
905 if (video_devices
.empty())
907 return &(*video_devices
.begin());
910 void MediaCaptureDevicesDispatcher::DisableDeviceEnumerationForTesting() {
911 is_device_enumeration_disabled_
= true;
914 scoped_refptr
<MediaStreamCaptureIndicator
>
915 MediaCaptureDevicesDispatcher::GetMediaStreamCaptureIndicator() {
916 return media_stream_capture_indicator_
;
919 DesktopStreamsRegistry
*
920 MediaCaptureDevicesDispatcher::GetDesktopStreamsRegistry() {
921 if (!desktop_streams_registry_
)
922 desktop_streams_registry_
.reset(new DesktopStreamsRegistry());
923 return desktop_streams_registry_
.get();
926 void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged() {
927 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
928 BrowserThread::PostTask(
929 BrowserThread::UI
, FROM_HERE
,
931 &MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread
,
932 base::Unretained(this)));
935 void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged() {
936 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
937 BrowserThread::PostTask(
938 BrowserThread::UI
, FROM_HERE
,
940 &MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread
,
941 base::Unretained(this)));
944 void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged(
945 int render_process_id
,
948 const GURL
& security_origin
,
949 content::MediaStreamType stream_type
,
950 content::MediaRequestState state
) {
951 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
952 BrowserThread::PostTask(
953 BrowserThread::UI
, FROM_HERE
,
955 &MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread
,
956 base::Unretained(this), render_process_id
, render_frame_id
,
957 page_request_id
, security_origin
, stream_type
, state
));
960 void MediaCaptureDevicesDispatcher::OnCreatingAudioStream(
961 int render_process_id
,
962 int render_frame_id
) {
963 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
964 BrowserThread::PostTask(
965 BrowserThread::UI
, FROM_HERE
,
967 &MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread
,
968 base::Unretained(this), render_process_id
, render_frame_id
));
971 void MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread() {
972 MediaStreamDevices devices
= GetAudioCaptureDevices();
973 FOR_EACH_OBSERVER(Observer
, observers_
,
974 OnUpdateAudioDevices(devices
));
977 void MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread() {
978 MediaStreamDevices devices
= GetVideoCaptureDevices();
979 FOR_EACH_OBSERVER(Observer
, observers_
,
980 OnUpdateVideoDevices(devices
));
983 void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread(
984 int render_process_id
,
987 const GURL
& security_origin
,
988 content::MediaStreamType stream_type
,
989 content::MediaRequestState state
) {
990 // Track desktop capture sessions. Tracking is necessary to avoid unbalanced
991 // session counts since not all requests will reach MEDIA_REQUEST_STATE_DONE,
992 // but they will all reach MEDIA_REQUEST_STATE_CLOSING.
993 if (stream_type
== content::MEDIA_DESKTOP_VIDEO_CAPTURE
) {
994 if (state
== content::MEDIA_REQUEST_STATE_DONE
) {
995 DesktopCaptureSession session
= { render_process_id
, render_frame_id
,
997 desktop_capture_sessions_
.push_back(session
);
998 } else if (state
== content::MEDIA_REQUEST_STATE_CLOSING
) {
999 for (DesktopCaptureSessions::iterator it
=
1000 desktop_capture_sessions_
.begin();
1001 it
!= desktop_capture_sessions_
.end();
1003 if (it
->render_process_id
== render_process_id
&&
1004 it
->render_frame_id
== render_frame_id
&&
1005 it
->page_request_id
== page_request_id
) {
1006 desktop_capture_sessions_
.erase(it
);
1013 // Cancel the request.
1014 if (state
== content::MEDIA_REQUEST_STATE_CLOSING
) {
1016 for (RequestsQueues::iterator rqs_it
= pending_requests_
.begin();
1017 rqs_it
!= pending_requests_
.end(); ++rqs_it
) {
1018 RequestsQueue
& queue
= rqs_it
->second
;
1019 for (RequestsQueue::iterator it
= queue
.begin();
1020 it
!= queue
.end(); ++it
) {
1021 if (it
->request
.render_process_id
== render_process_id
&&
1022 it
->request
.render_frame_id
== render_frame_id
&&
1023 it
->request
.page_request_id
== page_request_id
) {
1034 #if defined(OS_CHROMEOS)
1035 if (IsOriginForCasting(security_origin
) && IsVideoMediaType(stream_type
)) {
1036 // Notify ash that casting state has changed.
1037 if (state
== content::MEDIA_REQUEST_STATE_DONE
) {
1038 ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(true);
1039 } else if (state
== content::MEDIA_REQUEST_STATE_CLOSING
) {
1040 ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(false);
1045 FOR_EACH_OBSERVER(Observer
, observers_
,
1046 OnRequestUpdate(render_process_id
,
1052 void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread(
1053 int render_process_id
,
1054 int render_frame_id
) {
1055 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1056 FOR_EACH_OBSERVER(Observer
, observers_
,
1057 OnCreatingAudioStream(render_process_id
, render_frame_id
));
1060 bool MediaCaptureDevicesDispatcher::IsDesktopCaptureInProgress() {
1061 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1062 return desktop_capture_sessions_
.size() > 0;
1065 void MediaCaptureDevicesDispatcher::SetTestAudioCaptureDevices(
1066 const MediaStreamDevices
& devices
) {
1067 test_audio_devices_
= devices
;
1070 void MediaCaptureDevicesDispatcher::SetTestVideoCaptureDevices(
1071 const MediaStreamDevices
& devices
) {
1072 test_video_devices_
= devices
;