replace OVERRIDE and FINAL with override and final in ash/
[chromium-blink-merge.git] / chrome / browser / media / media_capture_devices_dispatcher.cc
blob47ba3dcae825ff4d475c4dffb98db300cd715799
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/media/media_capture_devices_dispatcher.h"
7 #include "base/command_line.h"
8 #include "base/logging.h"
9 #include "base/metrics/field_trial.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/prefs/scoped_user_pref_update.h"
12 #include "base/sha1.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "chrome/browser/content_settings/host_content_settings_map.h"
17 #include "chrome/browser/media/desktop_streams_registry.h"
18 #include "chrome/browser/media/media_stream_capture_indicator.h"
19 #include "chrome/browser/media/media_stream_device_permissions.h"
20 #include "chrome/browser/media/media_stream_infobar_delegate.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/browser_finder.h"
24 #include "chrome/browser/ui/browser_window.h"
25 #include "chrome/browser/ui/screen_capture_notification_ui.h"
26 #include "chrome/browser/ui/simple_message_box.h"
27 #include "chrome/browser/ui/website_settings/permission_bubble_manager.h"
28 #include "chrome/common/chrome_switches.h"
29 #include "chrome/common/chrome_version_info.h"
30 #include "chrome/common/pref_names.h"
31 #include "chrome/grit/generated_resources.h"
32 #include "components/content_settings/core/browser/content_settings_provider.h"
33 #include "components/pref_registry/pref_registry_syncable.h"
34 #include "content/public/browser/browser_thread.h"
35 #include "content/public/browser/desktop_media_id.h"
36 #include "content/public/browser/media_capture_devices.h"
37 #include "content/public/browser/notification_service.h"
38 #include "content/public/browser/notification_source.h"
39 #include "content/public/browser/notification_types.h"
40 #include "content/public/browser/render_frame_host.h"
41 #include "content/public/browser/render_process_host.h"
42 #include "content/public/browser/web_contents.h"
43 #include "content/public/common/media_stream_request.h"
44 #include "extensions/common/constants.h"
45 #include "extensions/common/extension.h"
46 #include "extensions/common/permissions/permissions_data.h"
47 #include "media/audio/audio_manager_base.h"
48 #include "media/base/media_switches.h"
49 #include "net/base/net_util.h"
50 #include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
51 #include "ui/base/l10n/l10n_util.h"
53 #if defined(OS_CHROMEOS)
54 #include "ash/shell.h"
55 #endif // defined(OS_CHROMEOS)
58 #if defined(ENABLE_EXTENSIONS)
59 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
60 #include "chrome/browser/extensions/extension_service.h"
61 #include "extensions/browser/app_window/app_window.h"
62 #include "extensions/browser/app_window/app_window_registry.h"
63 #include "extensions/browser/extension_system.h"
64 #endif
66 using content::BrowserThread;
67 using content::MediaCaptureDevices;
68 using content::MediaStreamDevices;
70 namespace {
72 // A finch experiment to enable the permission bubble for media requests only.
73 bool MediaStreamPermissionBubbleExperimentEnabled() {
74 const std::string group =
75 base::FieldTrialList::FindFullName("MediaStreamPermissionBubble");
76 if (group == "enabled")
77 return true;
79 return false;
82 // Finds a device in |devices| that has |device_id|, or NULL if not found.
83 const content::MediaStreamDevice* FindDeviceWithId(
84 const content::MediaStreamDevices& devices,
85 const std::string& device_id) {
86 content::MediaStreamDevices::const_iterator iter = devices.begin();
87 for (; iter != devices.end(); ++iter) {
88 if (iter->id == device_id) {
89 return &(*iter);
92 return NULL;
95 // This is a short-term solution to grant camera and/or microphone access to
96 // extensions:
97 // 1. Virtual keyboard extension.
98 // 2. Google Voice Search Hotword extension.
99 // 3. Flutter gesture recognition extension.
100 // 4. TODO(smus): Airbender experiment 1.
101 // 5. TODO(smus): Airbender experiment 2.
102 // 6. Hotwording component extension.
103 // Once http://crbug.com/292856 is fixed, remove this whitelist.
104 bool IsMediaRequestWhitelistedForExtension(
105 const extensions::Extension* extension) {
106 return extension->id() == "mppnpdlheglhdfmldimlhpnegondlapf" ||
107 extension->id() == "bepbmhgboaologfdajaanbcjmnhjmhfn" ||
108 extension->id() == "jokbpnebhdcladagohdnfgjcpejggllo" ||
109 extension->id() == "clffjmdilanldobdnedchkdbofoimcgb" ||
110 extension->id() == "nnckehldicaciogcbchegobnafnjkcne" ||
111 extension->id() == "nbpagnldghgfoolbancepceaanlmhfmd";
114 bool IsBuiltInExtension(const GURL& origin) {
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/";
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 (extension) {
144 title = extension->name();
145 } else {
146 GURL url = web_contents->GetURL();
147 title = url.SchemeIsSecure() ? net::GetHostAndOptionalPort(url)
148 : url.GetOrigin().spec();
150 return base::UTF8ToUTF16(title);
153 // Helper to get list of media stream devices for desktop capture in |devices|.
154 // Registers to display notification if |display_notification| is true.
155 // Returns an instance of MediaStreamUI to be passed to content layer.
156 scoped_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture(
157 content::MediaStreamDevices& devices,
158 content::DesktopMediaID media_id,
159 bool capture_audio,
160 bool display_notification,
161 const base::string16& application_title,
162 const base::string16& registered_extension_name) {
163 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
164 scoped_ptr<content::MediaStreamUI> ui;
166 // Add selected desktop source to the list.
167 devices.push_back(content::MediaStreamDevice(
168 content::MEDIA_DESKTOP_VIDEO_CAPTURE, media_id.ToString(), "Screen"));
169 if (capture_audio) {
170 // Use the special loopback device ID for system audio capture.
171 devices.push_back(content::MediaStreamDevice(
172 content::MEDIA_LOOPBACK_AUDIO_CAPTURE,
173 media::AudioManagerBase::kLoopbackInputDeviceId, "System Audio"));
176 // If required, register to display the notification for stream capture.
177 if (display_notification) {
178 if (application_title == registered_extension_name) {
179 ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
180 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT,
181 application_title));
182 } else {
183 ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
184 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
185 registered_extension_name,
186 application_title));
190 return ui.Pass();
193 #if !defined(OS_ANDROID)
194 // Find browser or app window from a given |web_contents|.
195 gfx::NativeWindow FindParentWindowForWebContents(
196 content::WebContents* web_contents) {
197 Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
198 if (browser && browser->window())
199 return browser->window()->GetNativeWindow();
201 const extensions::AppWindowRegistry::AppWindowList& window_list =
202 extensions::AppWindowRegistry::Get(
203 web_contents->GetBrowserContext())->app_windows();
204 for (extensions::AppWindowRegistry::AppWindowList::const_iterator iter =
205 window_list.begin();
206 iter != window_list.end(); ++iter) {
207 if ((*iter)->web_contents() == web_contents)
208 return (*iter)->GetNativeWindow();
211 return NULL;
213 #endif
215 const extensions::Extension* GetExtensionForOrigin(
216 Profile* profile,
217 const GURL& security_origin) {
218 #if defined(ENABLE_EXTENSIONS)
219 if (!security_origin.SchemeIs(extensions::kExtensionScheme))
220 return NULL;
222 ExtensionService* extensions_service =
223 extensions::ExtensionSystem::Get(profile)->extension_service();
224 const extensions::Extension* extension =
225 extensions_service->extensions()->GetByID(security_origin.host());
226 DCHECK(extension);
227 return extension;
228 #else
229 return NULL;
230 #endif
233 } // namespace
235 MediaCaptureDevicesDispatcher::PendingAccessRequest::PendingAccessRequest(
236 const content::MediaStreamRequest& request,
237 const content::MediaResponseCallback& callback)
238 : request(request),
239 callback(callback) {
242 MediaCaptureDevicesDispatcher::PendingAccessRequest::~PendingAccessRequest() {}
244 MediaCaptureDevicesDispatcher* MediaCaptureDevicesDispatcher::GetInstance() {
245 return Singleton<MediaCaptureDevicesDispatcher>::get();
248 MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher()
249 : is_device_enumeration_disabled_(false),
250 media_stream_capture_indicator_(new MediaStreamCaptureIndicator()) {
251 // MediaCaptureDevicesDispatcher is a singleton. It should be created on
252 // UI thread. Otherwise, it will not receive
253 // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in
254 // possible use after free.
255 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
256 notifications_registrar_.Add(
257 this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
258 content::NotificationService::AllSources());
260 #if defined(OS_MACOSX)
261 // AVFoundation is used for video/audio device monitoring and video capture.
262 if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kForceQTKit)) {
263 CommandLine::ForCurrentProcess()->AppendSwitch(
264 switches::kEnableAVFoundation);
266 #endif
269 MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {}
271 void MediaCaptureDevicesDispatcher::RegisterProfilePrefs(
272 user_prefs::PrefRegistrySyncable* registry) {
273 registry->RegisterStringPref(
274 prefs::kDefaultAudioCaptureDevice,
275 std::string(),
276 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
277 registry->RegisterStringPref(
278 prefs::kDefaultVideoCaptureDevice,
279 std::string(),
280 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
283 void MediaCaptureDevicesDispatcher::AddObserver(Observer* observer) {
284 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
285 if (!observers_.HasObserver(observer))
286 observers_.AddObserver(observer);
289 void MediaCaptureDevicesDispatcher::RemoveObserver(Observer* observer) {
290 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
291 observers_.RemoveObserver(observer);
294 const MediaStreamDevices&
295 MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() {
296 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
297 if (is_device_enumeration_disabled_ || !test_audio_devices_.empty())
298 return test_audio_devices_;
300 return MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices();
303 const MediaStreamDevices&
304 MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() {
305 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
306 if (is_device_enumeration_disabled_ || !test_video_devices_.empty())
307 return test_video_devices_;
309 return MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices();
312 void MediaCaptureDevicesDispatcher::Observe(
313 int type,
314 const content::NotificationSource& source,
315 const content::NotificationDetails& details) {
316 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
317 if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) {
318 content::WebContents* web_contents =
319 content::Source<content::WebContents>(source).ptr();
320 pending_requests_.erase(web_contents);
324 void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequest(
325 content::WebContents* web_contents,
326 const content::MediaStreamRequest& request,
327 const content::MediaResponseCallback& callback,
328 const extensions::Extension* extension) {
329 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
331 if (request.video_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE ||
332 request.audio_type == content::MEDIA_LOOPBACK_AUDIO_CAPTURE) {
333 ProcessDesktopCaptureAccessRequest(
334 web_contents, request, callback, extension);
335 } else if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE ||
336 request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE) {
337 ProcessTabCaptureAccessRequest(
338 web_contents, request, callback, extension);
339 } else if (extension && (extension->is_platform_app() ||
340 IsMediaRequestWhitelistedForExtension(extension))) {
341 // For extensions access is approved based on extension permissions.
342 ProcessMediaAccessRequestFromPlatformAppOrExtension(
343 web_contents, request, callback, extension);
344 } else {
345 ProcessRegularMediaAccessRequest(web_contents, request, callback);
349 bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
350 content::BrowserContext* browser_context,
351 const GURL& security_origin,
352 content::MediaStreamType type) {
353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
354 DCHECK(type == content::MEDIA_DEVICE_AUDIO_CAPTURE ||
355 type == content::MEDIA_DEVICE_VIDEO_CAPTURE);
357 Profile* profile = Profile::FromBrowserContext(browser_context);
358 const extensions::Extension* extension =
359 GetExtensionForOrigin(profile, security_origin);
361 if (extension && (extension->is_platform_app() ||
362 IsMediaRequestWhitelistedForExtension(extension))) {
363 return extension->permissions_data()->HasAPIPermission(
364 type == content::MEDIA_DEVICE_AUDIO_CAPTURE
365 ? extensions::APIPermission::kAudioCapture
366 : extensions::APIPermission::kVideoCapture);
369 if (CheckAllowAllMediaStreamContentForOrigin(profile, security_origin))
370 return true;
372 const char* policy_name = type == content::MEDIA_DEVICE_AUDIO_CAPTURE
373 ? prefs::kAudioCaptureAllowed
374 : prefs::kVideoCaptureAllowed;
375 const char* list_policy_name = type == content::MEDIA_DEVICE_AUDIO_CAPTURE
376 ? prefs::kAudioCaptureAllowedUrls
377 : prefs::kVideoCaptureAllowedUrls;
378 if (GetDevicePolicy(
379 profile, security_origin, policy_name, list_policy_name) ==
380 ALWAYS_ALLOW) {
381 return true;
384 // There's no secondary URL for these content types, hence duplicating
385 // |security_origin|.
386 if (profile->GetHostContentSettingsMap()->GetContentSetting(
387 security_origin,
388 security_origin,
389 type == content::MEDIA_DEVICE_AUDIO_CAPTURE
390 ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC
391 : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
392 NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_ALLOW) {
393 return true;
396 return false;
399 bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
400 content::WebContents* web_contents,
401 const GURL& security_origin,
402 content::MediaStreamType type) {
403 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
404 DCHECK(type == content::MEDIA_DEVICE_AUDIO_CAPTURE ||
405 type == content::MEDIA_DEVICE_VIDEO_CAPTURE);
407 Profile* profile =
408 Profile::FromBrowserContext(web_contents->GetBrowserContext());
410 if (CheckAllowAllMediaStreamContentForOrigin(profile, security_origin))
411 return true;
413 const char* policy_name = type == content::MEDIA_DEVICE_AUDIO_CAPTURE
414 ? prefs::kAudioCaptureAllowed
415 : prefs::kVideoCaptureAllowed;
416 const char* list_policy_name = type == content::MEDIA_DEVICE_AUDIO_CAPTURE
417 ? prefs::kAudioCaptureAllowedUrls
418 : prefs::kVideoCaptureAllowedUrls;
419 if (GetDevicePolicy(
420 profile, security_origin, policy_name, list_policy_name) ==
421 ALWAYS_ALLOW) {
422 return true;
425 // There's no secondary URL for these content types, hence duplicating
426 // |security_origin|.
427 if (profile->GetHostContentSettingsMap()->GetContentSetting(
428 security_origin,
429 security_origin,
430 type == content::MEDIA_DEVICE_AUDIO_CAPTURE
431 ? CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC
432 : CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
433 NO_RESOURCE_IDENTIFIER) == CONTENT_SETTING_ALLOW) {
434 return true;
437 return false;
440 #if defined(ENABLE_EXTENSIONS)
441 bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission(
442 content::WebContents* web_contents,
443 const GURL& security_origin,
444 content::MediaStreamType type,
445 const extensions::Extension* extension) {
446 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
447 DCHECK(type == content::MEDIA_DEVICE_AUDIO_CAPTURE ||
448 type == content::MEDIA_DEVICE_VIDEO_CAPTURE);
450 if (extension->is_platform_app() ||
451 IsMediaRequestWhitelistedForExtension(extension)) {
452 return extension->permissions_data()->HasAPIPermission(
453 type == content::MEDIA_DEVICE_AUDIO_CAPTURE
454 ? extensions::APIPermission::kAudioCapture
455 : extensions::APIPermission::kVideoCapture);
458 return CheckMediaAccessPermission(web_contents, security_origin, type);
460 #endif
462 void MediaCaptureDevicesDispatcher::ProcessDesktopCaptureAccessRequest(
463 content::WebContents* web_contents,
464 const content::MediaStreamRequest& request,
465 const content::MediaResponseCallback& callback,
466 const extensions::Extension* extension) {
467 content::MediaStreamDevices devices;
468 scoped_ptr<content::MediaStreamUI> ui;
470 if (request.video_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE) {
471 callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass());
472 return;
475 // If the device id wasn't specified then this is a screen capture request
476 // (i.e. chooseDesktopMedia() API wasn't used to generate device id).
477 if (request.requested_video_device_id.empty()) {
478 ProcessScreenCaptureAccessRequest(
479 web_contents, request, callback, extension);
480 return;
483 // The extension name that the stream is registered with.
484 std::string original_extension_name;
485 // Resolve DesktopMediaID for the specified device id.
486 content::DesktopMediaID media_id;
487 // TODO(miu): Replace "main RenderFrame" IDs with the request's actual
488 // RenderFrame IDs once the desktop capture extension API implementation is
489 // fixed. http://crbug.com/304341
490 content::WebContents* const web_contents_for_stream =
491 content::WebContents::FromRenderFrameHost(
492 content::RenderFrameHost::FromID(request.render_process_id,
493 request.render_frame_id));
494 content::RenderFrameHost* const main_frame = web_contents_for_stream ?
495 web_contents_for_stream->GetMainFrame() : NULL;
496 if (main_frame) {
497 media_id = GetDesktopStreamsRegistry()->RequestMediaForStreamId(
498 request.requested_video_device_id,
499 main_frame->GetProcess()->GetID(),
500 main_frame->GetRoutingID(),
501 request.security_origin,
502 &original_extension_name);
505 // Received invalid device id.
506 if (media_id.type == content::DesktopMediaID::TYPE_NONE) {
507 callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass());
508 return;
511 bool loopback_audio_supported = false;
512 #if defined(USE_CRAS) || defined(OS_WIN)
513 // Currently loopback audio capture is supported only on Windows and ChromeOS.
514 loopback_audio_supported = true;
515 #endif
517 // Audio is only supported for screen capture streams.
518 bool capture_audio =
519 (media_id.type == content::DesktopMediaID::TYPE_SCREEN &&
520 request.audio_type == content::MEDIA_LOOPBACK_AUDIO_CAPTURE &&
521 loopback_audio_supported);
523 ui = GetDevicesForDesktopCapture(
524 devices, media_id, capture_audio, true,
525 GetApplicationTitle(web_contents, extension),
526 base::UTF8ToUTF16(original_extension_name));
528 callback.Run(devices, content::MEDIA_DEVICE_OK, ui.Pass());
531 void MediaCaptureDevicesDispatcher::ProcessScreenCaptureAccessRequest(
532 content::WebContents* web_contents,
533 const content::MediaStreamRequest& request,
534 const content::MediaResponseCallback& callback,
535 const extensions::Extension* extension) {
536 content::MediaStreamDevices devices;
537 scoped_ptr<content::MediaStreamUI> ui;
539 DCHECK_EQ(request.video_type, content::MEDIA_DESKTOP_VIDEO_CAPTURE);
541 bool loopback_audio_supported = false;
542 #if defined(USE_CRAS) || defined(OS_WIN)
543 // Currently loopback audio capture is supported only on Windows and ChromeOS.
544 loopback_audio_supported = true;
545 #endif
547 const bool component_extension =
548 extension && extension->location() == extensions::Manifest::COMPONENT;
550 const bool screen_capture_enabled =
551 CommandLine::ForCurrentProcess()->HasSwitch(
552 switches::kEnableUserMediaScreenCapturing) ||
553 IsOriginForCasting(request.security_origin) ||
554 IsBuiltInExtension(request.security_origin);
556 const bool origin_is_secure =
557 request.security_origin.SchemeIsSecure() ||
558 request.security_origin.SchemeIs(extensions::kExtensionScheme) ||
559 CommandLine::ForCurrentProcess()->HasSwitch(
560 switches::kAllowHttpScreenCapture);
562 // If basic conditions (screen capturing is enabled and origin is secure)
563 // aren't fulfilled, we'll use "invalid state" as result. Otherwise, we set
564 // it after checking permission.
565 // TODO(grunell): It would be good to change this result for something else,
566 // probably a new one.
567 content::MediaStreamRequestResult result =
568 content::MEDIA_DEVICE_INVALID_STATE;
570 // Approve request only when the following conditions are met:
571 // 1. Screen capturing is enabled via command line switch or white-listed for
572 // the given origin.
573 // 2. Request comes from a page with a secure origin or from an extension.
574 if (screen_capture_enabled && origin_is_secure) {
575 // Get title of the calling application prior to showing the message box.
576 // chrome::ShowMessageBox() starts a nested message loop which may allow
577 // |web_contents| to be destroyed on the UI thread before the message box
578 // is closed. See http://crbug.com/326690.
579 base::string16 application_title =
580 GetApplicationTitle(web_contents, extension);
581 #if !defined(OS_ANDROID)
582 gfx::NativeWindow parent_window =
583 FindParentWindowForWebContents(web_contents);
584 #else
585 gfx::NativeWindow parent_window = NULL;
586 #endif
587 web_contents = NULL;
589 // For component extensions, bypass message box.
590 bool user_approved = false;
591 if (!component_extension) {
592 base::string16 application_name = base::UTF8ToUTF16(
593 extension ? extension->name() : request.security_origin.spec());
594 base::string16 confirmation_text = l10n_util::GetStringFUTF16(
595 request.audio_type == content::MEDIA_NO_SERVICE ?
596 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT :
597 IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT,
598 application_name);
599 chrome::MessageBoxResult result = chrome::ShowMessageBox(
600 parent_window,
601 l10n_util::GetStringFUTF16(
602 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name),
603 confirmation_text,
604 chrome::MESSAGE_BOX_TYPE_QUESTION);
605 user_approved = (result == chrome::MESSAGE_BOX_RESULT_YES);
608 if (user_approved || component_extension) {
609 content::DesktopMediaID screen_id;
610 #if defined(OS_CHROMEOS)
611 screen_id = content::DesktopMediaID::RegisterAuraWindow(
612 ash::Shell::GetInstance()->GetPrimaryRootWindow());
613 #else // defined(OS_CHROMEOS)
614 screen_id =
615 content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
616 webrtc::kFullDesktopScreenId);
617 #endif // !defined(OS_CHROMEOS)
619 bool capture_audio =
620 (request.audio_type == content::MEDIA_LOOPBACK_AUDIO_CAPTURE &&
621 loopback_audio_supported);
623 // Unless we're being invoked from a component extension, register to
624 // display the notification for stream capture.
625 bool display_notification = !component_extension;
627 ui = GetDevicesForDesktopCapture(devices, screen_id, capture_audio,
628 display_notification, application_title,
629 application_title);
630 DCHECK(!devices.empty());
633 // The only case when devices can be empty is if the user has denied
634 // permission.
635 result = devices.empty() ? content::MEDIA_DEVICE_PERMISSION_DENIED
636 : content::MEDIA_DEVICE_OK;
639 callback.Run(devices, result, ui.Pass());
642 void MediaCaptureDevicesDispatcher::ProcessTabCaptureAccessRequest(
643 content::WebContents* web_contents,
644 const content::MediaStreamRequest& request,
645 const content::MediaResponseCallback& callback,
646 const extensions::Extension* extension) {
647 content::MediaStreamDevices devices;
648 scoped_ptr<content::MediaStreamUI> ui;
650 #if defined(ENABLE_EXTENSIONS)
651 Profile* profile =
652 Profile::FromBrowserContext(web_contents->GetBrowserContext());
653 extensions::TabCaptureRegistry* tab_capture_registry =
654 extensions::TabCaptureRegistry::Get(profile);
655 if (!tab_capture_registry) {
656 NOTREACHED();
657 callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass());
658 return;
660 const bool tab_capture_allowed = tab_capture_registry->VerifyRequest(
661 request.render_process_id, request.render_frame_id, extension->id());
663 if (request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE &&
664 tab_capture_allowed &&
665 extension->permissions_data()->HasAPIPermission(
666 extensions::APIPermission::kTabCapture)) {
667 devices.push_back(content::MediaStreamDevice(
668 content::MEDIA_TAB_AUDIO_CAPTURE, std::string(), std::string()));
671 if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE &&
672 tab_capture_allowed &&
673 extension->permissions_data()->HasAPIPermission(
674 extensions::APIPermission::kTabCapture)) {
675 devices.push_back(content::MediaStreamDevice(
676 content::MEDIA_TAB_VIDEO_CAPTURE, std::string(), std::string()));
679 if (!devices.empty()) {
680 ui = media_stream_capture_indicator_->RegisterMediaStream(
681 web_contents, devices);
683 callback.Run(
684 devices,
685 devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE :
686 content::MEDIA_DEVICE_OK,
687 ui.Pass());
688 #else // defined(ENABLE_EXTENSIONS)
689 callback.Run(devices, content::MEDIA_DEVICE_TAB_CAPTURE_FAILURE, ui.Pass());
690 #endif // defined(ENABLE_EXTENSIONS)
693 void MediaCaptureDevicesDispatcher::
694 ProcessMediaAccessRequestFromPlatformAppOrExtension(
695 content::WebContents* web_contents,
696 const content::MediaStreamRequest& request,
697 const content::MediaResponseCallback& callback,
698 const extensions::Extension* extension) {
699 // TODO(vrk): This code is largely duplicated in
700 // MediaStreamDevicesController::Accept(). Move this code into a shared method
701 // between the two classes.
703 Profile* profile =
704 Profile::FromBrowserContext(web_contents->GetBrowserContext());
706 bool audio_allowed =
707 request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE &&
708 extension->permissions_data()->HasAPIPermission(
709 extensions::APIPermission::kAudioCapture) &&
710 GetDevicePolicy(profile, extension->url(),
711 prefs::kAudioCaptureAllowed,
712 prefs::kAudioCaptureAllowedUrls) != ALWAYS_DENY;
713 bool video_allowed =
714 request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE &&
715 extension->permissions_data()->HasAPIPermission(
716 extensions::APIPermission::kVideoCapture) &&
717 GetDevicePolicy(profile, extension->url(),
718 prefs::kVideoCaptureAllowed,
719 prefs::kVideoCaptureAllowedUrls) != ALWAYS_DENY;
721 bool get_default_audio_device = audio_allowed;
722 bool get_default_video_device = video_allowed;
724 content::MediaStreamDevices devices;
726 // Set an initial error result. If neither audio or video is allowed, we'll
727 // never try to get any device below but will just create |ui| and return an
728 // empty list with "invalid state" result. If at least one is allowed, we'll
729 // try to get device(s), and if failure, we want to return "no hardware"
730 // result.
731 // TODO(grunell): The invalid state result should be changed to a new denied
732 // result + a dcheck to ensure at least one of audio or video types is
733 // capture.
734 content::MediaStreamRequestResult result =
735 (audio_allowed || video_allowed) ? content::MEDIA_DEVICE_NO_HARDWARE
736 : content::MEDIA_DEVICE_INVALID_STATE;
738 // Get the exact audio or video device if an id is specified.
739 // We only set any error result here and before running the callback change
740 // it to OK if we have any device.
741 if (audio_allowed && !request.requested_audio_device_id.empty()) {
742 const content::MediaStreamDevice* audio_device =
743 GetRequestedAudioDevice(request.requested_audio_device_id);
744 if (audio_device) {
745 devices.push_back(*audio_device);
746 get_default_audio_device = false;
749 if (video_allowed && !request.requested_video_device_id.empty()) {
750 const content::MediaStreamDevice* video_device =
751 GetRequestedVideoDevice(request.requested_video_device_id);
752 if (video_device) {
753 devices.push_back(*video_device);
754 get_default_video_device = false;
758 // If either or both audio and video devices were requested but not
759 // specified by id, get the default devices.
760 if (get_default_audio_device || get_default_video_device) {
761 GetDefaultDevicesForProfile(profile,
762 get_default_audio_device,
763 get_default_video_device,
764 &devices);
767 scoped_ptr<content::MediaStreamUI> ui;
768 if (!devices.empty()) {
769 result = content::MEDIA_DEVICE_OK;
770 ui = media_stream_capture_indicator_->RegisterMediaStream(
771 web_contents, devices);
774 callback.Run(devices, result, ui.Pass());
777 void MediaCaptureDevicesDispatcher::ProcessRegularMediaAccessRequest(
778 content::WebContents* web_contents,
779 const content::MediaStreamRequest& request,
780 const content::MediaResponseCallback& callback) {
781 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
783 RequestsQueue& queue = pending_requests_[web_contents];
784 queue.push_back(PendingAccessRequest(request, callback));
786 // If this is the only request then show the infobar.
787 if (queue.size() == 1)
788 ProcessQueuedAccessRequest(web_contents);
791 void MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest(
792 content::WebContents* web_contents) {
793 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
795 std::map<content::WebContents*, RequestsQueue>::iterator it =
796 pending_requests_.find(web_contents);
798 if (it == pending_requests_.end() || it->second.empty()) {
799 // Don't do anything if the tab was closed.
800 return;
803 DCHECK(!it->second.empty());
805 if (PermissionBubbleManager::Enabled() ||
806 MediaStreamPermissionBubbleExperimentEnabled()) {
807 scoped_ptr<MediaStreamDevicesController> controller(
808 new MediaStreamDevicesController(web_contents,
809 it->second.front().request,
810 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse,
811 base::Unretained(this), web_contents)));
812 if (controller->DismissInfoBarAndTakeActionOnSettings())
813 return;
814 PermissionBubbleManager* bubble_manager =
815 PermissionBubbleManager::FromWebContents(web_contents);
816 if (bubble_manager)
817 bubble_manager->AddRequest(controller.release());
818 return;
821 // TODO(gbillock): delete this block and the MediaStreamInfoBarDelegate
822 // when we've transitioned to bubbles. (crbug/337458)
823 MediaStreamInfoBarDelegate::Create(
824 web_contents, it->second.front().request,
825 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse,
826 base::Unretained(this), web_contents));
829 void MediaCaptureDevicesDispatcher::OnAccessRequestResponse(
830 content::WebContents* web_contents,
831 const content::MediaStreamDevices& devices,
832 content::MediaStreamRequestResult result,
833 scoped_ptr<content::MediaStreamUI> ui) {
834 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
836 std::map<content::WebContents*, RequestsQueue>::iterator it =
837 pending_requests_.find(web_contents);
838 if (it == pending_requests_.end()) {
839 // WebContents has been destroyed. Don't need to do anything.
840 return;
843 RequestsQueue& queue(it->second);
844 if (queue.empty())
845 return;
847 content::MediaResponseCallback callback = queue.front().callback;
848 queue.pop_front();
850 if (!queue.empty()) {
851 // Post a task to process next queued request. It has to be done
852 // asynchronously to make sure that calling infobar is not destroyed until
853 // after this function returns.
854 BrowserThread::PostTask(
855 BrowserThread::UI, FROM_HERE,
856 base::Bind(&MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest,
857 base::Unretained(this), web_contents));
860 callback.Run(devices, result, ui.Pass());
863 void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile(
864 Profile* profile,
865 bool audio,
866 bool video,
867 content::MediaStreamDevices* devices) {
868 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
869 DCHECK(audio || video);
871 PrefService* prefs = profile->GetPrefs();
872 std::string default_device;
873 if (audio) {
874 default_device = prefs->GetString(prefs::kDefaultAudioCaptureDevice);
875 const content::MediaStreamDevice* device =
876 GetRequestedAudioDevice(default_device);
877 if (!device)
878 device = GetFirstAvailableAudioDevice();
879 if (device)
880 devices->push_back(*device);
883 if (video) {
884 default_device = prefs->GetString(prefs::kDefaultVideoCaptureDevice);
885 const content::MediaStreamDevice* device =
886 GetRequestedVideoDevice(default_device);
887 if (!device)
888 device = GetFirstAvailableVideoDevice();
889 if (device)
890 devices->push_back(*device);
894 const content::MediaStreamDevice*
895 MediaCaptureDevicesDispatcher::GetRequestedAudioDevice(
896 const std::string& requested_audio_device_id) {
897 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
898 const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
899 const content::MediaStreamDevice* const device =
900 FindDeviceWithId(audio_devices, requested_audio_device_id);
901 return device;
904 const content::MediaStreamDevice*
905 MediaCaptureDevicesDispatcher::GetFirstAvailableAudioDevice() {
906 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
907 const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
908 if (audio_devices.empty())
909 return NULL;
910 return &(*audio_devices.begin());
913 const content::MediaStreamDevice*
914 MediaCaptureDevicesDispatcher::GetRequestedVideoDevice(
915 const std::string& requested_video_device_id) {
916 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
917 const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices();
918 const content::MediaStreamDevice* const device =
919 FindDeviceWithId(video_devices, requested_video_device_id);
920 return device;
923 const content::MediaStreamDevice*
924 MediaCaptureDevicesDispatcher::GetFirstAvailableVideoDevice() {
925 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
926 const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices();
927 if (video_devices.empty())
928 return NULL;
929 return &(*video_devices.begin());
932 void MediaCaptureDevicesDispatcher::DisableDeviceEnumerationForTesting() {
933 is_device_enumeration_disabled_ = true;
936 scoped_refptr<MediaStreamCaptureIndicator>
937 MediaCaptureDevicesDispatcher::GetMediaStreamCaptureIndicator() {
938 return media_stream_capture_indicator_;
941 DesktopStreamsRegistry*
942 MediaCaptureDevicesDispatcher::GetDesktopStreamsRegistry() {
943 if (!desktop_streams_registry_)
944 desktop_streams_registry_.reset(new DesktopStreamsRegistry());
945 return desktop_streams_registry_.get();
948 void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged() {
949 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
950 BrowserThread::PostTask(
951 BrowserThread::UI, FROM_HERE,
952 base::Bind(
953 &MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread,
954 base::Unretained(this)));
957 void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged() {
958 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
959 BrowserThread::PostTask(
960 BrowserThread::UI, FROM_HERE,
961 base::Bind(
962 &MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread,
963 base::Unretained(this)));
966 void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged(
967 int render_process_id,
968 int render_frame_id,
969 int page_request_id,
970 const GURL& security_origin,
971 content::MediaStreamType stream_type,
972 content::MediaRequestState state) {
973 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
974 BrowserThread::PostTask(
975 BrowserThread::UI, FROM_HERE,
976 base::Bind(
977 &MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread,
978 base::Unretained(this), render_process_id, render_frame_id,
979 page_request_id, security_origin, stream_type, state));
982 void MediaCaptureDevicesDispatcher::OnCreatingAudioStream(
983 int render_process_id,
984 int render_frame_id) {
985 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
986 BrowserThread::PostTask(
987 BrowserThread::UI, FROM_HERE,
988 base::Bind(
989 &MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread,
990 base::Unretained(this), render_process_id, render_frame_id));
993 void MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread() {
994 MediaStreamDevices devices = GetAudioCaptureDevices();
995 FOR_EACH_OBSERVER(Observer, observers_,
996 OnUpdateAudioDevices(devices));
999 void MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread() {
1000 MediaStreamDevices devices = GetVideoCaptureDevices();
1001 FOR_EACH_OBSERVER(Observer, observers_,
1002 OnUpdateVideoDevices(devices));
1005 void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread(
1006 int render_process_id,
1007 int render_frame_id,
1008 int page_request_id,
1009 const GURL& security_origin,
1010 content::MediaStreamType stream_type,
1011 content::MediaRequestState state) {
1012 // Track desktop capture sessions. Tracking is necessary to avoid unbalanced
1013 // session counts since not all requests will reach MEDIA_REQUEST_STATE_DONE,
1014 // but they will all reach MEDIA_REQUEST_STATE_CLOSING.
1015 if (stream_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE) {
1016 if (state == content::MEDIA_REQUEST_STATE_DONE) {
1017 DesktopCaptureSession session = { render_process_id, render_frame_id,
1018 page_request_id };
1019 desktop_capture_sessions_.push_back(session);
1020 } else if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
1021 for (DesktopCaptureSessions::iterator it =
1022 desktop_capture_sessions_.begin();
1023 it != desktop_capture_sessions_.end();
1024 ++it) {
1025 if (it->render_process_id == render_process_id &&
1026 it->render_frame_id == render_frame_id &&
1027 it->page_request_id == page_request_id) {
1028 desktop_capture_sessions_.erase(it);
1029 break;
1035 // Cancel the request.
1036 if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
1037 bool found = false;
1038 for (RequestsQueues::iterator rqs_it = pending_requests_.begin();
1039 rqs_it != pending_requests_.end(); ++rqs_it) {
1040 RequestsQueue& queue = rqs_it->second;
1041 for (RequestsQueue::iterator it = queue.begin();
1042 it != queue.end(); ++it) {
1043 if (it->request.render_process_id == render_process_id &&
1044 it->request.render_frame_id == render_frame_id &&
1045 it->request.page_request_id == page_request_id) {
1046 queue.erase(it);
1047 found = true;
1048 break;
1051 if (found)
1052 break;
1056 #if defined(OS_CHROMEOS)
1057 if (IsOriginForCasting(security_origin) && IsVideoMediaType(stream_type)) {
1058 // Notify ash that casting state has changed.
1059 if (state == content::MEDIA_REQUEST_STATE_DONE) {
1060 ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(true);
1061 } else if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
1062 ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(false);
1065 #endif
1067 FOR_EACH_OBSERVER(Observer, observers_,
1068 OnRequestUpdate(render_process_id,
1069 render_frame_id,
1070 stream_type,
1071 state));
1074 void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread(
1075 int render_process_id,
1076 int render_frame_id) {
1077 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1078 FOR_EACH_OBSERVER(Observer, observers_,
1079 OnCreatingAudioStream(render_process_id, render_frame_id));
1082 bool MediaCaptureDevicesDispatcher::IsDesktopCaptureInProgress() {
1083 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1084 return desktop_capture_sessions_.size() > 0;
1087 void MediaCaptureDevicesDispatcher::SetTestAudioCaptureDevices(
1088 const MediaStreamDevices& devices) {
1089 test_audio_devices_ = devices;
1092 void MediaCaptureDevicesDispatcher::SetTestVideoCaptureDevices(
1093 const MediaStreamDevices& devices) {
1094 test_video_devices_ = devices;