Fix sorting issues in chrome_browser.gypi restructuring
[chromium-blink-merge.git] / chrome / browser / media / media_capture_devices_dispatcher.cc
blobcc776b30b5a8543c61c6a8e61ab190ae201e47a7
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/content_settings_provider.h"
32 #include "components/content_settings/core/browser/host_content_settings_map.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 "media/audio/audio_manager_base.h"
46 #include "media/base/media_switches.h"
47 #include "net/base/net_util.h"
48 #include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
49 #include "ui/base/l10n/l10n_util.h"
51 #if defined(OS_CHROMEOS)
52 #include "ash/shell.h"
53 #endif // defined(OS_CHROMEOS)
55 #if defined(ENABLE_EXTENSIONS)
56 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
57 #include "extensions/browser/app_window/app_window.h"
58 #include "extensions/browser/app_window/app_window_registry.h"
59 #include "extensions/browser/extension_registry.h"
60 #include "extensions/common/extension.h"
61 #include "extensions/common/permissions/permissions_data.h"
62 #endif
64 using content::BrowserThread;
65 using content::MediaCaptureDevices;
66 using content::MediaStreamDevices;
68 namespace {
70 // A finch experiment to enable the permission bubble for media requests only.
71 bool MediaStreamPermissionBubbleExperimentEnabled() {
72 const std::string group =
73 base::FieldTrialList::FindFullName("MediaStreamPermissionBubble");
74 if (group == "enabled")
75 return true;
77 return false;
80 // Finds a device in |devices| that has |device_id|, or NULL if not found.
81 const content::MediaStreamDevice* FindDeviceWithId(
82 const content::MediaStreamDevices& devices,
83 const std::string& device_id) {
84 content::MediaStreamDevices::const_iterator iter = devices.begin();
85 for (; iter != devices.end(); ++iter) {
86 if (iter->id == device_id) {
87 return &(*iter);
90 return NULL;
93 #if defined(ENABLE_EXTENSIONS)
94 // This is a short-term solution to grant camera and/or microphone access to
95 // extensions:
96 // 1. Virtual keyboard extension.
97 // 2. Google Voice Search Hotword extension.
98 // 3. Flutter gesture recognition extension.
99 // 4. TODO(smus): Airbender experiment 1.
100 // 5. TODO(smus): Airbender experiment 2.
101 // 6. Hotwording 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() == "bepbmhgboaologfdajaanbcjmnhjmhfn" ||
107 extension->id() == "jokbpnebhdcladagohdnfgjcpejggllo" ||
108 extension->id() == "clffjmdilanldobdnedchkdbofoimcgb" ||
109 extension->id() == "nnckehldicaciogcbchegobnafnjkcne" ||
110 extension->id() == "nbpagnldghgfoolbancepceaanlmhfmd";
113 bool IsBuiltInExtension(const GURL& origin) {
114 return
115 // Feedback Extension.
116 origin.spec() == "chrome-extension://gfdkimpbcpahaombhbimeihdjnejgicl/";
119 // Returns true of the security origin is associated with casting.
120 bool IsOriginForCasting(const GURL& origin) {
121 // Whitelisted tab casting extensions.
122 return
123 // Dev
124 origin.spec() == "chrome-extension://enhhojjnijigcajfphajepfemndkmdlo/" ||
125 // Canary
126 origin.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" ||
127 // Beta (internal)
128 origin.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" ||
129 // Google Cast Beta
130 origin.spec() == "chrome-extension://dliochdbjfkdbacpmhlcpmleaejidimm/" ||
131 // Google Cast Stable
132 origin.spec() == "chrome-extension://boadgeojelhgndaghljhdicfkmllpafd/";
134 #endif // defined(ENABLE_EXTENSIONS)
136 // Helper to get title of the calling application shown in the screen capture
137 // notification.
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
141 // web.
142 std::string title;
143 #if defined(ENABLE_EXTENSIONS)
144 if (extension) {
145 title = extension->name();
146 return base::UTF8ToUTF16(title);
148 #endif
149 GURL url = web_contents->GetURL();
150 title = url.SchemeIsSecure() ? net::GetHostAndOptionalPort(url)
151 : url.GetOrigin().spec();
152 return base::UTF8ToUTF16(title);
155 // Helper to get list of media stream devices for desktop capture in |devices|.
156 // Registers to display notification if |display_notification| is true.
157 // Returns an instance of MediaStreamUI to be passed to content layer.
158 scoped_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture(
159 content::MediaStreamDevices* devices,
160 content::DesktopMediaID media_id,
161 bool capture_audio,
162 bool display_notification,
163 const base::string16& application_title,
164 const base::string16& registered_extension_name) {
165 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
166 scoped_ptr<content::MediaStreamUI> ui;
168 // Add selected desktop source to the list.
169 devices->push_back(content::MediaStreamDevice(
170 content::MEDIA_DESKTOP_VIDEO_CAPTURE, media_id.ToString(), "Screen"));
171 if (capture_audio) {
172 // Use the special loopback device ID for system audio capture.
173 devices->push_back(content::MediaStreamDevice(
174 content::MEDIA_DESKTOP_AUDIO_CAPTURE,
175 media::AudioManagerBase::kLoopbackInputDeviceId, "System Audio"));
178 // If required, register to display the notification for stream capture.
179 if (display_notification) {
180 if (application_title == registered_extension_name) {
181 ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
182 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT,
183 application_title));
184 } else {
185 ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
186 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
187 registered_extension_name,
188 application_title));
192 return ui.Pass();
195 #if !defined(OS_ANDROID)
196 // Find browser or app window from a given |web_contents|.
197 gfx::NativeWindow FindParentWindowForWebContents(
198 content::WebContents* web_contents) {
199 Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
200 if (browser && browser->window())
201 return browser->window()->GetNativeWindow();
203 const extensions::AppWindowRegistry::AppWindowList& window_list =
204 extensions::AppWindowRegistry::Get(
205 web_contents->GetBrowserContext())->app_windows();
206 for (extensions::AppWindowRegistry::AppWindowList::const_iterator iter =
207 window_list.begin();
208 iter != window_list.end(); ++iter) {
209 if ((*iter)->web_contents() == web_contents)
210 return (*iter)->GetNativeWindow();
213 return NULL;
215 #endif
217 #if defined(ENABLE_EXTENSIONS)
218 const extensions::Extension* GetExtensionForOrigin(
219 Profile* profile,
220 const GURL& security_origin) {
221 if (!security_origin.SchemeIs(extensions::kExtensionScheme))
222 return NULL;
224 const extensions::Extension* extension =
225 extensions::ExtensionRegistry::Get(profile)->enabled_extensions().GetByID(
226 security_origin.host());
227 DCHECK(extension);
228 return extension;
230 #endif
232 } // namespace
234 MediaCaptureDevicesDispatcher::PendingAccessRequest::PendingAccessRequest(
235 const content::MediaStreamRequest& request,
236 const content::MediaResponseCallback& callback)
237 : request(request),
238 callback(callback) {
241 MediaCaptureDevicesDispatcher::PendingAccessRequest::~PendingAccessRequest() {}
243 MediaCaptureDevicesDispatcher* MediaCaptureDevicesDispatcher::GetInstance() {
244 return Singleton<MediaCaptureDevicesDispatcher>::get();
247 MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher()
248 : is_device_enumeration_disabled_(false),
249 media_stream_capture_indicator_(new MediaStreamCaptureIndicator()) {
250 // MediaCaptureDevicesDispatcher is a singleton. It should be created on
251 // UI thread. Otherwise, it will not receive
252 // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in
253 // possible use after free.
254 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
255 notifications_registrar_.Add(
256 this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
257 content::NotificationService::AllSources());
259 #if defined(OS_MACOSX)
260 // AVFoundation is used for video/audio device monitoring and video capture.
261 if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kForceQTKit)) {
262 CommandLine::ForCurrentProcess()->AppendSwitch(
263 switches::kEnableAVFoundation);
265 #endif
268 MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {}
270 void MediaCaptureDevicesDispatcher::RegisterProfilePrefs(
271 user_prefs::PrefRegistrySyncable* registry) {
272 registry->RegisterStringPref(
273 prefs::kDefaultAudioCaptureDevice,
274 std::string(),
275 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
276 registry->RegisterStringPref(
277 prefs::kDefaultVideoCaptureDevice,
278 std::string(),
279 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
282 void MediaCaptureDevicesDispatcher::AddObserver(Observer* observer) {
283 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
284 if (!observers_.HasObserver(observer))
285 observers_.AddObserver(observer);
288 void MediaCaptureDevicesDispatcher::RemoveObserver(Observer* observer) {
289 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
290 observers_.RemoveObserver(observer);
293 const MediaStreamDevices&
294 MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() {
295 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
296 if (is_device_enumeration_disabled_ || !test_audio_devices_.empty())
297 return test_audio_devices_;
299 return MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices();
302 const MediaStreamDevices&
303 MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() {
304 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
305 if (is_device_enumeration_disabled_ || !test_video_devices_.empty())
306 return test_video_devices_;
308 return MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices();
311 void MediaCaptureDevicesDispatcher::Observe(
312 int type,
313 const content::NotificationSource& source,
314 const content::NotificationDetails& details) {
315 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
316 if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) {
317 content::WebContents* web_contents =
318 content::Source<content::WebContents>(source).ptr();
319 pending_requests_.erase(web_contents);
323 void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequest(
324 content::WebContents* web_contents,
325 const content::MediaStreamRequest& request,
326 const content::MediaResponseCallback& callback,
327 const extensions::Extension* extension) {
328 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
330 if (request.video_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE ||
331 request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE) {
332 ProcessDesktopCaptureAccessRequest(
333 web_contents, request, callback, extension);
334 } else if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE ||
335 request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE) {
336 ProcessTabCaptureAccessRequest(
337 web_contents, request, callback, extension);
338 } else {
339 #if defined(ENABLE_EXTENSIONS)
340 bool is_whitelisted =
341 extension && (extension->is_platform_app() ||
342 IsMediaRequestWhitelistedForExtension(extension));
343 if (is_whitelisted) {
344 // For extensions access is approved based on extension permissions.
345 ProcessMediaAccessRequestFromPlatformAppOrExtension(
346 web_contents, request, callback, extension);
347 return;
349 #endif
350 ProcessRegularMediaAccessRequest(web_contents, request, callback);
354 bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
355 content::BrowserContext* browser_context,
356 const GURL& security_origin,
357 content::MediaStreamType type) {
358 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
359 DCHECK(type == content::MEDIA_DEVICE_AUDIO_CAPTURE ||
360 type == content::MEDIA_DEVICE_VIDEO_CAPTURE);
362 Profile* profile = Profile::FromBrowserContext(browser_context);
363 #if defined(ENABLE_EXTENSIONS)
364 const extensions::Extension* extension =
365 GetExtensionForOrigin(profile, security_origin);
367 if (extension && (extension->is_platform_app() ||
368 IsMediaRequestWhitelistedForExtension(extension))) {
369 return extension->permissions_data()->HasAPIPermission(
370 type == content::MEDIA_DEVICE_AUDIO_CAPTURE
371 ? extensions::APIPermission::kAudioCapture
372 : extensions::APIPermission::kVideoCapture);
374 #endif
376 if (CheckAllowAllMediaStreamContentForOrigin(profile, security_origin))
377 return true;
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;
385 if (GetDevicePolicy(
386 profile, security_origin, policy_name, list_policy_name) ==
387 ALWAYS_ALLOW) {
388 return true;
391 // There's no secondary URL for these content types, hence duplicating
392 // |security_origin|.
393 if (profile->GetHostContentSettingsMap()->GetContentSetting(
394 security_origin,
395 security_origin,
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) {
400 return true;
403 return false;
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);
414 Profile* profile =
415 Profile::FromBrowserContext(web_contents->GetBrowserContext());
417 if (CheckAllowAllMediaStreamContentForOrigin(profile, security_origin))
418 return true;
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;
426 if (GetDevicePolicy(
427 profile, security_origin, policy_name, list_policy_name) ==
428 ALWAYS_ALLOW) {
429 return true;
432 // There's no secondary URL for these content types, hence duplicating
433 // |security_origin|.
434 if (profile->GetHostContentSettingsMap()->GetContentSetting(
435 security_origin,
436 security_origin,
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) {
441 return true;
444 return false;
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);
467 #endif
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());
479 return;
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);
487 return;
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;
503 if (main_frame) {
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());
515 return;
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;
522 #endif
524 // Audio is only supported for screen capture streams.
525 bool capture_audio =
526 (media_id.type == content::DesktopMediaID::TYPE_SCREEN &&
527 request.audio_type == content::MEDIA_DESKTOP_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;
552 #endif
554 bool component_extension = false;
555 #if defined(ENABLE_EXTENSIONS)
556 component_extension =
557 extension && extension->location() == extensions::Manifest::COMPONENT;
558 #endif
560 bool screen_capture_enabled =
561 base::CommandLine::ForCurrentProcess()->HasSwitch(
562 switches::kEnableUserMediaScreenCapturing);
563 #if defined(ENABLE_EXTENSIONS)
564 screen_capture_enabled |=
565 IsOriginForCasting(request.security_origin) ||
566 IsBuiltInExtension(request.security_origin);
567 #endif
569 const bool origin_is_secure =
570 request.security_origin.SchemeIsSecure() ||
571 request.security_origin.SchemeIs(extensions::kExtensionScheme) ||
572 CommandLine::ForCurrentProcess()->HasSwitch(
573 switches::kAllowHttpScreenCapture);
575 // If basic conditions (screen capturing is enabled and origin is secure)
576 // aren't fulfilled, we'll use "invalid state" as result. Otherwise, we set
577 // it after checking permission.
578 // TODO(grunell): It would be good to change this result for something else,
579 // probably a new one.
580 content::MediaStreamRequestResult result =
581 content::MEDIA_DEVICE_INVALID_STATE;
583 // Approve request only when the following conditions are met:
584 // 1. Screen capturing is enabled via command line switch or white-listed for
585 // the given origin.
586 // 2. Request comes from a page with a secure origin or from an extension.
587 if (screen_capture_enabled && origin_is_secure) {
588 // Get title of the calling application prior to showing the message box.
589 // chrome::ShowMessageBox() starts a nested message loop which may allow
590 // |web_contents| to be destroyed on the UI thread before the message box
591 // is closed. See http://crbug.com/326690.
592 base::string16 application_title =
593 GetApplicationTitle(web_contents, extension);
594 #if !defined(OS_ANDROID)
595 gfx::NativeWindow parent_window =
596 FindParentWindowForWebContents(web_contents);
597 #else
598 gfx::NativeWindow parent_window = NULL;
599 #endif
600 web_contents = NULL;
602 // For component extensions, bypass message box.
603 bool user_approved = false;
604 if (!component_extension) {
605 base::string16 application_name =
606 base::UTF8ToUTF16(request.security_origin.spec());
607 #if defined(ENABLE_EXTENSIONS)
608 if (extension)
609 application_name = base::UTF8ToUTF16(extension->name());
610 #endif
611 base::string16 confirmation_text = l10n_util::GetStringFUTF16(
612 request.audio_type == content::MEDIA_NO_SERVICE ?
613 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT :
614 IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT,
615 application_name);
616 chrome::MessageBoxResult result = chrome::ShowMessageBox(
617 parent_window,
618 l10n_util::GetStringFUTF16(
619 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name),
620 confirmation_text,
621 chrome::MESSAGE_BOX_TYPE_QUESTION);
622 user_approved = (result == chrome::MESSAGE_BOX_RESULT_YES);
625 if (user_approved || component_extension) {
626 content::DesktopMediaID screen_id;
627 #if defined(OS_CHROMEOS)
628 screen_id = content::DesktopMediaID::RegisterAuraWindow(
629 ash::Shell::GetInstance()->GetPrimaryRootWindow());
630 #else // defined(OS_CHROMEOS)
631 screen_id =
632 content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
633 webrtc::kFullDesktopScreenId);
634 #endif // !defined(OS_CHROMEOS)
636 bool capture_audio =
637 (request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE &&
638 loopback_audio_supported);
640 // Unless we're being invoked from a component extension, register to
641 // display the notification for stream capture.
642 bool display_notification = !component_extension;
644 ui = GetDevicesForDesktopCapture(&devices, screen_id, capture_audio,
645 display_notification, application_title,
646 application_title);
647 DCHECK(!devices.empty());
650 // The only case when devices can be empty is if the user has denied
651 // permission.
652 result = devices.empty() ? content::MEDIA_DEVICE_PERMISSION_DENIED
653 : content::MEDIA_DEVICE_OK;
656 callback.Run(devices, result, ui.Pass());
659 void MediaCaptureDevicesDispatcher::ProcessTabCaptureAccessRequest(
660 content::WebContents* web_contents,
661 const content::MediaStreamRequest& request,
662 const content::MediaResponseCallback& callback,
663 const extensions::Extension* extension) {
664 content::MediaStreamDevices devices;
665 scoped_ptr<content::MediaStreamUI> ui;
667 #if defined(ENABLE_EXTENSIONS)
668 Profile* profile =
669 Profile::FromBrowserContext(web_contents->GetBrowserContext());
670 extensions::TabCaptureRegistry* tab_capture_registry =
671 extensions::TabCaptureRegistry::Get(profile);
672 if (!tab_capture_registry) {
673 NOTREACHED();
674 callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass());
675 return;
677 const bool tab_capture_allowed = tab_capture_registry->VerifyRequest(
678 request.render_process_id, request.render_frame_id, extension->id());
680 if (request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE &&
681 tab_capture_allowed &&
682 extension->permissions_data()->HasAPIPermission(
683 extensions::APIPermission::kTabCapture)) {
684 devices.push_back(content::MediaStreamDevice(
685 content::MEDIA_TAB_AUDIO_CAPTURE, std::string(), std::string()));
688 if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE &&
689 tab_capture_allowed &&
690 extension->permissions_data()->HasAPIPermission(
691 extensions::APIPermission::kTabCapture)) {
692 devices.push_back(content::MediaStreamDevice(
693 content::MEDIA_TAB_VIDEO_CAPTURE, std::string(), std::string()));
696 if (!devices.empty()) {
697 ui = media_stream_capture_indicator_->RegisterMediaStream(
698 web_contents, devices);
700 callback.Run(
701 devices,
702 devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE :
703 content::MEDIA_DEVICE_OK,
704 ui.Pass());
705 #else // defined(ENABLE_EXTENSIONS)
706 callback.Run(devices, content::MEDIA_DEVICE_TAB_CAPTURE_FAILURE, ui.Pass());
707 #endif // defined(ENABLE_EXTENSIONS)
710 #if defined(ENABLE_EXTENSIONS)
711 void MediaCaptureDevicesDispatcher::
712 ProcessMediaAccessRequestFromPlatformAppOrExtension(
713 content::WebContents* web_contents,
714 const content::MediaStreamRequest& request,
715 const content::MediaResponseCallback& callback,
716 const extensions::Extension* extension) {
717 // TODO(vrk): This code is largely duplicated in
718 // MediaStreamDevicesController::Accept(). Move this code into a shared method
719 // between the two classes.
721 Profile* profile =
722 Profile::FromBrowserContext(web_contents->GetBrowserContext());
724 bool audio_allowed =
725 request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE &&
726 extension->permissions_data()->HasAPIPermission(
727 extensions::APIPermission::kAudioCapture) &&
728 GetDevicePolicy(profile, extension->url(),
729 prefs::kAudioCaptureAllowed,
730 prefs::kAudioCaptureAllowedUrls) != ALWAYS_DENY;
731 bool video_allowed =
732 request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE &&
733 extension->permissions_data()->HasAPIPermission(
734 extensions::APIPermission::kVideoCapture) &&
735 GetDevicePolicy(profile, extension->url(),
736 prefs::kVideoCaptureAllowed,
737 prefs::kVideoCaptureAllowedUrls) != ALWAYS_DENY;
739 bool get_default_audio_device = audio_allowed;
740 bool get_default_video_device = video_allowed;
742 content::MediaStreamDevices devices;
744 // Set an initial error result. If neither audio or video is allowed, we'll
745 // never try to get any device below but will just create |ui| and return an
746 // empty list with "invalid state" result. If at least one is allowed, we'll
747 // try to get device(s), and if failure, we want to return "no hardware"
748 // result.
749 // TODO(grunell): The invalid state result should be changed to a new denied
750 // result + a dcheck to ensure at least one of audio or video types is
751 // capture.
752 content::MediaStreamRequestResult result =
753 (audio_allowed || video_allowed) ? content::MEDIA_DEVICE_NO_HARDWARE
754 : content::MEDIA_DEVICE_INVALID_STATE;
756 // Get the exact audio or video device if an id is specified.
757 // We only set any error result here and before running the callback change
758 // it to OK if we have any device.
759 if (audio_allowed && !request.requested_audio_device_id.empty()) {
760 const content::MediaStreamDevice* audio_device =
761 GetRequestedAudioDevice(request.requested_audio_device_id);
762 if (audio_device) {
763 devices.push_back(*audio_device);
764 get_default_audio_device = false;
767 if (video_allowed && !request.requested_video_device_id.empty()) {
768 const content::MediaStreamDevice* video_device =
769 GetRequestedVideoDevice(request.requested_video_device_id);
770 if (video_device) {
771 devices.push_back(*video_device);
772 get_default_video_device = false;
776 // If either or both audio and video devices were requested but not
777 // specified by id, get the default devices.
778 if (get_default_audio_device || get_default_video_device) {
779 GetDefaultDevicesForProfile(profile,
780 get_default_audio_device,
781 get_default_video_device,
782 &devices);
785 scoped_ptr<content::MediaStreamUI> ui;
786 if (!devices.empty()) {
787 result = content::MEDIA_DEVICE_OK;
788 ui = media_stream_capture_indicator_->RegisterMediaStream(
789 web_contents, devices);
792 callback.Run(devices, result, ui.Pass());
794 #endif
796 void MediaCaptureDevicesDispatcher::ProcessRegularMediaAccessRequest(
797 content::WebContents* web_contents,
798 const content::MediaStreamRequest& request,
799 const content::MediaResponseCallback& callback) {
800 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
802 RequestsQueue& queue = pending_requests_[web_contents];
803 queue.push_back(PendingAccessRequest(request, callback));
805 // If this is the only request then show the infobar.
806 if (queue.size() == 1)
807 ProcessQueuedAccessRequest(web_contents);
810 void MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest(
811 content::WebContents* web_contents) {
812 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
814 std::map<content::WebContents*, RequestsQueue>::iterator it =
815 pending_requests_.find(web_contents);
817 if (it == pending_requests_.end() || it->second.empty()) {
818 // Don't do anything if the tab was closed.
819 return;
822 DCHECK(!it->second.empty());
824 if (PermissionBubbleManager::Enabled() ||
825 MediaStreamPermissionBubbleExperimentEnabled()) {
826 scoped_ptr<MediaStreamDevicesController> controller(
827 new MediaStreamDevicesController(web_contents,
828 it->second.front().request,
829 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse,
830 base::Unretained(this), web_contents)));
831 if (controller->DismissInfoBarAndTakeActionOnSettings())
832 return;
833 PermissionBubbleManager* bubble_manager =
834 PermissionBubbleManager::FromWebContents(web_contents);
835 if (bubble_manager)
836 bubble_manager->AddRequest(controller.release());
837 return;
840 // TODO(gbillock): delete this block and the MediaStreamInfoBarDelegate
841 // when we've transitioned to bubbles. (crbug/337458)
842 MediaStreamInfoBarDelegate::Create(
843 web_contents, it->second.front().request,
844 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse,
845 base::Unretained(this), web_contents));
848 void MediaCaptureDevicesDispatcher::OnAccessRequestResponse(
849 content::WebContents* web_contents,
850 const content::MediaStreamDevices& devices,
851 content::MediaStreamRequestResult result,
852 scoped_ptr<content::MediaStreamUI> ui) {
853 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
855 std::map<content::WebContents*, RequestsQueue>::iterator it =
856 pending_requests_.find(web_contents);
857 if (it == pending_requests_.end()) {
858 // WebContents has been destroyed. Don't need to do anything.
859 return;
862 RequestsQueue& queue(it->second);
863 if (queue.empty())
864 return;
866 content::MediaResponseCallback callback = queue.front().callback;
867 queue.pop_front();
869 if (!queue.empty()) {
870 // Post a task to process next queued request. It has to be done
871 // asynchronously to make sure that calling infobar is not destroyed until
872 // after this function returns.
873 BrowserThread::PostTask(
874 BrowserThread::UI, FROM_HERE,
875 base::Bind(&MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest,
876 base::Unretained(this), web_contents));
879 callback.Run(devices, result, ui.Pass());
882 void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile(
883 Profile* profile,
884 bool audio,
885 bool video,
886 content::MediaStreamDevices* devices) {
887 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
888 DCHECK(audio || video);
890 PrefService* prefs = profile->GetPrefs();
891 std::string default_device;
892 if (audio) {
893 default_device = prefs->GetString(prefs::kDefaultAudioCaptureDevice);
894 const content::MediaStreamDevice* device =
895 GetRequestedAudioDevice(default_device);
896 if (!device)
897 device = GetFirstAvailableAudioDevice();
898 if (device)
899 devices->push_back(*device);
902 if (video) {
903 default_device = prefs->GetString(prefs::kDefaultVideoCaptureDevice);
904 const content::MediaStreamDevice* device =
905 GetRequestedVideoDevice(default_device);
906 if (!device)
907 device = GetFirstAvailableVideoDevice();
908 if (device)
909 devices->push_back(*device);
913 const content::MediaStreamDevice*
914 MediaCaptureDevicesDispatcher::GetRequestedAudioDevice(
915 const std::string& requested_audio_device_id) {
916 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
917 const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
918 const content::MediaStreamDevice* const device =
919 FindDeviceWithId(audio_devices, requested_audio_device_id);
920 return device;
923 const content::MediaStreamDevice*
924 MediaCaptureDevicesDispatcher::GetFirstAvailableAudioDevice() {
925 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
926 const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
927 if (audio_devices.empty())
928 return NULL;
929 return &(*audio_devices.begin());
932 const content::MediaStreamDevice*
933 MediaCaptureDevicesDispatcher::GetRequestedVideoDevice(
934 const std::string& requested_video_device_id) {
935 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
936 const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices();
937 const content::MediaStreamDevice* const device =
938 FindDeviceWithId(video_devices, requested_video_device_id);
939 return device;
942 const content::MediaStreamDevice*
943 MediaCaptureDevicesDispatcher::GetFirstAvailableVideoDevice() {
944 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
945 const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices();
946 if (video_devices.empty())
947 return NULL;
948 return &(*video_devices.begin());
951 void MediaCaptureDevicesDispatcher::DisableDeviceEnumerationForTesting() {
952 is_device_enumeration_disabled_ = true;
955 scoped_refptr<MediaStreamCaptureIndicator>
956 MediaCaptureDevicesDispatcher::GetMediaStreamCaptureIndicator() {
957 return media_stream_capture_indicator_;
960 DesktopStreamsRegistry*
961 MediaCaptureDevicesDispatcher::GetDesktopStreamsRegistry() {
962 if (!desktop_streams_registry_)
963 desktop_streams_registry_.reset(new DesktopStreamsRegistry());
964 return desktop_streams_registry_.get();
967 void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged() {
968 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
969 BrowserThread::PostTask(
970 BrowserThread::UI, FROM_HERE,
971 base::Bind(
972 &MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread,
973 base::Unretained(this)));
976 void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged() {
977 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
978 BrowserThread::PostTask(
979 BrowserThread::UI, FROM_HERE,
980 base::Bind(
981 &MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread,
982 base::Unretained(this)));
985 void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged(
986 int render_process_id,
987 int render_frame_id,
988 int page_request_id,
989 const GURL& security_origin,
990 content::MediaStreamType stream_type,
991 content::MediaRequestState state) {
992 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
993 BrowserThread::PostTask(
994 BrowserThread::UI, FROM_HERE,
995 base::Bind(
996 &MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread,
997 base::Unretained(this), render_process_id, render_frame_id,
998 page_request_id, security_origin, stream_type, state));
1001 void MediaCaptureDevicesDispatcher::OnCreatingAudioStream(
1002 int render_process_id,
1003 int render_frame_id) {
1004 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1005 BrowserThread::PostTask(
1006 BrowserThread::UI, FROM_HERE,
1007 base::Bind(
1008 &MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread,
1009 base::Unretained(this), render_process_id, render_frame_id));
1012 void MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread() {
1013 MediaStreamDevices devices = GetAudioCaptureDevices();
1014 FOR_EACH_OBSERVER(Observer, observers_,
1015 OnUpdateAudioDevices(devices));
1018 void MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread() {
1019 MediaStreamDevices devices = GetVideoCaptureDevices();
1020 FOR_EACH_OBSERVER(Observer, observers_,
1021 OnUpdateVideoDevices(devices));
1024 void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread(
1025 int render_process_id,
1026 int render_frame_id,
1027 int page_request_id,
1028 const GURL& security_origin,
1029 content::MediaStreamType stream_type,
1030 content::MediaRequestState state) {
1031 // Track desktop capture sessions. Tracking is necessary to avoid unbalanced
1032 // session counts since not all requests will reach MEDIA_REQUEST_STATE_DONE,
1033 // but they will all reach MEDIA_REQUEST_STATE_CLOSING.
1034 if (stream_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE) {
1035 if (state == content::MEDIA_REQUEST_STATE_DONE) {
1036 DesktopCaptureSession session = { render_process_id, render_frame_id,
1037 page_request_id };
1038 desktop_capture_sessions_.push_back(session);
1039 } else if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
1040 for (DesktopCaptureSessions::iterator it =
1041 desktop_capture_sessions_.begin();
1042 it != desktop_capture_sessions_.end();
1043 ++it) {
1044 if (it->render_process_id == render_process_id &&
1045 it->render_frame_id == render_frame_id &&
1046 it->page_request_id == page_request_id) {
1047 desktop_capture_sessions_.erase(it);
1048 break;
1054 // Cancel the request.
1055 if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
1056 bool found = false;
1057 for (RequestsQueues::iterator rqs_it = pending_requests_.begin();
1058 rqs_it != pending_requests_.end(); ++rqs_it) {
1059 RequestsQueue& queue = rqs_it->second;
1060 for (RequestsQueue::iterator it = queue.begin();
1061 it != queue.end(); ++it) {
1062 if (it->request.render_process_id == render_process_id &&
1063 it->request.render_frame_id == render_frame_id &&
1064 it->request.page_request_id == page_request_id) {
1065 queue.erase(it);
1066 found = true;
1067 break;
1070 if (found)
1071 break;
1075 #if defined(OS_CHROMEOS)
1076 if (IsOriginForCasting(security_origin) && IsVideoMediaType(stream_type)) {
1077 // Notify ash that casting state has changed.
1078 if (state == content::MEDIA_REQUEST_STATE_DONE) {
1079 ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(true);
1080 } else if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
1081 ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(false);
1084 #endif
1086 FOR_EACH_OBSERVER(Observer, observers_,
1087 OnRequestUpdate(render_process_id,
1088 render_frame_id,
1089 stream_type,
1090 state));
1093 void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread(
1094 int render_process_id,
1095 int render_frame_id) {
1096 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1097 FOR_EACH_OBSERVER(Observer, observers_,
1098 OnCreatingAudioStream(render_process_id, render_frame_id));
1101 bool MediaCaptureDevicesDispatcher::IsDesktopCaptureInProgress() {
1102 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1103 return desktop_capture_sessions_.size() > 0;
1106 void MediaCaptureDevicesDispatcher::SetTestAudioCaptureDevices(
1107 const MediaStreamDevices& devices) {
1108 test_audio_devices_ = devices;
1111 void MediaCaptureDevicesDispatcher::SetTestVideoCaptureDevices(
1112 const MediaStreamDevices& devices) {
1113 test_video_devices_ = devices;