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 #if defined(OS_MACOSX)
261 // AVFoundation is used for video/audio device monitoring and video capture.
262 if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kForceQTKit
)) {
263 CommandLine::ForCurrentProcess()->AppendSwitch(
264 switches::kEnableAVFoundation
);
269 MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {}
271 void MediaCaptureDevicesDispatcher::RegisterProfilePrefs(
272 user_prefs::PrefRegistrySyncable
* registry
) {
273 registry
->RegisterStringPref(
274 prefs::kDefaultAudioCaptureDevice
,
276 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
277 registry
->RegisterStringPref(
278 prefs::kDefaultVideoCaptureDevice
,
280 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
283 void MediaCaptureDevicesDispatcher::AddObserver(Observer
* observer
) {
284 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
285 if (!observers_
.HasObserver(observer
))
286 observers_
.AddObserver(observer
);
289 void MediaCaptureDevicesDispatcher::RemoveObserver(Observer
* observer
) {
290 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
291 observers_
.RemoveObserver(observer
);
294 const MediaStreamDevices
&
295 MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() {
296 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
297 if (is_device_enumeration_disabled_
|| !test_audio_devices_
.empty())
298 return test_audio_devices_
;
300 return MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices();
303 const MediaStreamDevices
&
304 MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() {
305 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
306 if (is_device_enumeration_disabled_
|| !test_video_devices_
.empty())
307 return test_video_devices_
;
309 return MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices();
312 void MediaCaptureDevicesDispatcher::Observe(
314 const content::NotificationSource
& source
,
315 const content::NotificationDetails
& details
) {
316 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
317 if (type
== content::NOTIFICATION_WEB_CONTENTS_DESTROYED
) {
318 content::WebContents
* web_contents
=
319 content::Source
<content::WebContents
>(source
).ptr();
320 pending_requests_
.erase(web_contents
);
324 void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequest(
325 content::WebContents
* web_contents
,
326 const content::MediaStreamRequest
& request
,
327 const content::MediaResponseCallback
& callback
,
328 const extensions::Extension
* extension
) {
329 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
331 if (request
.video_type
== content::MEDIA_DESKTOP_VIDEO_CAPTURE
||
332 request
.audio_type
== content::MEDIA_LOOPBACK_AUDIO_CAPTURE
) {
333 ProcessDesktopCaptureAccessRequest(
334 web_contents
, request
, callback
, extension
);
335 } else if (request
.video_type
== content::MEDIA_TAB_VIDEO_CAPTURE
||
336 request
.audio_type
== content::MEDIA_TAB_AUDIO_CAPTURE
) {
337 ProcessTabCaptureAccessRequest(
338 web_contents
, request
, callback
, extension
);
339 } else if (extension
&& (extension
->is_platform_app() ||
340 IsMediaRequestWhitelistedForExtension(extension
))) {
341 // For extensions access is approved based on extension permissions.
342 ProcessMediaAccessRequestFromPlatformAppOrExtension(
343 web_contents
, request
, callback
, extension
);
345 ProcessRegularMediaAccessRequest(web_contents
, request
, callback
);
349 bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
350 content::BrowserContext
* browser_context
,
351 const GURL
& security_origin
,
352 content::MediaStreamType type
) {
353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
354 DCHECK(type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
||
355 type
== content::MEDIA_DEVICE_VIDEO_CAPTURE
);
357 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
358 const extensions::Extension
* extension
=
359 GetExtensionForOrigin(profile
, security_origin
);
361 if (extension
&& (extension
->is_platform_app() ||
362 IsMediaRequestWhitelistedForExtension(extension
))) {
363 return extension
->permissions_data()->HasAPIPermission(
364 type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
365 ? extensions::APIPermission::kAudioCapture
366 : extensions::APIPermission::kVideoCapture
);
369 if (CheckAllowAllMediaStreamContentForOrigin(profile
, security_origin
))
372 const char* policy_name
= type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
373 ? prefs::kAudioCaptureAllowed
374 : prefs::kVideoCaptureAllowed
;
375 const char* list_policy_name
= type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
376 ? prefs::kAudioCaptureAllowedUrls
377 : prefs::kVideoCaptureAllowedUrls
;
379 profile
, security_origin
, policy_name
, list_policy_name
) ==
384 // There's no secondary URL for these content types, hence duplicating
385 // |security_origin|.
386 if (profile
->GetHostContentSettingsMap()->GetContentSetting(
389 type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
390 ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC
391 : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA
,
392 NO_RESOURCE_IDENTIFIER
) == CONTENT_SETTING_ALLOW
) {
399 bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
400 content::WebContents
* web_contents
,
401 const GURL
& security_origin
,
402 content::MediaStreamType type
) {
403 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
404 DCHECK(type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
||
405 type
== content::MEDIA_DEVICE_VIDEO_CAPTURE
);
408 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
410 if (CheckAllowAllMediaStreamContentForOrigin(profile
, security_origin
))
413 const char* policy_name
= type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
414 ? prefs::kAudioCaptureAllowed
415 : prefs::kVideoCaptureAllowed
;
416 const char* list_policy_name
= type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
417 ? prefs::kAudioCaptureAllowedUrls
418 : prefs::kVideoCaptureAllowedUrls
;
420 profile
, security_origin
, policy_name
, list_policy_name
) ==
425 // There's no secondary URL for these content types, hence duplicating
426 // |security_origin|.
427 if (profile
->GetHostContentSettingsMap()->GetContentSetting(
430 type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
431 ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC
432 : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA
,
433 NO_RESOURCE_IDENTIFIER
) == CONTENT_SETTING_ALLOW
) {
440 #if defined(ENABLE_EXTENSIONS)
441 bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
442 content::WebContents
* web_contents
,
443 const GURL
& security_origin
,
444 content::MediaStreamType type
,
445 const extensions::Extension
* extension
) {
446 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
447 DCHECK(type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
||
448 type
== content::MEDIA_DEVICE_VIDEO_CAPTURE
);
450 if (extension
->is_platform_app() ||
451 IsMediaRequestWhitelistedForExtension(extension
)) {
452 return extension
->permissions_data()->HasAPIPermission(
453 type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
454 ? extensions::APIPermission::kAudioCapture
455 : extensions::APIPermission::kVideoCapture
);
458 return CheckMediaAccessPermission(web_contents
, security_origin
, type
);
462 void MediaCaptureDevicesDispatcher::ProcessDesktopCaptureAccessRequest(
463 content::WebContents
* web_contents
,
464 const content::MediaStreamRequest
& request
,
465 const content::MediaResponseCallback
& callback
,
466 const extensions::Extension
* extension
) {
467 content::MediaStreamDevices devices
;
468 scoped_ptr
<content::MediaStreamUI
> ui
;
470 if (request
.video_type
!= content::MEDIA_DESKTOP_VIDEO_CAPTURE
) {
471 callback
.Run(devices
, content::MEDIA_DEVICE_INVALID_STATE
, ui
.Pass());
475 // If the device id wasn't specified then this is a screen capture request
476 // (i.e. chooseDesktopMedia() API wasn't used to generate device id).
477 if (request
.requested_video_device_id
.empty()) {
478 ProcessScreenCaptureAccessRequest(
479 web_contents
, request
, callback
, extension
);
483 // The extension name that the stream is registered with.
484 std::string original_extension_name
;
485 // Resolve DesktopMediaID for the specified device id.
486 content::DesktopMediaID media_id
;
487 // TODO(miu): Replace "main RenderFrame" IDs with the request's actual
488 // RenderFrame IDs once the desktop capture extension API implementation is
489 // fixed. http://crbug.com/304341
490 content::WebContents
* const web_contents_for_stream
=
491 content::WebContents::FromRenderFrameHost(
492 content::RenderFrameHost::FromID(request
.render_process_id
,
493 request
.render_frame_id
));
494 content::RenderFrameHost
* const main_frame
= web_contents_for_stream
?
495 web_contents_for_stream
->GetMainFrame() : NULL
;
497 media_id
= GetDesktopStreamsRegistry()->RequestMediaForStreamId(
498 request
.requested_video_device_id
,
499 main_frame
->GetProcess()->GetID(),
500 main_frame
->GetRoutingID(),
501 request
.security_origin
,
502 &original_extension_name
);
505 // Received invalid device id.
506 if (media_id
.type
== content::DesktopMediaID::TYPE_NONE
) {
507 callback
.Run(devices
, content::MEDIA_DEVICE_INVALID_STATE
, ui
.Pass());
511 bool loopback_audio_supported
= false;
512 #if defined(USE_CRAS) || defined(OS_WIN)
513 // Currently loopback audio capture is supported only on Windows and ChromeOS.
514 loopback_audio_supported
= true;
517 // Audio is only supported for screen capture streams.
519 (media_id
.type
== content::DesktopMediaID::TYPE_SCREEN
&&
520 request
.audio_type
== content::MEDIA_LOOPBACK_AUDIO_CAPTURE
&&
521 loopback_audio_supported
);
523 ui
= GetDevicesForDesktopCapture(
524 devices
, media_id
, capture_audio
, true,
525 GetApplicationTitle(web_contents
, extension
),
526 base::UTF8ToUTF16(original_extension_name
));
528 callback
.Run(devices
, content::MEDIA_DEVICE_OK
, ui
.Pass());
531 void MediaCaptureDevicesDispatcher::ProcessScreenCaptureAccessRequest(
532 content::WebContents
* web_contents
,
533 const content::MediaStreamRequest
& request
,
534 const content::MediaResponseCallback
& callback
,
535 const extensions::Extension
* extension
) {
536 content::MediaStreamDevices devices
;
537 scoped_ptr
<content::MediaStreamUI
> ui
;
539 DCHECK_EQ(request
.video_type
, content::MEDIA_DESKTOP_VIDEO_CAPTURE
);
541 bool loopback_audio_supported
= false;
542 #if defined(USE_CRAS) || defined(OS_WIN)
543 // Currently loopback audio capture is supported only on Windows and ChromeOS.
544 loopback_audio_supported
= true;
547 const bool component_extension
=
548 extension
&& extension
->location() == extensions::Manifest::COMPONENT
;
550 const bool screen_capture_enabled
=
551 CommandLine::ForCurrentProcess()->HasSwitch(
552 switches::kEnableUserMediaScreenCapturing
) ||
553 IsOriginForCasting(request
.security_origin
) ||
554 IsBuiltInExtension(request
.security_origin
);
556 const bool origin_is_secure
=
557 request
.security_origin
.SchemeIsSecure() ||
558 request
.security_origin
.SchemeIs(extensions::kExtensionScheme
) ||
559 CommandLine::ForCurrentProcess()->HasSwitch(
560 switches::kAllowHttpScreenCapture
);
562 // If basic conditions (screen capturing is enabled and origin is secure)
563 // aren't fulfilled, we'll use "invalid state" as result. Otherwise, we set
564 // it after checking permission.
565 // TODO(grunell): It would be good to change this result for something else,
566 // probably a new one.
567 content::MediaStreamRequestResult result
=
568 content::MEDIA_DEVICE_INVALID_STATE
;
570 // Approve request only when the following conditions are met:
571 // 1. Screen capturing is enabled via command line switch or white-listed for
573 // 2. Request comes from a page with a secure origin or from an extension.
574 if (screen_capture_enabled
&& origin_is_secure
) {
575 // Get title of the calling application prior to showing the message box.
576 // chrome::ShowMessageBox() starts a nested message loop which may allow
577 // |web_contents| to be destroyed on the UI thread before the message box
578 // is closed. See http://crbug.com/326690.
579 base::string16 application_title
=
580 GetApplicationTitle(web_contents
, extension
);
581 #if !defined(OS_ANDROID)
582 gfx::NativeWindow parent_window
=
583 FindParentWindowForWebContents(web_contents
);
585 gfx::NativeWindow parent_window
= NULL
;
589 // For component extensions, bypass message box.
590 bool user_approved
= false;
591 if (!component_extension
) {
592 base::string16 application_name
= base::UTF8ToUTF16(
593 extension
? extension
->name() : request
.security_origin
.spec());
594 base::string16 confirmation_text
= l10n_util::GetStringFUTF16(
595 request
.audio_type
== content::MEDIA_NO_SERVICE
?
596 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT
:
597 IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT
,
599 chrome::MessageBoxResult result
= chrome::ShowMessageBox(
601 l10n_util::GetStringFUTF16(
602 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE
, application_name
),
604 chrome::MESSAGE_BOX_TYPE_QUESTION
);
605 user_approved
= (result
== chrome::MESSAGE_BOX_RESULT_YES
);
608 if (user_approved
|| component_extension
) {
609 content::DesktopMediaID screen_id
;
610 #if defined(OS_CHROMEOS)
611 screen_id
= content::DesktopMediaID::RegisterAuraWindow(
612 ash::Shell::GetInstance()->GetPrimaryRootWindow());
613 #else // defined(OS_CHROMEOS)
615 content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN
,
616 webrtc::kFullDesktopScreenId
);
617 #endif // !defined(OS_CHROMEOS)
620 (request
.audio_type
== content::MEDIA_LOOPBACK_AUDIO_CAPTURE
&&
621 loopback_audio_supported
);
623 // Unless we're being invoked from a component extension, register to
624 // display the notification for stream capture.
625 bool display_notification
= !component_extension
;
627 ui
= GetDevicesForDesktopCapture(devices
, screen_id
, capture_audio
,
628 display_notification
, application_title
,
630 DCHECK(!devices
.empty());
633 // The only case when devices can be empty is if the user has denied
635 result
= devices
.empty() ? content::MEDIA_DEVICE_PERMISSION_DENIED
636 : content::MEDIA_DEVICE_OK
;
639 callback
.Run(devices
, result
, ui
.Pass());
642 void MediaCaptureDevicesDispatcher::ProcessTabCaptureAccessRequest(
643 content::WebContents
* web_contents
,
644 const content::MediaStreamRequest
& request
,
645 const content::MediaResponseCallback
& callback
,
646 const extensions::Extension
* extension
) {
647 content::MediaStreamDevices devices
;
648 scoped_ptr
<content::MediaStreamUI
> ui
;
650 #if defined(ENABLE_EXTENSIONS)
652 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
653 extensions::TabCaptureRegistry
* tab_capture_registry
=
654 extensions::TabCaptureRegistry::Get(profile
);
655 if (!tab_capture_registry
) {
657 callback
.Run(devices
, content::MEDIA_DEVICE_INVALID_STATE
, ui
.Pass());
660 const bool tab_capture_allowed
= tab_capture_registry
->VerifyRequest(
661 request
.render_process_id
, request
.render_frame_id
, extension
->id());
663 if (request
.audio_type
== content::MEDIA_TAB_AUDIO_CAPTURE
&&
664 tab_capture_allowed
&&
665 extension
->permissions_data()->HasAPIPermission(
666 extensions::APIPermission::kTabCapture
)) {
667 devices
.push_back(content::MediaStreamDevice(
668 content::MEDIA_TAB_AUDIO_CAPTURE
, std::string(), std::string()));
671 if (request
.video_type
== content::MEDIA_TAB_VIDEO_CAPTURE
&&
672 tab_capture_allowed
&&
673 extension
->permissions_data()->HasAPIPermission(
674 extensions::APIPermission::kTabCapture
)) {
675 devices
.push_back(content::MediaStreamDevice(
676 content::MEDIA_TAB_VIDEO_CAPTURE
, std::string(), std::string()));
679 if (!devices
.empty()) {
680 ui
= media_stream_capture_indicator_
->RegisterMediaStream(
681 web_contents
, devices
);
685 devices
.empty() ? content::MEDIA_DEVICE_INVALID_STATE
:
686 content::MEDIA_DEVICE_OK
,
688 #else // defined(ENABLE_EXTENSIONS)
689 callback
.Run(devices
, content::MEDIA_DEVICE_TAB_CAPTURE_FAILURE
, ui
.Pass());
690 #endif // defined(ENABLE_EXTENSIONS)
693 void MediaCaptureDevicesDispatcher::
694 ProcessMediaAccessRequestFromPlatformAppOrExtension(
695 content::WebContents
* web_contents
,
696 const content::MediaStreamRequest
& request
,
697 const content::MediaResponseCallback
& callback
,
698 const extensions::Extension
* extension
) {
699 // TODO(vrk): This code is largely duplicated in
700 // MediaStreamDevicesController::Accept(). Move this code into a shared method
701 // between the two classes.
704 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
707 request
.audio_type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
&&
708 extension
->permissions_data()->HasAPIPermission(
709 extensions::APIPermission::kAudioCapture
) &&
710 GetDevicePolicy(profile
, extension
->url(),
711 prefs::kAudioCaptureAllowed
,
712 prefs::kAudioCaptureAllowedUrls
) != ALWAYS_DENY
;
714 request
.video_type
== content::MEDIA_DEVICE_VIDEO_CAPTURE
&&
715 extension
->permissions_data()->HasAPIPermission(
716 extensions::APIPermission::kVideoCapture
) &&
717 GetDevicePolicy(profile
, extension
->url(),
718 prefs::kVideoCaptureAllowed
,
719 prefs::kVideoCaptureAllowedUrls
) != ALWAYS_DENY
;
721 bool get_default_audio_device
= audio_allowed
;
722 bool get_default_video_device
= video_allowed
;
724 content::MediaStreamDevices devices
;
726 // Set an initial error result. If neither audio or video is allowed, we'll
727 // never try to get any device below but will just create |ui| and return an
728 // empty list with "invalid state" result. If at least one is allowed, we'll
729 // try to get device(s), and if failure, we want to return "no hardware"
731 // TODO(grunell): The invalid state result should be changed to a new denied
732 // result + a dcheck to ensure at least one of audio or video types is
734 content::MediaStreamRequestResult result
=
735 (audio_allowed
|| video_allowed
) ? content::MEDIA_DEVICE_NO_HARDWARE
736 : content::MEDIA_DEVICE_INVALID_STATE
;
738 // Get the exact audio or video device if an id is specified.
739 // We only set any error result here and before running the callback change
740 // it to OK if we have any device.
741 if (audio_allowed
&& !request
.requested_audio_device_id
.empty()) {
742 const content::MediaStreamDevice
* audio_device
=
743 GetRequestedAudioDevice(request
.requested_audio_device_id
);
745 devices
.push_back(*audio_device
);
746 get_default_audio_device
= false;
749 if (video_allowed
&& !request
.requested_video_device_id
.empty()) {
750 const content::MediaStreamDevice
* video_device
=
751 GetRequestedVideoDevice(request
.requested_video_device_id
);
753 devices
.push_back(*video_device
);
754 get_default_video_device
= false;
758 // If either or both audio and video devices were requested but not
759 // specified by id, get the default devices.
760 if (get_default_audio_device
|| get_default_video_device
) {
761 GetDefaultDevicesForProfile(profile
,
762 get_default_audio_device
,
763 get_default_video_device
,
767 scoped_ptr
<content::MediaStreamUI
> ui
;
768 if (!devices
.empty()) {
769 result
= content::MEDIA_DEVICE_OK
;
770 ui
= media_stream_capture_indicator_
->RegisterMediaStream(
771 web_contents
, devices
);
774 callback
.Run(devices
, result
, ui
.Pass());
777 void MediaCaptureDevicesDispatcher::ProcessRegularMediaAccessRequest(
778 content::WebContents
* web_contents
,
779 const content::MediaStreamRequest
& request
,
780 const content::MediaResponseCallback
& callback
) {
781 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
783 RequestsQueue
& queue
= pending_requests_
[web_contents
];
784 queue
.push_back(PendingAccessRequest(request
, callback
));
786 // If this is the only request then show the infobar.
787 if (queue
.size() == 1)
788 ProcessQueuedAccessRequest(web_contents
);
791 void MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest(
792 content::WebContents
* web_contents
) {
793 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
795 std::map
<content::WebContents
*, RequestsQueue
>::iterator it
=
796 pending_requests_
.find(web_contents
);
798 if (it
== pending_requests_
.end() || it
->second
.empty()) {
799 // Don't do anything if the tab was closed.
803 DCHECK(!it
->second
.empty());
805 if (PermissionBubbleManager::Enabled() ||
806 MediaStreamPermissionBubbleExperimentEnabled()) {
807 scoped_ptr
<MediaStreamDevicesController
> controller(
808 new MediaStreamDevicesController(web_contents
,
809 it
->second
.front().request
,
810 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse
,
811 base::Unretained(this), web_contents
)));
812 if (controller
->DismissInfoBarAndTakeActionOnSettings())
814 PermissionBubbleManager
* bubble_manager
=
815 PermissionBubbleManager::FromWebContents(web_contents
);
817 bubble_manager
->AddRequest(controller
.release());
821 // TODO(gbillock): delete this block and the MediaStreamInfoBarDelegate
822 // when we've transitioned to bubbles. (crbug/337458)
823 MediaStreamInfoBarDelegate::Create(
824 web_contents
, it
->second
.front().request
,
825 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse
,
826 base::Unretained(this), web_contents
));
829 void MediaCaptureDevicesDispatcher::OnAccessRequestResponse(
830 content::WebContents
* web_contents
,
831 const content::MediaStreamDevices
& devices
,
832 content::MediaStreamRequestResult result
,
833 scoped_ptr
<content::MediaStreamUI
> ui
) {
834 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
836 std::map
<content::WebContents
*, RequestsQueue
>::iterator it
=
837 pending_requests_
.find(web_contents
);
838 if (it
== pending_requests_
.end()) {
839 // WebContents has been destroyed. Don't need to do anything.
843 RequestsQueue
& queue(it
->second
);
847 content::MediaResponseCallback callback
= queue
.front().callback
;
850 if (!queue
.empty()) {
851 // Post a task to process next queued request. It has to be done
852 // asynchronously to make sure that calling infobar is not destroyed until
853 // after this function returns.
854 BrowserThread::PostTask(
855 BrowserThread::UI
, FROM_HERE
,
856 base::Bind(&MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest
,
857 base::Unretained(this), web_contents
));
860 callback
.Run(devices
, result
, ui
.Pass());
863 void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile(
867 content::MediaStreamDevices
* devices
) {
868 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
869 DCHECK(audio
|| video
);
871 PrefService
* prefs
= profile
->GetPrefs();
872 std::string default_device
;
874 default_device
= prefs
->GetString(prefs::kDefaultAudioCaptureDevice
);
875 const content::MediaStreamDevice
* device
=
876 GetRequestedAudioDevice(default_device
);
878 device
= GetFirstAvailableAudioDevice();
880 devices
->push_back(*device
);
884 default_device
= prefs
->GetString(prefs::kDefaultVideoCaptureDevice
);
885 const content::MediaStreamDevice
* device
=
886 GetRequestedVideoDevice(default_device
);
888 device
= GetFirstAvailableVideoDevice();
890 devices
->push_back(*device
);
894 const content::MediaStreamDevice
*
895 MediaCaptureDevicesDispatcher::GetRequestedAudioDevice(
896 const std::string
& requested_audio_device_id
) {
897 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
898 const content::MediaStreamDevices
& audio_devices
= GetAudioCaptureDevices();
899 const content::MediaStreamDevice
* const device
=
900 FindDeviceWithId(audio_devices
, requested_audio_device_id
);
904 const content::MediaStreamDevice
*
905 MediaCaptureDevicesDispatcher::GetFirstAvailableAudioDevice() {
906 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
907 const content::MediaStreamDevices
& audio_devices
= GetAudioCaptureDevices();
908 if (audio_devices
.empty())
910 return &(*audio_devices
.begin());
913 const content::MediaStreamDevice
*
914 MediaCaptureDevicesDispatcher::GetRequestedVideoDevice(
915 const std::string
& requested_video_device_id
) {
916 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
917 const content::MediaStreamDevices
& video_devices
= GetVideoCaptureDevices();
918 const content::MediaStreamDevice
* const device
=
919 FindDeviceWithId(video_devices
, requested_video_device_id
);
923 const content::MediaStreamDevice
*
924 MediaCaptureDevicesDispatcher::GetFirstAvailableVideoDevice() {
925 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
926 const content::MediaStreamDevices
& video_devices
= GetVideoCaptureDevices();
927 if (video_devices
.empty())
929 return &(*video_devices
.begin());
932 void MediaCaptureDevicesDispatcher::DisableDeviceEnumerationForTesting() {
933 is_device_enumeration_disabled_
= true;
936 scoped_refptr
<MediaStreamCaptureIndicator
>
937 MediaCaptureDevicesDispatcher::GetMediaStreamCaptureIndicator() {
938 return media_stream_capture_indicator_
;
941 DesktopStreamsRegistry
*
942 MediaCaptureDevicesDispatcher::GetDesktopStreamsRegistry() {
943 if (!desktop_streams_registry_
)
944 desktop_streams_registry_
.reset(new DesktopStreamsRegistry());
945 return desktop_streams_registry_
.get();
948 void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged() {
949 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
950 BrowserThread::PostTask(
951 BrowserThread::UI
, FROM_HERE
,
953 &MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread
,
954 base::Unretained(this)));
957 void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged() {
958 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
959 BrowserThread::PostTask(
960 BrowserThread::UI
, FROM_HERE
,
962 &MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread
,
963 base::Unretained(this)));
966 void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged(
967 int render_process_id
,
970 const GURL
& security_origin
,
971 content::MediaStreamType stream_type
,
972 content::MediaRequestState state
) {
973 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
974 BrowserThread::PostTask(
975 BrowserThread::UI
, FROM_HERE
,
977 &MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread
,
978 base::Unretained(this), render_process_id
, render_frame_id
,
979 page_request_id
, security_origin
, stream_type
, state
));
982 void MediaCaptureDevicesDispatcher::OnCreatingAudioStream(
983 int render_process_id
,
984 int render_frame_id
) {
985 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
986 BrowserThread::PostTask(
987 BrowserThread::UI
, FROM_HERE
,
989 &MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread
,
990 base::Unretained(this), render_process_id
, render_frame_id
));
993 void MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread() {
994 MediaStreamDevices devices
= GetAudioCaptureDevices();
995 FOR_EACH_OBSERVER(Observer
, observers_
,
996 OnUpdateAudioDevices(devices
));
999 void MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread() {
1000 MediaStreamDevices devices
= GetVideoCaptureDevices();
1001 FOR_EACH_OBSERVER(Observer
, observers_
,
1002 OnUpdateVideoDevices(devices
));
1005 void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread(
1006 int render_process_id
,
1007 int render_frame_id
,
1008 int page_request_id
,
1009 const GURL
& security_origin
,
1010 content::MediaStreamType stream_type
,
1011 content::MediaRequestState state
) {
1012 // Track desktop capture sessions. Tracking is necessary to avoid unbalanced
1013 // session counts since not all requests will reach MEDIA_REQUEST_STATE_DONE,
1014 // but they will all reach MEDIA_REQUEST_STATE_CLOSING.
1015 if (stream_type
== content::MEDIA_DESKTOP_VIDEO_CAPTURE
) {
1016 if (state
== content::MEDIA_REQUEST_STATE_DONE
) {
1017 DesktopCaptureSession session
= { render_process_id
, render_frame_id
,
1019 desktop_capture_sessions_
.push_back(session
);
1020 } else if (state
== content::MEDIA_REQUEST_STATE_CLOSING
) {
1021 for (DesktopCaptureSessions::iterator it
=
1022 desktop_capture_sessions_
.begin();
1023 it
!= desktop_capture_sessions_
.end();
1025 if (it
->render_process_id
== render_process_id
&&
1026 it
->render_frame_id
== render_frame_id
&&
1027 it
->page_request_id
== page_request_id
) {
1028 desktop_capture_sessions_
.erase(it
);
1035 // Cancel the request.
1036 if (state
== content::MEDIA_REQUEST_STATE_CLOSING
) {
1038 for (RequestsQueues::iterator rqs_it
= pending_requests_
.begin();
1039 rqs_it
!= pending_requests_
.end(); ++rqs_it
) {
1040 RequestsQueue
& queue
= rqs_it
->second
;
1041 for (RequestsQueue::iterator it
= queue
.begin();
1042 it
!= queue
.end(); ++it
) {
1043 if (it
->request
.render_process_id
== render_process_id
&&
1044 it
->request
.render_frame_id
== render_frame_id
&&
1045 it
->request
.page_request_id
== page_request_id
) {
1056 #if defined(OS_CHROMEOS)
1057 if (IsOriginForCasting(security_origin
) && IsVideoMediaType(stream_type
)) {
1058 // Notify ash that casting state has changed.
1059 if (state
== content::MEDIA_REQUEST_STATE_DONE
) {
1060 ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(true);
1061 } else if (state
== content::MEDIA_REQUEST_STATE_CLOSING
) {
1062 ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(false);
1067 FOR_EACH_OBSERVER(Observer
, observers_
,
1068 OnRequestUpdate(render_process_id
,
1074 void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread(
1075 int render_process_id
,
1076 int render_frame_id
) {
1077 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1078 FOR_EACH_OBSERVER(Observer
, observers_
,
1079 OnCreatingAudioStream(render_process_id
, render_frame_id
));
1082 bool MediaCaptureDevicesDispatcher::IsDesktopCaptureInProgress() {
1083 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
1084 return desktop_capture_sessions_
.size() > 0;
1087 void MediaCaptureDevicesDispatcher::SetTestAudioCaptureDevices(
1088 const MediaStreamDevices
& devices
) {
1089 test_audio_devices_
= devices
;
1092 void MediaCaptureDevicesDispatcher::SetTestVideoCaptureDevices(
1093 const MediaStreamDevices
& devices
) {
1094 test_video_devices_
= devices
;