Adds method to provide custom AudioManager at runtime.
[chromium-blink-merge.git] / chrome / browser / media / media_capture_devices_dispatcher.cc
blobec8043d89d50ea3d6b82fa81af7ccbbd3aacacf2
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"
61 #endif
63 using content::BrowserThread;
64 using content::MediaCaptureDevices;
65 using content::MediaStreamDevices;
67 namespace {
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")
74 return true;
76 return false;
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) {
86 return &(*iter);
89 return NULL;
92 #if defined(ENABLE_EXTENSIONS)
93 // This is a short-term solution to grant camera and/or microphone access to
94 // extensions:
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) {
115 return
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.
123 return
124 // Dev
125 origin.spec() == "chrome-extension://enhhojjnijigcajfphajepfemndkmdlo/" ||
126 // Canary
127 origin.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" ||
128 // Beta (internal)
129 origin.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" ||
130 // Google Cast Beta
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());
145 // crbug.com/446688
146 return hex_hash == "4F25792AF1AA7483936DE29C07806F203C7170A0" ||
147 hex_hash == "BD8781D757D830FC2E85470A1B6E8A718B7EE0D9" ||
148 hex_hash == "4AC2B6C63C6480D150DFDA13E4A5956EB1D0DDBB" ||
149 hex_hash == "81986D4F846CEDDDB962643FA501D1780DD441BB";
150 #else
151 return false;
152 #endif // defined(OS_CHROMEOS)
154 #endif // defined(ENABLE_EXTENSIONS)
156 // Helper to get title of the calling application shown in the screen capture
157 // notification.
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
161 // web.
162 std::string title;
163 #if defined(ENABLE_EXTENSIONS)
164 if (extension) {
165 title = extension->name();
166 return base::UTF8ToUTF16(title);
168 #endif
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,
181 bool capture_audio,
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"));
191 if (capture_audio) {
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,
203 application_title));
204 } else {
205 ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
206 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
207 registered_extension_name,
208 application_title));
212 return ui.Pass();
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 =
227 window_list.begin();
228 iter != window_list.end(); ++iter) {
229 if ((*iter)->web_contents() == web_contents)
230 return (*iter)->GetNativeWindow();
233 return NULL;
235 #endif
237 #if defined(ENABLE_EXTENSIONS)
238 const extensions::Extension* GetExtensionForOrigin(
239 Profile* profile,
240 const GURL& security_origin) {
241 if (!security_origin.SchemeIs(extensions::kExtensionScheme))
242 return NULL;
244 const extensions::Extension* extension =
245 extensions::ExtensionRegistry::Get(profile)->enabled_extensions().GetByID(
246 security_origin.host());
247 DCHECK(extension);
248 return extension;
250 #endif
252 } // namespace
254 MediaCaptureDevicesDispatcher::PendingAccessRequest::PendingAccessRequest(
255 const content::MediaStreamRequest& request,
256 const content::MediaResponseCallback& callback)
257 : request(request),
258 callback(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);
286 #endif
289 MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {}
291 void MediaCaptureDevicesDispatcher::RegisterProfilePrefs(
292 user_prefs::PrefRegistrySyncable* registry) {
293 registry->RegisterStringPref(
294 prefs::kDefaultAudioCaptureDevice,
295 std::string(),
296 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
297 registry->RegisterStringPref(
298 prefs::kDefaultVideoCaptureDevice,
299 std::string(),
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(
333 int type,
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);
359 } else {
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);
368 return;
370 #endif
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);
395 #endif
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)) {
404 return true;
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;
413 if (GetDevicePolicy(
414 profile, security_origin, policy_name, list_policy_name) ==
415 ALWAYS_ALLOW) {
416 return true;
419 // There's no secondary URL for these content types, hence duplicating
420 // |security_origin|.
421 if (profile->GetHostContentSettingsMap()->GetContentSetting(
422 security_origin,
423 security_origin,
424 contentSettingsType,
425 content_settings::ResourceIdentifier()) == CONTENT_SETTING_ALLOW) {
426 return true;
429 return false;
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);
440 Profile* profile =
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)) {
450 return true;
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;
459 if (GetDevicePolicy(
460 profile, security_origin, policy_name, list_policy_name) ==
461 ALWAYS_ALLOW) {
462 return true;
465 // There's no secondary URL for these content types, hence duplicating
466 // |security_origin|.
467 if (profile->GetHostContentSettingsMap()->GetContentSetting(
468 security_origin,
469 security_origin,
470 contentSettingsType,
471 content_settings::ResourceIdentifier()) == CONTENT_SETTING_ALLOW) {
472 return true;
475 return false;
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);
498 #endif
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());
510 return;
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);
518 return;
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;
534 if (main_frame) {
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());
546 return;
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;
553 #endif
555 // Audio is only supported for screen capture streams.
556 bool capture_audio =
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;
583 #endif
585 bool component_extension = false;
586 #if defined(ENABLE_EXTENSIONS)
587 component_extension =
588 extension && extension->location() == extensions::Manifest::COMPONENT;
589 #endif
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);
599 #endif
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
617 // the given origin.
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);
629 #else
630 gfx::NativeWindow parent_window = NULL;
631 #endif
632 web_contents = NULL;
634 bool whitelisted_extension = false;
635 #if defined(ENABLE_EXTENSIONS)
636 whitelisted_extension = IsExtensionWhitelistedForScreenCapture(
637 extension);
638 #endif
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)
646 if (extension)
647 application_name = base::UTF8ToUTF16(extension->name());
648 #endif
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,
653 application_name);
654 chrome::MessageBoxResult result = chrome::ShowMessageBox(
655 parent_window,
656 l10n_util::GetStringFUTF16(
657 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name),
658 confirmation_text,
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)
669 screen_id =
670 content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
671 webrtc::kFullDesktopScreenId);
672 #endif // !defined(OS_CHROMEOS)
674 bool capture_audio =
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,
684 application_title);
685 DCHECK(!devices.empty());
688 // The only case when devices can be empty is if the user has denied
689 // permission.
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)
706 Profile* profile =
707 Profile::FromBrowserContext(web_contents->GetBrowserContext());
708 extensions::TabCaptureRegistry* tab_capture_registry =
709 extensions::TabCaptureRegistry::Get(profile);
710 if (!tab_capture_registry) {
711 NOTREACHED();
712 callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass());
713 return;
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);
738 callback.Run(
739 devices,
740 devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE :
741 content::MEDIA_DEVICE_OK,
742 ui.Pass());
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.
759 Profile* profile =
760 Profile::FromBrowserContext(web_contents->GetBrowserContext());
762 bool audio_allowed =
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;
769 bool video_allowed =
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"
786 // result.
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
789 // capture.
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);
800 if (audio_device) {
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);
808 if (video_device) {
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,
820 &devices);
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());
832 #endif
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.
857 return;
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())
870 return;
871 PermissionBubbleManager* bubble_manager =
872 PermissionBubbleManager::FromWebContents(web_contents);
873 if (bubble_manager)
874 bubble_manager->AddRequest(controller.release());
875 return;
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.
897 return;
900 RequestsQueue& queue(it->second);
901 if (queue.empty())
902 return;
904 content::MediaResponseCallback callback = queue.front().callback;
905 queue.pop_front();
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(
921 Profile* profile,
922 bool audio,
923 bool video,
924 content::MediaStreamDevices* devices) {
925 DCHECK_CURRENTLY_ON(BrowserThread::UI);
926 DCHECK(audio || video);
928 PrefService* prefs = profile->GetPrefs();
929 std::string default_device;
930 if (audio) {
931 default_device = prefs->GetString(prefs::kDefaultAudioCaptureDevice);
932 const content::MediaStreamDevice* device =
933 GetRequestedAudioDevice(default_device);
934 if (!device)
935 device = GetFirstAvailableAudioDevice();
936 if (device)
937 devices->push_back(*device);
940 if (video) {
941 default_device = prefs->GetString(prefs::kDefaultVideoCaptureDevice);
942 const content::MediaStreamDevice* device =
943 GetRequestedVideoDevice(default_device);
944 if (!device)
945 device = GetFirstAvailableVideoDevice();
946 if (device)
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);
958 return device;
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())
966 return NULL;
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);
977 return device;
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())
985 return NULL;
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,
1009 base::Bind(
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,
1018 base::Bind(
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,
1033 base::Bind(
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,
1045 base::Bind(
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,
1075 page_request_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();
1081 ++it) {
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);
1086 break;
1092 // Cancel the request.
1093 if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
1094 bool found = false;
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) {
1103 queue.erase(it);
1104 found = true;
1105 break;
1108 if (found)
1109 break;
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);
1122 #endif
1124 FOR_EACH_OBSERVER(Observer, observers_,
1125 OnRequestUpdate(render_process_id,
1126 render_frame_id,
1127 stream_type,
1128 state));
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;