Fire an error if a pref used in the UI is missing once all prefs are fetched.
[chromium-blink-merge.git] / chrome / browser / media / media_capture_devices_dispatcher.cc
blobef6127b337e5a3a6aaec44dac7a5c4bee431a59b
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 // 7. XKB input method component extension.
103 // 8. M17n/T13n/CJK input method component extension.
104 // Once http://crbug.com/292856 is fixed, remove this whitelist.
105 bool IsMediaRequestWhitelistedForExtension(
106 const extensions::Extension* extension) {
107 return extension->id() == "mppnpdlheglhdfmldimlhpnegondlapf" ||
108 extension->id() == "bepbmhgboaologfdajaanbcjmnhjmhfn" ||
109 extension->id() == "jokbpnebhdcladagohdnfgjcpejggllo" ||
110 extension->id() == "clffjmdilanldobdnedchkdbofoimcgb" ||
111 extension->id() == "nnckehldicaciogcbchegobnafnjkcne" ||
112 extension->id() == "nbpagnldghgfoolbancepceaanlmhfmd" ||
113 extension->id() == "jkghodnilhceideoidjikpgommlajknk" ||
114 extension->id() == "gjaehgfemfahhmlgpdfknkhdnemmolop";
117 bool IsBuiltInExtension(const GURL& origin) {
118 return
119 // Feedback Extension.
120 origin.spec() == "chrome-extension://gfdkimpbcpahaombhbimeihdjnejgicl/";
123 // Returns true of the security origin is associated with casting.
124 bool IsOriginForCasting(const GURL& origin) {
125 // Whitelisted tab casting extensions.
126 return
127 // Dev
128 origin.spec() == "chrome-extension://enhhojjnijigcajfphajepfemndkmdlo/" ||
129 // Canary
130 origin.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" ||
131 // Beta (internal)
132 origin.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" ||
133 // Google Cast Beta
134 origin.spec() == "chrome-extension://dliochdbjfkdbacpmhlcpmleaejidimm/" ||
135 // Google Cast Stable
136 origin.spec() == "chrome-extension://boadgeojelhgndaghljhdicfkmllpafd/" ||
137 // http://crbug.com/457908
138 origin.spec() == "chrome-extension://ekpaaapppgpmolpcldedioblbkmijaca/" ||
139 origin.spec() == "chrome-extension://fjhoaacokmgbjemoflkofnenfaiekifl/";
142 bool IsExtensionWhitelistedForScreenCapture(
143 const extensions::Extension* extension) {
144 #if defined(OS_CHROMEOS)
145 std::string hash = base::SHA1HashString(extension->id());
146 std::string hex_hash = base::HexEncode(hash.c_str(), hash.length());
148 // crbug.com/446688
149 return hex_hash == "4F25792AF1AA7483936DE29C07806F203C7170A0" ||
150 hex_hash == "BD8781D757D830FC2E85470A1B6E8A718B7EE0D9" ||
151 hex_hash == "4AC2B6C63C6480D150DFDA13E4A5956EB1D0DDBB" ||
152 hex_hash == "81986D4F846CEDDDB962643FA501D1780DD441BB";
153 #else
154 return false;
155 #endif // defined(OS_CHROMEOS)
157 #endif // defined(ENABLE_EXTENSIONS)
159 // Helper to get title of the calling application shown in the screen capture
160 // notification.
161 base::string16 GetApplicationTitle(content::WebContents* web_contents,
162 const extensions::Extension* extension) {
163 // Use extension name as title for extensions and host/origin for drive-by
164 // web.
165 std::string title;
166 #if defined(ENABLE_EXTENSIONS)
167 if (extension) {
168 title = extension->name();
169 return base::UTF8ToUTF16(title);
171 #endif
172 GURL url = web_contents->GetURL();
173 title = url.SchemeIsSecure() ? net::GetHostAndOptionalPort(url)
174 : url.GetOrigin().spec();
175 return base::UTF8ToUTF16(title);
178 // Helper to get list of media stream devices for desktop capture in |devices|.
179 // Registers to display notification if |display_notification| is true.
180 // Returns an instance of MediaStreamUI to be passed to content layer.
181 scoped_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture(
182 content::MediaStreamDevices* devices,
183 content::DesktopMediaID media_id,
184 bool capture_audio,
185 bool display_notification,
186 const base::string16& application_title,
187 const base::string16& registered_extension_name) {
188 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
189 scoped_ptr<content::MediaStreamUI> ui;
191 // Add selected desktop source to the list.
192 devices->push_back(content::MediaStreamDevice(
193 content::MEDIA_DESKTOP_VIDEO_CAPTURE, media_id.ToString(), "Screen"));
194 if (capture_audio) {
195 // Use the special loopback device ID for system audio capture.
196 devices->push_back(content::MediaStreamDevice(
197 content::MEDIA_DESKTOP_AUDIO_CAPTURE,
198 media::AudioManagerBase::kLoopbackInputDeviceId, "System Audio"));
201 // If required, register to display the notification for stream capture.
202 if (display_notification) {
203 if (application_title == registered_extension_name) {
204 ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
205 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT,
206 application_title));
207 } else {
208 ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
209 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
210 registered_extension_name,
211 application_title));
215 return ui.Pass();
218 #if !defined(OS_ANDROID)
219 // Find browser or app window from a given |web_contents|.
220 gfx::NativeWindow FindParentWindowForWebContents(
221 content::WebContents* web_contents) {
222 Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
223 if (browser && browser->window())
224 return browser->window()->GetNativeWindow();
226 const extensions::AppWindowRegistry::AppWindowList& window_list =
227 extensions::AppWindowRegistry::Get(
228 web_contents->GetBrowserContext())->app_windows();
229 for (extensions::AppWindowRegistry::AppWindowList::const_iterator iter =
230 window_list.begin();
231 iter != window_list.end(); ++iter) {
232 if ((*iter)->web_contents() == web_contents)
233 return (*iter)->GetNativeWindow();
236 return NULL;
238 #endif
240 #if defined(ENABLE_EXTENSIONS)
241 const extensions::Extension* GetExtensionForOrigin(
242 Profile* profile,
243 const GURL& security_origin) {
244 if (!security_origin.SchemeIs(extensions::kExtensionScheme))
245 return NULL;
247 const extensions::Extension* extension =
248 extensions::ExtensionRegistry::Get(profile)->enabled_extensions().GetByID(
249 security_origin.host());
250 DCHECK(extension);
251 return extension;
253 #endif
255 } // namespace
257 MediaCaptureDevicesDispatcher::PendingAccessRequest::PendingAccessRequest(
258 const content::MediaStreamRequest& request,
259 const content::MediaResponseCallback& callback)
260 : request(request),
261 callback(callback) {
264 MediaCaptureDevicesDispatcher::PendingAccessRequest::~PendingAccessRequest() {}
266 MediaCaptureDevicesDispatcher* MediaCaptureDevicesDispatcher::GetInstance() {
267 return Singleton<MediaCaptureDevicesDispatcher>::get();
270 MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher()
271 : is_device_enumeration_disabled_(false),
272 media_stream_capture_indicator_(new MediaStreamCaptureIndicator()) {
273 // MediaCaptureDevicesDispatcher is a singleton. It should be created on
274 // UI thread. Otherwise, it will not receive
275 // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in
276 // possible use after free.
277 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
278 notifications_registrar_.Add(
279 this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
280 content::NotificationService::AllSources());
282 #if defined(OS_MACOSX)
283 // AVFoundation is used for video/audio device monitoring and video capture.
284 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
285 switches::kForceQTKit)) {
286 base::CommandLine::ForCurrentProcess()->AppendSwitch(
287 switches::kEnableAVFoundation);
289 #endif
292 MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {}
294 void MediaCaptureDevicesDispatcher::RegisterProfilePrefs(
295 user_prefs::PrefRegistrySyncable* registry) {
296 registry->RegisterStringPref(
297 prefs::kDefaultAudioCaptureDevice,
298 std::string(),
299 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
300 registry->RegisterStringPref(
301 prefs::kDefaultVideoCaptureDevice,
302 std::string(),
303 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
306 void MediaCaptureDevicesDispatcher::AddObserver(Observer* observer) {
307 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
308 if (!observers_.HasObserver(observer))
309 observers_.AddObserver(observer);
312 void MediaCaptureDevicesDispatcher::RemoveObserver(Observer* observer) {
313 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
314 observers_.RemoveObserver(observer);
317 const MediaStreamDevices&
318 MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() {
319 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
320 if (is_device_enumeration_disabled_ || !test_audio_devices_.empty())
321 return test_audio_devices_;
323 return MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices();
326 const MediaStreamDevices&
327 MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() {
328 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
329 if (is_device_enumeration_disabled_ || !test_video_devices_.empty())
330 return test_video_devices_;
332 return MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices();
335 void MediaCaptureDevicesDispatcher::Observe(
336 int type,
337 const content::NotificationSource& source,
338 const content::NotificationDetails& details) {
339 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
340 if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) {
341 content::WebContents* web_contents =
342 content::Source<content::WebContents>(source).ptr();
343 pending_requests_.erase(web_contents);
347 void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequest(
348 content::WebContents* web_contents,
349 const content::MediaStreamRequest& request,
350 const content::MediaResponseCallback& callback,
351 const extensions::Extension* extension) {
352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
354 if (request.video_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE ||
355 request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE) {
356 ProcessDesktopCaptureAccessRequest(
357 web_contents, request, callback, extension);
358 } else if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE ||
359 request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE) {
360 ProcessTabCaptureAccessRequest(
361 web_contents, request, callback, extension);
362 } else {
363 #if defined(ENABLE_EXTENSIONS)
364 bool is_whitelisted =
365 extension && (extension->is_platform_app() ||
366 IsMediaRequestWhitelistedForExtension(extension));
367 if (is_whitelisted) {
368 // For extensions access is approved based on extension permissions.
369 ProcessMediaAccessRequestFromPlatformAppOrExtension(
370 web_contents, request, callback, extension);
371 return;
373 #endif
374 ProcessRegularMediaAccessRequest(web_contents, request, callback);
378 bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
379 content::BrowserContext* browser_context,
380 const GURL& security_origin,
381 content::MediaStreamType type) {
382 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
383 DCHECK(type == content::MEDIA_DEVICE_AUDIO_CAPTURE ||
384 type == content::MEDIA_DEVICE_VIDEO_CAPTURE);
386 Profile* profile = Profile::FromBrowserContext(browser_context);
387 #if defined(ENABLE_EXTENSIONS)
388 const extensions::Extension* extension =
389 GetExtensionForOrigin(profile, security_origin);
391 if (extension && (extension->is_platform_app() ||
392 IsMediaRequestWhitelistedForExtension(extension))) {
393 return extension->permissions_data()->HasAPIPermission(
394 type == content::MEDIA_DEVICE_AUDIO_CAPTURE
395 ? extensions::APIPermission::kAudioCapture
396 : extensions::APIPermission::kVideoCapture);
398 #endif
400 if (CheckAllowAllMediaStreamContentForOrigin(profile, security_origin))
401 return true;
403 const char* policy_name = type == content::MEDIA_DEVICE_AUDIO_CAPTURE
404 ? prefs::kAudioCaptureAllowed
405 : prefs::kVideoCaptureAllowed;
406 const char* list_policy_name = type == content::MEDIA_DEVICE_AUDIO_CAPTURE
407 ? prefs::kAudioCaptureAllowedUrls
408 : prefs::kVideoCaptureAllowedUrls;
409 if (GetDevicePolicy(
410 profile, security_origin, policy_name, list_policy_name) ==
411 ALWAYS_ALLOW) {
412 return true;
415 // There's no secondary URL for these content types, hence duplicating
416 // |security_origin|.
417 if (profile->GetHostContentSettingsMap()->GetContentSetting(
418 security_origin,
419 security_origin,
420 type == content::MEDIA_DEVICE_AUDIO_CAPTURE
421 ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC
422 : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
423 NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_ALLOW) {
424 return true;
427 return false;
430 bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
431 content::WebContents* web_contents,
432 const GURL& security_origin,
433 content::MediaStreamType type) {
434 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
435 DCHECK(type == content::MEDIA_DEVICE_AUDIO_CAPTURE ||
436 type == content::MEDIA_DEVICE_VIDEO_CAPTURE);
438 Profile* profile =
439 Profile::FromBrowserContext(web_contents->GetBrowserContext());
441 if (CheckAllowAllMediaStreamContentForOrigin(profile, security_origin))
442 return true;
444 const char* policy_name = type == content::MEDIA_DEVICE_AUDIO_CAPTURE
445 ? prefs::kAudioCaptureAllowed
446 : prefs::kVideoCaptureAllowed;
447 const char* list_policy_name = type == content::MEDIA_DEVICE_AUDIO_CAPTURE
448 ? prefs::kAudioCaptureAllowedUrls
449 : prefs::kVideoCaptureAllowedUrls;
450 if (GetDevicePolicy(
451 profile, security_origin, policy_name, list_policy_name) ==
452 ALWAYS_ALLOW) {
453 return true;
456 // There's no secondary URL for these content types, hence duplicating
457 // |security_origin|.
458 if (profile->GetHostContentSettingsMap()->GetContentSetting(
459 security_origin,
460 security_origin,
461 type == content::MEDIA_DEVICE_AUDIO_CAPTURE
462 ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC
463 : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
464 NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_ALLOW) {
465 return true;
468 return false;
471 #if defined(ENABLE_EXTENSIONS)
472 bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
473 content::WebContents* web_contents,
474 const GURL& security_origin,
475 content::MediaStreamType type,
476 const extensions::Extension* extension) {
477 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
478 DCHECK(type == content::MEDIA_DEVICE_AUDIO_CAPTURE ||
479 type == content::MEDIA_DEVICE_VIDEO_CAPTURE);
481 if (extension->is_platform_app() ||
482 IsMediaRequestWhitelistedForExtension(extension)) {
483 return extension->permissions_data()->HasAPIPermission(
484 type == content::MEDIA_DEVICE_AUDIO_CAPTURE
485 ? extensions::APIPermission::kAudioCapture
486 : extensions::APIPermission::kVideoCapture);
489 return CheckMediaAccessPermission(web_contents, security_origin, type);
491 #endif
493 void MediaCaptureDevicesDispatcher::ProcessDesktopCaptureAccessRequest(
494 content::WebContents* web_contents,
495 const content::MediaStreamRequest& request,
496 const content::MediaResponseCallback& callback,
497 const extensions::Extension* extension) {
498 content::MediaStreamDevices devices;
499 scoped_ptr<content::MediaStreamUI> ui;
501 if (request.video_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE) {
502 callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass());
503 return;
506 // If the device id wasn't specified then this is a screen capture request
507 // (i.e. chooseDesktopMedia() API wasn't used to generate device id).
508 if (request.requested_video_device_id.empty()) {
509 ProcessScreenCaptureAccessRequest(
510 web_contents, request, callback, extension);
511 return;
514 // The extension name that the stream is registered with.
515 std::string original_extension_name;
516 // Resolve DesktopMediaID for the specified device id.
517 content::DesktopMediaID media_id;
518 // TODO(miu): Replace "main RenderFrame" IDs with the request's actual
519 // RenderFrame IDs once the desktop capture extension API implementation is
520 // fixed. http://crbug.com/304341
521 content::WebContents* const web_contents_for_stream =
522 content::WebContents::FromRenderFrameHost(
523 content::RenderFrameHost::FromID(request.render_process_id,
524 request.render_frame_id));
525 content::RenderFrameHost* const main_frame = web_contents_for_stream ?
526 web_contents_for_stream->GetMainFrame() : NULL;
527 if (main_frame) {
528 media_id = GetDesktopStreamsRegistry()->RequestMediaForStreamId(
529 request.requested_video_device_id,
530 main_frame->GetProcess()->GetID(),
531 main_frame->GetRoutingID(),
532 request.security_origin,
533 &original_extension_name);
536 // Received invalid device id.
537 if (media_id.type == content::DesktopMediaID::TYPE_NONE) {
538 callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass());
539 return;
542 bool loopback_audio_supported = false;
543 #if defined(USE_CRAS) || defined(OS_WIN)
544 // Currently loopback audio capture is supported only on Windows and ChromeOS.
545 loopback_audio_supported = true;
546 #endif
548 // Audio is only supported for screen capture streams.
549 bool capture_audio =
550 (media_id.type == content::DesktopMediaID::TYPE_SCREEN &&
551 request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE &&
552 loopback_audio_supported);
554 ui = GetDevicesForDesktopCapture(
555 &devices, media_id, capture_audio, true,
556 GetApplicationTitle(web_contents, extension),
557 base::UTF8ToUTF16(original_extension_name));
559 callback.Run(devices, content::MEDIA_DEVICE_OK, ui.Pass());
562 void MediaCaptureDevicesDispatcher::ProcessScreenCaptureAccessRequest(
563 content::WebContents* web_contents,
564 const content::MediaStreamRequest& request,
565 const content::MediaResponseCallback& callback,
566 const extensions::Extension* extension) {
567 content::MediaStreamDevices devices;
568 scoped_ptr<content::MediaStreamUI> ui;
570 DCHECK_EQ(request.video_type, content::MEDIA_DESKTOP_VIDEO_CAPTURE);
572 bool loopback_audio_supported = false;
573 #if defined(USE_CRAS) || defined(OS_WIN)
574 // Currently loopback audio capture is supported only on Windows and ChromeOS.
575 loopback_audio_supported = true;
576 #endif
578 bool component_extension = false;
579 #if defined(ENABLE_EXTENSIONS)
580 component_extension =
581 extension && extension->location() == extensions::Manifest::COMPONENT;
582 #endif
584 bool screen_capture_enabled =
585 base::CommandLine::ForCurrentProcess()->HasSwitch(
586 switches::kEnableUserMediaScreenCapturing);
587 #if defined(ENABLE_EXTENSIONS)
588 screen_capture_enabled |=
589 IsOriginForCasting(request.security_origin) ||
590 IsExtensionWhitelistedForScreenCapture(extension) ||
591 IsBuiltInExtension(request.security_origin);
592 #endif
594 const bool origin_is_secure =
595 request.security_origin.SchemeIsSecure() ||
596 request.security_origin.SchemeIs(extensions::kExtensionScheme) ||
597 base::CommandLine::ForCurrentProcess()->HasSwitch(
598 switches::kAllowHttpScreenCapture);
600 // If basic conditions (screen capturing is enabled and origin is secure)
601 // aren't fulfilled, we'll use "invalid state" as result. Otherwise, we set
602 // it after checking permission.
603 // TODO(grunell): It would be good to change this result for something else,
604 // probably a new one.
605 content::MediaStreamRequestResult result =
606 content::MEDIA_DEVICE_INVALID_STATE;
608 // Approve request only when the following conditions are met:
609 // 1. Screen capturing is enabled via command line switch or white-listed for
610 // the given origin.
611 // 2. Request comes from a page with a secure origin or from an extension.
612 if (screen_capture_enabled && origin_is_secure) {
613 // Get title of the calling application prior to showing the message box.
614 // chrome::ShowMessageBox() starts a nested message loop which may allow
615 // |web_contents| to be destroyed on the UI thread before the message box
616 // is closed. See http://crbug.com/326690.
617 base::string16 application_title =
618 GetApplicationTitle(web_contents, extension);
619 #if !defined(OS_ANDROID)
620 gfx::NativeWindow parent_window =
621 FindParentWindowForWebContents(web_contents);
622 #else
623 gfx::NativeWindow parent_window = NULL;
624 #endif
625 web_contents = NULL;
627 bool whitelisted_extension = false;
628 #if defined(ENABLE_EXTENSIONS)
629 whitelisted_extension = IsExtensionWhitelistedForScreenCapture(
630 extension);
631 #endif
633 // For whitelisted or component extensions, bypass message box.
634 bool user_approved = false;
635 if (!whitelisted_extension && !component_extension) {
636 base::string16 application_name =
637 base::UTF8ToUTF16(request.security_origin.spec());
638 #if defined(ENABLE_EXTENSIONS)
639 if (extension)
640 application_name = base::UTF8ToUTF16(extension->name());
641 #endif
642 base::string16 confirmation_text = l10n_util::GetStringFUTF16(
643 request.audio_type == content::MEDIA_NO_SERVICE ?
644 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT :
645 IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT,
646 application_name);
647 chrome::MessageBoxResult result = chrome::ShowMessageBox(
648 parent_window,
649 l10n_util::GetStringFUTF16(
650 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name),
651 confirmation_text,
652 chrome::MESSAGE_BOX_TYPE_QUESTION);
653 user_approved = (result == chrome::MESSAGE_BOX_RESULT_YES);
656 if (user_approved || component_extension || whitelisted_extension) {
657 content::DesktopMediaID screen_id;
658 #if defined(OS_CHROMEOS)
659 screen_id = content::DesktopMediaID::RegisterAuraWindow(
660 ash::Shell::GetInstance()->GetPrimaryRootWindow());
661 #else // defined(OS_CHROMEOS)
662 screen_id =
663 content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
664 webrtc::kFullDesktopScreenId);
665 #endif // !defined(OS_CHROMEOS)
667 bool capture_audio =
668 (request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE &&
669 loopback_audio_supported);
671 // Unless we're being invoked from a component extension, register to
672 // display the notification for stream capture.
673 bool display_notification = !component_extension;
675 ui = GetDevicesForDesktopCapture(&devices, screen_id, capture_audio,
676 display_notification, application_title,
677 application_title);
678 DCHECK(!devices.empty());
681 // The only case when devices can be empty is if the user has denied
682 // permission.
683 result = devices.empty() ? content::MEDIA_DEVICE_PERMISSION_DENIED
684 : content::MEDIA_DEVICE_OK;
687 callback.Run(devices, result, ui.Pass());
690 void MediaCaptureDevicesDispatcher::ProcessTabCaptureAccessRequest(
691 content::WebContents* web_contents,
692 const content::MediaStreamRequest& request,
693 const content::MediaResponseCallback& callback,
694 const extensions::Extension* extension) {
695 content::MediaStreamDevices devices;
696 scoped_ptr<content::MediaStreamUI> ui;
698 #if defined(ENABLE_EXTENSIONS)
699 Profile* profile =
700 Profile::FromBrowserContext(web_contents->GetBrowserContext());
701 extensions::TabCaptureRegistry* tab_capture_registry =
702 extensions::TabCaptureRegistry::Get(profile);
703 if (!tab_capture_registry) {
704 NOTREACHED();
705 callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass());
706 return;
708 const bool tab_capture_allowed = tab_capture_registry->VerifyRequest(
709 request.render_process_id, request.render_frame_id, extension->id());
711 if (request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE &&
712 tab_capture_allowed &&
713 extension->permissions_data()->HasAPIPermission(
714 extensions::APIPermission::kTabCapture)) {
715 devices.push_back(content::MediaStreamDevice(
716 content::MEDIA_TAB_AUDIO_CAPTURE, std::string(), std::string()));
719 if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE &&
720 tab_capture_allowed &&
721 extension->permissions_data()->HasAPIPermission(
722 extensions::APIPermission::kTabCapture)) {
723 devices.push_back(content::MediaStreamDevice(
724 content::MEDIA_TAB_VIDEO_CAPTURE, std::string(), std::string()));
727 if (!devices.empty()) {
728 ui = media_stream_capture_indicator_->RegisterMediaStream(
729 web_contents, devices);
731 callback.Run(
732 devices,
733 devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE :
734 content::MEDIA_DEVICE_OK,
735 ui.Pass());
736 #else // defined(ENABLE_EXTENSIONS)
737 callback.Run(devices, content::MEDIA_DEVICE_TAB_CAPTURE_FAILURE, ui.Pass());
738 #endif // defined(ENABLE_EXTENSIONS)
741 #if defined(ENABLE_EXTENSIONS)
742 void MediaCaptureDevicesDispatcher::
743 ProcessMediaAccessRequestFromPlatformAppOrExtension(
744 content::WebContents* web_contents,
745 const content::MediaStreamRequest& request,
746 const content::MediaResponseCallback& callback,
747 const extensions::Extension* extension) {
748 // TODO(vrk): This code is largely duplicated in
749 // MediaStreamDevicesController::Accept(). Move this code into a shared method
750 // between the two classes.
752 Profile* profile =
753 Profile::FromBrowserContext(web_contents->GetBrowserContext());
755 bool audio_allowed =
756 request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE &&
757 extension->permissions_data()->HasAPIPermission(
758 extensions::APIPermission::kAudioCapture) &&
759 GetDevicePolicy(profile, extension->url(),
760 prefs::kAudioCaptureAllowed,
761 prefs::kAudioCaptureAllowedUrls) != ALWAYS_DENY;
762 bool video_allowed =
763 request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE &&
764 extension->permissions_data()->HasAPIPermission(
765 extensions::APIPermission::kVideoCapture) &&
766 GetDevicePolicy(profile, extension->url(),
767 prefs::kVideoCaptureAllowed,
768 prefs::kVideoCaptureAllowedUrls) != ALWAYS_DENY;
770 bool get_default_audio_device = audio_allowed;
771 bool get_default_video_device = video_allowed;
773 content::MediaStreamDevices devices;
775 // Set an initial error result. If neither audio or video is allowed, we'll
776 // never try to get any device below but will just create |ui| and return an
777 // empty list with "invalid state" result. If at least one is allowed, we'll
778 // try to get device(s), and if failure, we want to return "no hardware"
779 // result.
780 // TODO(grunell): The invalid state result should be changed to a new denied
781 // result + a dcheck to ensure at least one of audio or video types is
782 // capture.
783 content::MediaStreamRequestResult result =
784 (audio_allowed || video_allowed) ? content::MEDIA_DEVICE_NO_HARDWARE
785 : content::MEDIA_DEVICE_INVALID_STATE;
787 // Get the exact audio or video device if an id is specified.
788 // We only set any error result here and before running the callback change
789 // it to OK if we have any device.
790 if (audio_allowed && !request.requested_audio_device_id.empty()) {
791 const content::MediaStreamDevice* audio_device =
792 GetRequestedAudioDevice(request.requested_audio_device_id);
793 if (audio_device) {
794 devices.push_back(*audio_device);
795 get_default_audio_device = false;
798 if (video_allowed && !request.requested_video_device_id.empty()) {
799 const content::MediaStreamDevice* video_device =
800 GetRequestedVideoDevice(request.requested_video_device_id);
801 if (video_device) {
802 devices.push_back(*video_device);
803 get_default_video_device = false;
807 // If either or both audio and video devices were requested but not
808 // specified by id, get the default devices.
809 if (get_default_audio_device || get_default_video_device) {
810 GetDefaultDevicesForProfile(profile,
811 get_default_audio_device,
812 get_default_video_device,
813 &devices);
816 scoped_ptr<content::MediaStreamUI> ui;
817 if (!devices.empty()) {
818 result = content::MEDIA_DEVICE_OK;
819 ui = media_stream_capture_indicator_->RegisterMediaStream(
820 web_contents, devices);
823 callback.Run(devices, result, ui.Pass());
825 #endif
827 void MediaCaptureDevicesDispatcher::ProcessRegularMediaAccessRequest(
828 content::WebContents* web_contents,
829 const content::MediaStreamRequest& request,
830 const content::MediaResponseCallback& callback) {
831 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
833 RequestsQueue& queue = pending_requests_[web_contents];
834 queue.push_back(PendingAccessRequest(request, callback));
836 // If this is the only request then show the infobar.
837 if (queue.size() == 1)
838 ProcessQueuedAccessRequest(web_contents);
841 void MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest(
842 content::WebContents* web_contents) {
843 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
845 std::map<content::WebContents*, RequestsQueue>::iterator it =
846 pending_requests_.find(web_contents);
848 if (it == pending_requests_.end() || it->second.empty()) {
849 // Don't do anything if the tab was closed.
850 return;
853 DCHECK(!it->second.empty());
855 if (PermissionBubbleManager::Enabled() ||
856 MediaStreamPermissionBubbleExperimentEnabled()) {
857 scoped_ptr<MediaStreamDevicesController> controller(
858 new MediaStreamDevicesController(web_contents,
859 it->second.front().request,
860 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse,
861 base::Unretained(this), web_contents)));
862 if (controller->DismissInfoBarAndTakeActionOnSettings())
863 return;
864 PermissionBubbleManager* bubble_manager =
865 PermissionBubbleManager::FromWebContents(web_contents);
866 if (bubble_manager)
867 bubble_manager->AddRequest(controller.release());
868 return;
871 // TODO(gbillock): delete this block and the MediaStreamInfoBarDelegate
872 // when we've transitioned to bubbles. (crbug/337458)
873 MediaStreamInfoBarDelegate::Create(
874 web_contents, it->second.front().request,
875 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse,
876 base::Unretained(this), web_contents));
879 void MediaCaptureDevicesDispatcher::OnAccessRequestResponse(
880 content::WebContents* web_contents,
881 const content::MediaStreamDevices& devices,
882 content::MediaStreamRequestResult result,
883 scoped_ptr<content::MediaStreamUI> ui) {
884 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
886 std::map<content::WebContents*, RequestsQueue>::iterator it =
887 pending_requests_.find(web_contents);
888 if (it == pending_requests_.end()) {
889 // WebContents has been destroyed. Don't need to do anything.
890 return;
893 RequestsQueue& queue(it->second);
894 if (queue.empty())
895 return;
897 content::MediaResponseCallback callback = queue.front().callback;
898 queue.pop_front();
900 if (!queue.empty()) {
901 // Post a task to process next queued request. It has to be done
902 // asynchronously to make sure that calling infobar is not destroyed until
903 // after this function returns.
904 BrowserThread::PostTask(
905 BrowserThread::UI, FROM_HERE,
906 base::Bind(&MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest,
907 base::Unretained(this), web_contents));
910 callback.Run(devices, result, ui.Pass());
913 void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile(
914 Profile* profile,
915 bool audio,
916 bool video,
917 content::MediaStreamDevices* devices) {
918 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
919 DCHECK(audio || video);
921 PrefService* prefs = profile->GetPrefs();
922 std::string default_device;
923 if (audio) {
924 default_device = prefs->GetString(prefs::kDefaultAudioCaptureDevice);
925 const content::MediaStreamDevice* device =
926 GetRequestedAudioDevice(default_device);
927 if (!device)
928 device = GetFirstAvailableAudioDevice();
929 if (device)
930 devices->push_back(*device);
933 if (video) {
934 default_device = prefs->GetString(prefs::kDefaultVideoCaptureDevice);
935 const content::MediaStreamDevice* device =
936 GetRequestedVideoDevice(default_device);
937 if (!device)
938 device = GetFirstAvailableVideoDevice();
939 if (device)
940 devices->push_back(*device);
944 const content::MediaStreamDevice*
945 MediaCaptureDevicesDispatcher::GetRequestedAudioDevice(
946 const std::string& requested_audio_device_id) {
947 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
948 const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
949 const content::MediaStreamDevice* const device =
950 FindDeviceWithId(audio_devices, requested_audio_device_id);
951 return device;
954 const content::MediaStreamDevice*
955 MediaCaptureDevicesDispatcher::GetFirstAvailableAudioDevice() {
956 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
957 const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
958 if (audio_devices.empty())
959 return NULL;
960 return &(*audio_devices.begin());
963 const content::MediaStreamDevice*
964 MediaCaptureDevicesDispatcher::GetRequestedVideoDevice(
965 const std::string& requested_video_device_id) {
966 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
967 const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices();
968 const content::MediaStreamDevice* const device =
969 FindDeviceWithId(video_devices, requested_video_device_id);
970 return device;
973 const content::MediaStreamDevice*
974 MediaCaptureDevicesDispatcher::GetFirstAvailableVideoDevice() {
975 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
976 const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices();
977 if (video_devices.empty())
978 return NULL;
979 return &(*video_devices.begin());
982 void MediaCaptureDevicesDispatcher::DisableDeviceEnumerationForTesting() {
983 is_device_enumeration_disabled_ = true;
986 scoped_refptr<MediaStreamCaptureIndicator>
987 MediaCaptureDevicesDispatcher::GetMediaStreamCaptureIndicator() {
988 return media_stream_capture_indicator_;
991 DesktopStreamsRegistry*
992 MediaCaptureDevicesDispatcher::GetDesktopStreamsRegistry() {
993 if (!desktop_streams_registry_)
994 desktop_streams_registry_.reset(new DesktopStreamsRegistry());
995 return desktop_streams_registry_.get();
998 void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged() {
999 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1000 BrowserThread::PostTask(
1001 BrowserThread::UI, FROM_HERE,
1002 base::Bind(
1003 &MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread,
1004 base::Unretained(this)));
1007 void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged() {
1008 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1009 BrowserThread::PostTask(
1010 BrowserThread::UI, FROM_HERE,
1011 base::Bind(
1012 &MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread,
1013 base::Unretained(this)));
1016 void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged(
1017 int render_process_id,
1018 int render_frame_id,
1019 int page_request_id,
1020 const GURL& security_origin,
1021 content::MediaStreamType stream_type,
1022 content::MediaRequestState state) {
1023 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1024 BrowserThread::PostTask(
1025 BrowserThread::UI, FROM_HERE,
1026 base::Bind(
1027 &MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread,
1028 base::Unretained(this), render_process_id, render_frame_id,
1029 page_request_id, security_origin, stream_type, state));
1032 void MediaCaptureDevicesDispatcher::OnCreatingAudioStream(
1033 int render_process_id,
1034 int render_frame_id) {
1035 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
1036 BrowserThread::PostTask(
1037 BrowserThread::UI, FROM_HERE,
1038 base::Bind(
1039 &MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread,
1040 base::Unretained(this), render_process_id, render_frame_id));
1043 void MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread() {
1044 MediaStreamDevices devices = GetAudioCaptureDevices();
1045 FOR_EACH_OBSERVER(Observer, observers_,
1046 OnUpdateAudioDevices(devices));
1049 void MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread() {
1050 MediaStreamDevices devices = GetVideoCaptureDevices();
1051 FOR_EACH_OBSERVER(Observer, observers_,
1052 OnUpdateVideoDevices(devices));
1055 void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread(
1056 int render_process_id,
1057 int render_frame_id,
1058 int page_request_id,
1059 const GURL& security_origin,
1060 content::MediaStreamType stream_type,
1061 content::MediaRequestState state) {
1062 // Track desktop capture sessions. Tracking is necessary to avoid unbalanced
1063 // session counts since not all requests will reach MEDIA_REQUEST_STATE_DONE,
1064 // but they will all reach MEDIA_REQUEST_STATE_CLOSING.
1065 if (stream_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE) {
1066 if (state == content::MEDIA_REQUEST_STATE_DONE) {
1067 DesktopCaptureSession session = { render_process_id, render_frame_id,
1068 page_request_id };
1069 desktop_capture_sessions_.push_back(session);
1070 } else if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
1071 for (DesktopCaptureSessions::iterator it =
1072 desktop_capture_sessions_.begin();
1073 it != desktop_capture_sessions_.end();
1074 ++it) {
1075 if (it->render_process_id == render_process_id &&
1076 it->render_frame_id == render_frame_id &&
1077 it->page_request_id == page_request_id) {
1078 desktop_capture_sessions_.erase(it);
1079 break;
1085 // Cancel the request.
1086 if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
1087 bool found = false;
1088 for (RequestsQueues::iterator rqs_it = pending_requests_.begin();
1089 rqs_it != pending_requests_.end(); ++rqs_it) {
1090 RequestsQueue& queue = rqs_it->second;
1091 for (RequestsQueue::iterator it = queue.begin();
1092 it != queue.end(); ++it) {
1093 if (it->request.render_process_id == render_process_id &&
1094 it->request.render_frame_id == render_frame_id &&
1095 it->request.page_request_id == page_request_id) {
1096 queue.erase(it);
1097 found = true;
1098 break;
1101 if (found)
1102 break;
1106 #if defined(OS_CHROMEOS)
1107 if (IsOriginForCasting(security_origin) && IsVideoMediaType(stream_type)) {
1108 // Notify ash that casting state has changed.
1109 if (state == content::MEDIA_REQUEST_STATE_DONE) {
1110 ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(true);
1111 } else if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
1112 ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(false);
1115 #endif
1117 FOR_EACH_OBSERVER(Observer, observers_,
1118 OnRequestUpdate(render_process_id,
1119 render_frame_id,
1120 stream_type,
1121 state));
1124 void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread(
1125 int render_process_id,
1126 int render_frame_id) {
1127 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1128 FOR_EACH_OBSERVER(Observer, observers_,
1129 OnCreatingAudioStream(render_process_id, render_frame_id));
1132 bool MediaCaptureDevicesDispatcher::IsDesktopCaptureInProgress() {
1133 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1134 return desktop_capture_sessions_.size() > 0;
1137 void MediaCaptureDevicesDispatcher::SetTestAudioCaptureDevices(
1138 const MediaStreamDevices& devices) {
1139 test_audio_devices_ = devices;
1142 void MediaCaptureDevicesDispatcher::SetTestVideoCaptureDevices(
1143 const MediaStreamDevices& devices) {
1144 test_video_devices_ = devices;