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.
4
5 #include "chrome/browser/media/media_capture_devices_dispatcher.h"
6
7 #include "apps/app_window.h"
8 #include "apps/app_window_registry.h"
9 #include "base/command_line.h"
10 #include "base/logging.h"
11 #include "base/metrics/field_trial.h"
12 #include "base/prefs/pref_service.h"
13 #include "base/prefs/scoped_user_pref_update.h"
14 #include "base/sha1.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
19 #include "chrome/browser/media/desktop_streams_registry.h"
20 #include "chrome/browser/media/media_stream_capture_indicator.h"
21 #include "chrome/browser/media/media_stream_infobar_delegate.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/ui/browser.h"
24 #include "chrome/browser/ui/browser_finder.h"
25 #include "chrome/browser/ui/browser_window.h"
26 #include "chrome/browser/ui/screen_capture_notification_ui.h"
27 #include "chrome/browser/ui/simple_message_box.h"
28 #include "chrome/browser/ui/website_settings/permission_bubble_manager.h"
29 #include "chrome/common/chrome_switches.h"
30 #include "chrome/common/chrome_version_info.h"
31 #include "chrome/common/pref_names.h"
32 #include "components/pref_registry/pref_registry_syncable.h"
33 #include "content/public/browser/browser_thread.h"
34 #include "content/public/browser/desktop_media_id.h"
35 #include "content/public/browser/media_capture_devices.h"
36 #include "content/public/browser/notification_service.h"
37 #include "content/public/browser/notification_source.h"
38 #include "content/public/browser/notification_types.h"
39 #include "content/public/browser/render_frame_host.h"
40 #include "content/public/browser/web_contents.h"
41 #include "content/public/common/media_stream_request.h"
42 #include "extensions/common/constants.h"
43 #include "extensions/common/extension.h"
44 #include "extensions/common/permissions/permissions_data.h"
45 #include "grit/generated_resources.h"
46 #include "media/audio/audio_manager_base.h"
47 #include "media/base/media_switches.h"
48 #include "net/base/net_util.h"
49 #include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h"
50 #include "ui/base/l10n/l10n_util.h"
51
52 #if defined(OS_CHROMEOS)
53 #include "ash/shell.h"
54 #endif // defined(OS_CHROMEOS)
55
56 // Only do audio stream monitoring for platforms that use it for the tab media
57 // indicator UI or the OOM killer.
58 #if !defined(OS_ANDROID) && !defined(OS_IOS)
59 #define AUDIO_STREAM_MONITORING
60 #include "chrome/browser/media/audio_stream_monitor.h"
61 #endif // !defined(OS_ANDROID) && !defined(OS_IOS)
62
63 using content::BrowserThread;
64 using content::MediaCaptureDevices;
65 using content::MediaStreamDevices;
66
67 namespace {
68
69 // A finch experiment to enable the permission bubble for media requests only.
MediaStreamPermissionBubbleExperimentEnabled()70 bool MediaStreamPermissionBubbleExperimentEnabled() {
71 const std::string group =
72 base::FieldTrialList::FindFullName("MediaStreamPermissionBubble");
73 if (group == "enabled")
74 return true;
75
76 return false;
77 }
78
79 // Finds a device in |devices| that has |device_id|, or NULL if not found.
FindDeviceWithId(const content::MediaStreamDevices & devices,const std::string & device_id)80 const content::MediaStreamDevice* FindDeviceWithId(
81 const content::MediaStreamDevices& devices,
82 const std::string& device_id) {
83 content::MediaStreamDevices::const_iterator iter = devices.begin();
84 for (; iter != devices.end(); ++iter) {
85 if (iter->id == device_id) {
86 return &(*iter);
87 }
88 }
89 return NULL;
90 }
91
92 // This is a short-term solution to grant camera and/or microphone access to
93 // extensions:
94 // 1. Virtual keyboard extension.
95 // 2. Google Voice Search Hotword extension.
96 // 3. Flutter gesture recognition extension.
97 // 4. TODO(smus): Airbender experiment 1.
98 // 5. TODO(smus): Airbender experiment 2.
99 // Once http://crbug.com/292856 is fixed, remove this whitelist.
IsMediaRequestWhitelistedForExtension(const extensions::Extension * extension)100 bool IsMediaRequestWhitelistedForExtension(
101 const extensions::Extension* extension) {
102 return extension->id() == "mppnpdlheglhdfmldimlhpnegondlapf" ||
103 extension->id() == "bepbmhgboaologfdajaanbcjmnhjmhfn" ||
104 extension->id() == "jokbpnebhdcladagohdnfgjcpejggllo" ||
105 extension->id() == "clffjmdilanldobdnedchkdbofoimcgb" ||
106 extension->id() == "nnckehldicaciogcbchegobnafnjkcne";
107 }
108
IsBuiltInExtension(const GURL & origin)109 bool IsBuiltInExtension(const GURL& origin) {
110 return
111 // Feedback Extension.
112 origin.spec() == "chrome-extension://gfdkimpbcpahaombhbimeihdjnejgicl/";
113 }
114
115 // Returns true of the security origin is associated with casting.
IsOriginForCasting(const GURL & origin)116 bool IsOriginForCasting(const GURL& origin) {
117 #if defined(OFFICIAL_BUILD)
118 // Whitelisted tab casting extensions.
119 return
120 // Dev
121 origin.spec() == "chrome-extension://enhhojjnijigcajfphajepfemndkmdlo/" ||
122 // Canary
123 origin.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" ||
124 // Beta (internal)
125 origin.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" ||
126 // Google Cast Beta
127 origin.spec() == "chrome-extension://dliochdbjfkdbacpmhlcpmleaejidimm/" ||
128 // Google Cast Stable
129 origin.spec() == "chrome-extension://boadgeojelhgndaghljhdicfkmllpafd/";
130 #else
131 return false;
132 #endif
133 }
134
135 // Helper to get title of the calling application shown in the screen capture
136 // notification.
GetApplicationTitle(content::WebContents * web_contents,const extensions::Extension * extension)137 base::string16 GetApplicationTitle(content::WebContents* web_contents,
138 const extensions::Extension* extension) {
139 // Use extension name as title for extensions and host/origin for drive-by
140 // web.
141 std::string title;
142 if (extension) {
143 title = extension->name();
144 } else {
145 GURL url = web_contents->GetURL();
146 title = url.SchemeIsSecure() ? net::GetHostAndOptionalPort(url)
147 : url.GetOrigin().spec();
148 }
149 return base::UTF8ToUTF16(title);
150 }
151
152 // Helper to get list of media stream devices for desktop capture in |devices|.
153 // Registers to display notification if |display_notification| is true.
154 // Returns an instance of MediaStreamUI to be passed to content layer.
GetDevicesForDesktopCapture(content::MediaStreamDevices & devices,content::DesktopMediaID media_id,bool capture_audio,bool display_notification,const base::string16 & application_title,const base::string16 & registered_extension_name)155 scoped_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture(
156 content::MediaStreamDevices& devices,
157 content::DesktopMediaID media_id,
158 bool capture_audio,
159 bool display_notification,
160 const base::string16& application_title,
161 const base::string16& registered_extension_name) {
162 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
163 scoped_ptr<content::MediaStreamUI> ui;
164
165 // Add selected desktop source to the list.
166 devices.push_back(content::MediaStreamDevice(
167 content::MEDIA_DESKTOP_VIDEO_CAPTURE, media_id.ToString(), "Screen"));
168 if (capture_audio) {
169 // Use the special loopback device ID for system audio capture.
170 devices.push_back(content::MediaStreamDevice(
171 content::MEDIA_LOOPBACK_AUDIO_CAPTURE,
172 media::AudioManagerBase::kLoopbackInputDeviceId, "System Audio"));
173 }
174
175 // If required, register to display the notification for stream capture.
176 if (display_notification) {
177 if (application_title == registered_extension_name) {
178 ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
179 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT,
180 application_title));
181 } else {
182 ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
183 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT_DELEGATED,
184 registered_extension_name,
185 application_title));
186 }
187 }
188
189 return ui.Pass();
190 }
191
192 #if defined(AUDIO_STREAM_MONITORING)
193
AudioStreamMonitorFromRenderFrame(int render_process_id,int render_frame_id)194 AudioStreamMonitor* AudioStreamMonitorFromRenderFrame(
195 int render_process_id,
196 int render_frame_id) {
197 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
198 content::WebContents* const web_contents =
199 content::WebContents::FromRenderFrameHost(
200 content::RenderFrameHost::FromID(render_process_id, render_frame_id));
201 if (!web_contents)
202 return NULL;
203 return AudioStreamMonitor::FromWebContents(web_contents);
204 }
205
StartAudioStreamMonitoringOnUIThread(int render_process_id,int render_frame_id,int stream_id,const AudioStreamMonitor::ReadPowerAndClipCallback & read_power_callback)206 void StartAudioStreamMonitoringOnUIThread(
207 int render_process_id,
208 int render_frame_id,
209 int stream_id,
210 const AudioStreamMonitor::ReadPowerAndClipCallback& read_power_callback) {
211 AudioStreamMonitor* const audio_stream_monitor =
212 AudioStreamMonitorFromRenderFrame(render_process_id, render_frame_id);
213 if (audio_stream_monitor)
214 audio_stream_monitor->StartMonitoringStream(stream_id, read_power_callback);
215 }
216
StopAudioStreamMonitoringOnUIThread(int render_process_id,int render_frame_id,int stream_id)217 void StopAudioStreamMonitoringOnUIThread(
218 int render_process_id,
219 int render_frame_id,
220 int stream_id) {
221 AudioStreamMonitor* const audio_stream_monitor =
222 AudioStreamMonitorFromRenderFrame(render_process_id, render_frame_id);
223 if (audio_stream_monitor)
224 audio_stream_monitor->StopMonitoringStream(stream_id);
225 }
226
227 #endif // defined(AUDIO_STREAM_MONITORING)
228
229 #if !defined(OS_ANDROID)
230 // Find browser or app window from a given |web_contents|.
FindParentWindowForWebContents(content::WebContents * web_contents)231 gfx::NativeWindow FindParentWindowForWebContents(
232 content::WebContents* web_contents) {
233 Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
234 if (browser && browser->window())
235 return browser->window()->GetNativeWindow();
236
237 const apps::AppWindowRegistry::AppWindowList& window_list =
238 apps::AppWindowRegistry::Get(
239 web_contents->GetBrowserContext())->app_windows();
240 for (apps::AppWindowRegistry::AppWindowList::const_iterator iter =
241 window_list.begin();
242 iter != window_list.end(); ++iter) {
243 if ((*iter)->web_contents() == web_contents)
244 return (*iter)->GetNativeWindow();
245 }
246
247 return NULL;
248 }
249 #endif
250
251 } // namespace
252
PendingAccessRequest(const content::MediaStreamRequest & request,const content::MediaResponseCallback & callback)253 MediaCaptureDevicesDispatcher::PendingAccessRequest::PendingAccessRequest(
254 const content::MediaStreamRequest& request,
255 const content::MediaResponseCallback& callback)
256 : request(request),
257 callback(callback) {
258 }
259
~PendingAccessRequest()260 MediaCaptureDevicesDispatcher::PendingAccessRequest::~PendingAccessRequest() {}
261
GetInstance()262 MediaCaptureDevicesDispatcher* MediaCaptureDevicesDispatcher::GetInstance() {
263 return Singleton<MediaCaptureDevicesDispatcher>::get();
264 }
265
MediaCaptureDevicesDispatcher()266 MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher()
267 : is_device_enumeration_disabled_(false),
268 media_stream_capture_indicator_(new MediaStreamCaptureIndicator()) {
269 // MediaCaptureDevicesDispatcher is a singleton. It should be created on
270 // UI thread. Otherwise, it will not receive
271 // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in
272 // possible use after free.
273 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
274 notifications_registrar_.Add(
275 this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
276 content::NotificationService::AllSources());
277
278 // AVFoundation is used for video/audio device monitoring and video capture in
279 // Mac. Experimentally, connect it in Canary and Unkown (developer builds).
280 #if defined(OS_MACOSX)
281 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
282 if (channel == chrome::VersionInfo::CHANNEL_CANARY ||
283 channel == chrome::VersionInfo::CHANNEL_UNKNOWN) {
284 CommandLine::ForCurrentProcess()->AppendSwitch(
285 switches::kEnableAVFoundation);
286 }
287 #endif
288 }
289
~MediaCaptureDevicesDispatcher()290 MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {}
291
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)292 void MediaCaptureDevicesDispatcher::RegisterProfilePrefs(
293 user_prefs::PrefRegistrySyncable* registry) {
294 registry->RegisterStringPref(
295 prefs::kDefaultAudioCaptureDevice,
296 std::string(),
297 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
298 registry->RegisterStringPref(
299 prefs::kDefaultVideoCaptureDevice,
300 std::string(),
301 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
302 }
303
AddObserver(Observer * observer)304 void MediaCaptureDevicesDispatcher::AddObserver(Observer* observer) {
305 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
306 if (!observers_.HasObserver(observer))
307 observers_.AddObserver(observer);
308 }
309
RemoveObserver(Observer * observer)310 void MediaCaptureDevicesDispatcher::RemoveObserver(Observer* observer) {
311 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
312 observers_.RemoveObserver(observer);
313 }
314
315 const MediaStreamDevices&
GetAudioCaptureDevices()316 MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() {
317 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
318 if (is_device_enumeration_disabled_ || !test_audio_devices_.empty())
319 return test_audio_devices_;
320
321 return MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices();
322 }
323
324 const MediaStreamDevices&
GetVideoCaptureDevices()325 MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() {
326 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
327 if (is_device_enumeration_disabled_ || !test_video_devices_.empty())
328 return test_video_devices_;
329
330 return MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices();
331 }
332
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)333 void MediaCaptureDevicesDispatcher::Observe(
334 int type,
335 const content::NotificationSource& source,
336 const content::NotificationDetails& details) {
337 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
338 if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) {
339 content::WebContents* web_contents =
340 content::Source<content::WebContents>(source).ptr();
341 pending_requests_.erase(web_contents);
342 }
343 }
344
ProcessMediaAccessRequest(content::WebContents * web_contents,const content::MediaStreamRequest & request,const content::MediaResponseCallback & callback,const extensions::Extension * extension)345 void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequest(
346 content::WebContents* web_contents,
347 const content::MediaStreamRequest& request,
348 const content::MediaResponseCallback& callback,
349 const extensions::Extension* extension) {
350 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
351
352 if (request.video_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE ||
353 request.audio_type == content::MEDIA_LOOPBACK_AUDIO_CAPTURE) {
354 ProcessDesktopCaptureAccessRequest(
355 web_contents, request, callback, extension);
356 } else if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE ||
357 request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE) {
358 ProcessTabCaptureAccessRequest(
359 web_contents, request, callback, extension);
360 } else if (extension && (extension->is_platform_app() ||
361 IsMediaRequestWhitelistedForExtension(extension))) {
362 // For extensions access is approved based on extension permissions.
363 ProcessMediaAccessRequestFromPlatformAppOrExtension(
364 web_contents, request, callback, extension);
365 } else {
366 ProcessRegularMediaAccessRequest(web_contents, request, callback);
367 }
368 }
369
ProcessDesktopCaptureAccessRequest(content::WebContents * web_contents,const content::MediaStreamRequest & request,const content::MediaResponseCallback & callback,const extensions::Extension * extension)370 void MediaCaptureDevicesDispatcher::ProcessDesktopCaptureAccessRequest(
371 content::WebContents* web_contents,
372 const content::MediaStreamRequest& request,
373 const content::MediaResponseCallback& callback,
374 const extensions::Extension* extension) {
375 content::MediaStreamDevices devices;
376 scoped_ptr<content::MediaStreamUI> ui;
377
378 if (request.video_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE) {
379 callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass());
380 return;
381 }
382
383 // If the device id wasn't specified then this is a screen capture request
384 // (i.e. chooseDesktopMedia() API wasn't used to generate device id).
385 if (request.requested_video_device_id.empty()) {
386 ProcessScreenCaptureAccessRequest(
387 web_contents, request, callback, extension);
388 return;
389 }
390
391 // The extension name that the stream is registered with.
392 std::string original_extension_name;
393 // Resolve DesktopMediaID for the specified device id.
394 content::DesktopMediaID media_id =
395 GetDesktopStreamsRegistry()->RequestMediaForStreamId(
396 request.requested_video_device_id, request.render_process_id,
397 request.render_view_id, request.security_origin,
398 &original_extension_name);
399
400 // Received invalid device id.
401 if (media_id.type == content::DesktopMediaID::TYPE_NONE) {
402 callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass());
403 return;
404 }
405
406 bool loopback_audio_supported = false;
407 #if defined(USE_CRAS) || defined(OS_WIN)
408 // Currently loopback audio capture is supported only on Windows and ChromeOS.
409 loopback_audio_supported = true;
410 #endif
411
412 // Audio is only supported for screen capture streams.
413 bool capture_audio =
414 (media_id.type == content::DesktopMediaID::TYPE_SCREEN &&
415 request.audio_type == content::MEDIA_LOOPBACK_AUDIO_CAPTURE &&
416 loopback_audio_supported);
417
418 ui = GetDevicesForDesktopCapture(
419 devices, media_id, capture_audio, true,
420 GetApplicationTitle(web_contents, extension),
421 base::UTF8ToUTF16(original_extension_name));
422
423 callback.Run(devices, content::MEDIA_DEVICE_OK, ui.Pass());
424 }
425
ProcessScreenCaptureAccessRequest(content::WebContents * web_contents,const content::MediaStreamRequest & request,const content::MediaResponseCallback & callback,const extensions::Extension * extension)426 void MediaCaptureDevicesDispatcher::ProcessScreenCaptureAccessRequest(
427 content::WebContents* web_contents,
428 const content::MediaStreamRequest& request,
429 const content::MediaResponseCallback& callback,
430 const extensions::Extension* extension) {
431 content::MediaStreamDevices devices;
432 scoped_ptr<content::MediaStreamUI> ui;
433
434 DCHECK_EQ(request.video_type, content::MEDIA_DESKTOP_VIDEO_CAPTURE);
435
436 bool loopback_audio_supported = false;
437 #if defined(USE_CRAS) || defined(OS_WIN)
438 // Currently loopback audio capture is supported only on Windows and ChromeOS.
439 loopback_audio_supported = true;
440 #endif
441
442 const bool component_extension =
443 extension && extension->location() == extensions::Manifest::COMPONENT;
444
445 const bool screen_capture_enabled =
446 CommandLine::ForCurrentProcess()->HasSwitch(
447 switches::kEnableUserMediaScreenCapturing) ||
448 IsOriginForCasting(request.security_origin) ||
449 IsBuiltInExtension(request.security_origin);
450
451 const bool origin_is_secure =
452 request.security_origin.SchemeIsSecure() ||
453 request.security_origin.SchemeIs(extensions::kExtensionScheme) ||
454 CommandLine::ForCurrentProcess()->HasSwitch(
455 switches::kAllowHttpScreenCapture);
456
457 // Approve request only when the following conditions are met:
458 // 1. Screen capturing is enabled via command line switch or white-listed for
459 // the given origin.
460 // 2. Request comes from a page with a secure origin or from an extension.
461 if (screen_capture_enabled && origin_is_secure) {
462 // Get title of the calling application prior to showing the message box.
463 // chrome::ShowMessageBox() starts a nested message loop which may allow
464 // |web_contents| to be destroyed on the UI thread before the message box
465 // is closed. See http://crbug.com/326690.
466 base::string16 application_title =
467 GetApplicationTitle(web_contents, extension);
468 #if !defined(OS_ANDROID)
469 gfx::NativeWindow parent_window =
470 FindParentWindowForWebContents(web_contents);
471 #else
472 gfx::NativeWindow parent_window = NULL;
473 #endif
474 web_contents = NULL;
475
476 // For component extensions, bypass message box.
477 bool user_approved = false;
478 if (!component_extension) {
479 base::string16 application_name = base::UTF8ToUTF16(
480 extension ? extension->name() : request.security_origin.spec());
481 base::string16 confirmation_text = l10n_util::GetStringFUTF16(
482 request.audio_type == content::MEDIA_NO_SERVICE ?
483 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT :
484 IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT,
485 application_name);
486 chrome::MessageBoxResult result = chrome::ShowMessageBox(
487 parent_window,
488 l10n_util::GetStringFUTF16(
489 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name),
490 confirmation_text,
491 chrome::MESSAGE_BOX_TYPE_QUESTION);
492 user_approved = (result == chrome::MESSAGE_BOX_RESULT_YES);
493 }
494
495 if (user_approved || component_extension) {
496 content::DesktopMediaID screen_id;
497 #if defined(OS_CHROMEOS)
498 screen_id = content::DesktopMediaID::RegisterAuraWindow(
499 ash::Shell::GetInstance()->GetPrimaryRootWindow());
500 #else // defined(OS_CHROMEOS)
501 screen_id =
502 content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
503 webrtc::kFullDesktopScreenId);
504 #endif // !defined(OS_CHROMEOS)
505
506 bool capture_audio =
507 (request.audio_type == content::MEDIA_LOOPBACK_AUDIO_CAPTURE &&
508 loopback_audio_supported);
509
510 // Unless we're being invoked from a component extension, register to
511 // display the notification for stream capture.
512 bool display_notification = !component_extension;
513
514 ui = GetDevicesForDesktopCapture(devices, screen_id, capture_audio,
515 display_notification, application_title,
516 application_title);
517 }
518 }
519
520 callback.Run(
521 devices,
522 devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE :
523 content::MEDIA_DEVICE_OK,
524 ui.Pass());
525 }
526
ProcessTabCaptureAccessRequest(content::WebContents * web_contents,const content::MediaStreamRequest & request,const content::MediaResponseCallback & callback,const extensions::Extension * extension)527 void MediaCaptureDevicesDispatcher::ProcessTabCaptureAccessRequest(
528 content::WebContents* web_contents,
529 const content::MediaStreamRequest& request,
530 const content::MediaResponseCallback& callback,
531 const extensions::Extension* extension) {
532 content::MediaStreamDevices devices;
533 scoped_ptr<content::MediaStreamUI> ui;
534
535 #if defined(OS_ANDROID)
536 // Tab capture is not supported on Android.
537 callback.Run(devices, content::MEDIA_DEVICE_TAB_CAPTURE_FAILURE, ui.Pass());
538 #else // defined(OS_ANDROID)
539 Profile* profile =
540 Profile::FromBrowserContext(web_contents->GetBrowserContext());
541 extensions::TabCaptureRegistry* tab_capture_registry =
542 extensions::TabCaptureRegistry::Get(profile);
543 if (!tab_capture_registry) {
544 NOTREACHED();
545 callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass());
546 return;
547 }
548 bool tab_capture_allowed =
549 tab_capture_registry->VerifyRequest(request.render_process_id,
550 request.render_view_id);
551
552 if (request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE &&
553 tab_capture_allowed &&
554 extension->permissions_data()->HasAPIPermission(
555 extensions::APIPermission::kTabCapture)) {
556 devices.push_back(content::MediaStreamDevice(
557 content::MEDIA_TAB_AUDIO_CAPTURE, std::string(), std::string()));
558 }
559
560 if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE &&
561 tab_capture_allowed &&
562 extension->permissions_data()->HasAPIPermission(
563 extensions::APIPermission::kTabCapture)) {
564 devices.push_back(content::MediaStreamDevice(
565 content::MEDIA_TAB_VIDEO_CAPTURE, std::string(), std::string()));
566 }
567
568 if (!devices.empty()) {
569 ui = media_stream_capture_indicator_->RegisterMediaStream(
570 web_contents, devices);
571 }
572 callback.Run(
573 devices,
574 devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE :
575 content::MEDIA_DEVICE_OK,
576 ui.Pass());
577 #endif // !defined(OS_ANDROID)
578 }
579
580 void MediaCaptureDevicesDispatcher::
ProcessMediaAccessRequestFromPlatformAppOrExtension(content::WebContents * web_contents,const content::MediaStreamRequest & request,const content::MediaResponseCallback & callback,const extensions::Extension * extension)581 ProcessMediaAccessRequestFromPlatformAppOrExtension(
582 content::WebContents* web_contents,
583 const content::MediaStreamRequest& request,
584 const content::MediaResponseCallback& callback,
585 const extensions::Extension* extension) {
586
587 // TODO(vrk): This code is largely duplicated in
588 // MediaStreamDevicesController::Accept(). Move this code into a shared method
589 // between the two classes.
590
591 bool audio_allowed =
592 request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE &&
593 extension->permissions_data()->HasAPIPermission(
594 extensions::APIPermission::kAudioCapture);
595 bool video_allowed =
596 request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE &&
597 extension->permissions_data()->HasAPIPermission(
598 extensions::APIPermission::kVideoCapture);
599
600 bool get_default_audio_device = audio_allowed;
601 bool get_default_video_device = video_allowed;
602
603 content::MediaStreamDevices devices;
604
605 // Get the exact audio or video device if an id is specified.
606 if (audio_allowed && !request.requested_audio_device_id.empty()) {
607 const content::MediaStreamDevice* audio_device =
608 GetRequestedAudioDevice(request.requested_audio_device_id);
609 if (audio_device) {
610 devices.push_back(*audio_device);
611 get_default_audio_device = false;
612 }
613 }
614 if (video_allowed && !request.requested_video_device_id.empty()) {
615 const content::MediaStreamDevice* video_device =
616 GetRequestedVideoDevice(request.requested_video_device_id);
617 if (video_device) {
618 devices.push_back(*video_device);
619 get_default_video_device = false;
620 }
621 }
622
623 // If either or both audio and video devices were requested but not
624 // specified by id, get the default devices.
625 if (get_default_audio_device || get_default_video_device) {
626 Profile* profile =
627 Profile::FromBrowserContext(web_contents->GetBrowserContext());
628 GetDefaultDevicesForProfile(profile,
629 get_default_audio_device,
630 get_default_video_device,
631 &devices);
632 }
633
634 scoped_ptr<content::MediaStreamUI> ui;
635 if (!devices.empty()) {
636 ui = media_stream_capture_indicator_->RegisterMediaStream(
637 web_contents, devices);
638 }
639 callback.Run(
640 devices,
641 devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE :
642 content::MEDIA_DEVICE_OK,
643 ui.Pass());
644 }
645
ProcessRegularMediaAccessRequest(content::WebContents * web_contents,const content::MediaStreamRequest & request,const content::MediaResponseCallback & callback)646 void MediaCaptureDevicesDispatcher::ProcessRegularMediaAccessRequest(
647 content::WebContents* web_contents,
648 const content::MediaStreamRequest& request,
649 const content::MediaResponseCallback& callback) {
650 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
651
652 RequestsQueue& queue = pending_requests_[web_contents];
653 queue.push_back(PendingAccessRequest(request, callback));
654
655 // If this is the only request then show the infobar.
656 if (queue.size() == 1)
657 ProcessQueuedAccessRequest(web_contents);
658 }
659
ProcessQueuedAccessRequest(content::WebContents * web_contents)660 void MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest(
661 content::WebContents* web_contents) {
662 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
663
664 std::map<content::WebContents*, RequestsQueue>::iterator it =
665 pending_requests_.find(web_contents);
666
667 if (it == pending_requests_.end() || it->second.empty()) {
668 // Don't do anything if the tab was closed.
669 return;
670 }
671
672 DCHECK(!it->second.empty());
673
674 if (PermissionBubbleManager::Enabled() ||
675 MediaStreamPermissionBubbleExperimentEnabled()) {
676 scoped_ptr<MediaStreamDevicesController> controller(
677 new MediaStreamDevicesController(web_contents,
678 it->second.front().request,
679 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse,
680 base::Unretained(this), web_contents)));
681 if (controller->DismissInfoBarAndTakeActionOnSettings())
682 return;
683 PermissionBubbleManager* bubble_manager =
684 PermissionBubbleManager::FromWebContents(web_contents);
685 if (bubble_manager)
686 bubble_manager->AddRequest(controller.release());
687 return;
688 }
689
690 // TODO(gbillock): delete this block and the MediaStreamInfoBarDelegate
691 // when we've transitioned to bubbles. (crbug/337458)
692 MediaStreamInfoBarDelegate::Create(
693 web_contents, it->second.front().request,
694 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse,
695 base::Unretained(this), web_contents));
696 }
697
OnAccessRequestResponse(content::WebContents * web_contents,const content::MediaStreamDevices & devices,content::MediaStreamRequestResult result,scoped_ptr<content::MediaStreamUI> ui)698 void MediaCaptureDevicesDispatcher::OnAccessRequestResponse(
699 content::WebContents* web_contents,
700 const content::MediaStreamDevices& devices,
701 content::MediaStreamRequestResult result,
702 scoped_ptr<content::MediaStreamUI> ui) {
703 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
704
705 std::map<content::WebContents*, RequestsQueue>::iterator it =
706 pending_requests_.find(web_contents);
707 if (it == pending_requests_.end()) {
708 // WebContents has been destroyed. Don't need to do anything.
709 return;
710 }
711
712 RequestsQueue& queue(it->second);
713 if (queue.empty())
714 return;
715
716 content::MediaResponseCallback callback = queue.front().callback;
717 queue.pop_front();
718
719 if (!queue.empty()) {
720 // Post a task to process next queued request. It has to be done
721 // asynchronously to make sure that calling infobar is not destroyed until
722 // after this function returns.
723 BrowserThread::PostTask(
724 BrowserThread::UI, FROM_HERE,
725 base::Bind(&MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest,
726 base::Unretained(this), web_contents));
727 }
728
729 callback.Run(devices, result, ui.Pass());
730 }
731
GetDefaultDevicesForProfile(Profile * profile,bool audio,bool video,content::MediaStreamDevices * devices)732 void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile(
733 Profile* profile,
734 bool audio,
735 bool video,
736 content::MediaStreamDevices* devices) {
737 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
738 DCHECK(audio || video);
739
740 PrefService* prefs = profile->GetPrefs();
741 std::string default_device;
742 if (audio) {
743 default_device = prefs->GetString(prefs::kDefaultAudioCaptureDevice);
744 const content::MediaStreamDevice* device =
745 GetRequestedAudioDevice(default_device);
746 if (!device)
747 device = GetFirstAvailableAudioDevice();
748 if (device)
749 devices->push_back(*device);
750 }
751
752 if (video) {
753 default_device = prefs->GetString(prefs::kDefaultVideoCaptureDevice);
754 const content::MediaStreamDevice* device =
755 GetRequestedVideoDevice(default_device);
756 if (!device)
757 device = GetFirstAvailableVideoDevice();
758 if (device)
759 devices->push_back(*device);
760 }
761 }
762
763 const content::MediaStreamDevice*
GetRequestedAudioDevice(const std::string & requested_audio_device_id)764 MediaCaptureDevicesDispatcher::GetRequestedAudioDevice(
765 const std::string& requested_audio_device_id) {
766 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
767 const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
768 const content::MediaStreamDevice* const device =
769 FindDeviceWithId(audio_devices, requested_audio_device_id);
770 return device;
771 }
772
773 const content::MediaStreamDevice*
GetFirstAvailableAudioDevice()774 MediaCaptureDevicesDispatcher::GetFirstAvailableAudioDevice() {
775 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
776 const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices();
777 if (audio_devices.empty())
778 return NULL;
779 return &(*audio_devices.begin());
780 }
781
782 const content::MediaStreamDevice*
GetRequestedVideoDevice(const std::string & requested_video_device_id)783 MediaCaptureDevicesDispatcher::GetRequestedVideoDevice(
784 const std::string& requested_video_device_id) {
785 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
786 const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices();
787 const content::MediaStreamDevice* const device =
788 FindDeviceWithId(video_devices, requested_video_device_id);
789 return device;
790 }
791
792 const content::MediaStreamDevice*
GetFirstAvailableVideoDevice()793 MediaCaptureDevicesDispatcher::GetFirstAvailableVideoDevice() {
794 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
795 const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices();
796 if (video_devices.empty())
797 return NULL;
798 return &(*video_devices.begin());
799 }
800
DisableDeviceEnumerationForTesting()801 void MediaCaptureDevicesDispatcher::DisableDeviceEnumerationForTesting() {
802 is_device_enumeration_disabled_ = true;
803 }
804
805 scoped_refptr<MediaStreamCaptureIndicator>
GetMediaStreamCaptureIndicator()806 MediaCaptureDevicesDispatcher::GetMediaStreamCaptureIndicator() {
807 return media_stream_capture_indicator_;
808 }
809
810 DesktopStreamsRegistry*
GetDesktopStreamsRegistry()811 MediaCaptureDevicesDispatcher::GetDesktopStreamsRegistry() {
812 if (!desktop_streams_registry_)
813 desktop_streams_registry_.reset(new DesktopStreamsRegistry());
814 return desktop_streams_registry_.get();
815 }
816
OnAudioCaptureDevicesChanged()817 void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged() {
818 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
819 BrowserThread::PostTask(
820 BrowserThread::UI, FROM_HERE,
821 base::Bind(
822 &MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread,
823 base::Unretained(this)));
824 }
825
OnVideoCaptureDevicesChanged()826 void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged() {
827 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
828 BrowserThread::PostTask(
829 BrowserThread::UI, FROM_HERE,
830 base::Bind(
831 &MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread,
832 base::Unretained(this)));
833 }
834
OnMediaRequestStateChanged(int render_process_id,int render_view_id,int page_request_id,const GURL & security_origin,const content::MediaStreamDevice & device,content::MediaRequestState state)835 void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged(
836 int render_process_id,
837 int render_view_id,
838 int page_request_id,
839 const GURL& security_origin,
840 const content::MediaStreamDevice& device,
841 content::MediaRequestState state) {
842 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
843 BrowserThread::PostTask(
844 BrowserThread::UI, FROM_HERE,
845 base::Bind(
846 &MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread,
847 base::Unretained(this), render_process_id, render_view_id,
848 page_request_id, security_origin, device, state));
849 }
850
OnAudioStreamPlaying(int render_process_id,int render_frame_id,int stream_id,const ReadPowerAndClipCallback & read_power_callback)851 void MediaCaptureDevicesDispatcher::OnAudioStreamPlaying(
852 int render_process_id,
853 int render_frame_id,
854 int stream_id,
855 const ReadPowerAndClipCallback& read_power_callback) {
856 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
857 #if defined(AUDIO_STREAM_MONITORING)
858 BrowserThread::PostTask(
859 BrowserThread::UI,
860 FROM_HERE,
861 base::Bind(&StartAudioStreamMonitoringOnUIThread,
862 render_process_id,
863 render_frame_id,
864 stream_id,
865 read_power_callback));
866 #endif
867 }
868
OnAudioStreamStopped(int render_process_id,int render_frame_id,int stream_id)869 void MediaCaptureDevicesDispatcher::OnAudioStreamStopped(
870 int render_process_id,
871 int render_frame_id,
872 int stream_id) {
873 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
874 #if defined(AUDIO_STREAM_MONITORING)
875 BrowserThread::PostTask(
876 BrowserThread::UI,
877 FROM_HERE,
878 base::Bind(&StopAudioStreamMonitoringOnUIThread,
879 render_process_id,
880 render_frame_id,
881 stream_id));
882 #endif
883 }
884
OnCreatingAudioStream(int render_process_id,int render_frame_id)885 void MediaCaptureDevicesDispatcher::OnCreatingAudioStream(
886 int render_process_id,
887 int render_frame_id) {
888 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
889 BrowserThread::PostTask(
890 BrowserThread::UI, FROM_HERE,
891 base::Bind(
892 &MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread,
893 base::Unretained(this), render_process_id, render_frame_id));
894 }
895
NotifyAudioDevicesChangedOnUIThread()896 void MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread() {
897 MediaStreamDevices devices = GetAudioCaptureDevices();
898 FOR_EACH_OBSERVER(Observer, observers_,
899 OnUpdateAudioDevices(devices));
900 }
901
NotifyVideoDevicesChangedOnUIThread()902 void MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread() {
903 MediaStreamDevices devices = GetVideoCaptureDevices();
904 FOR_EACH_OBSERVER(Observer, observers_,
905 OnUpdateVideoDevices(devices));
906 }
907
UpdateMediaRequestStateOnUIThread(int render_process_id,int render_view_id,int page_request_id,const GURL & security_origin,const content::MediaStreamDevice & device,content::MediaRequestState state)908 void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread(
909 int render_process_id,
910 int render_view_id,
911 int page_request_id,
912 const GURL& security_origin,
913 const content::MediaStreamDevice& device,
914 content::MediaRequestState state) {
915 // Track desktop capture sessions. Tracking is necessary to avoid unbalanced
916 // session counts since not all requests will reach MEDIA_REQUEST_STATE_DONE,
917 // but they will all reach MEDIA_REQUEST_STATE_CLOSING.
918 if (device.type == content::MEDIA_DESKTOP_VIDEO_CAPTURE) {
919 if (state == content::MEDIA_REQUEST_STATE_DONE) {
920 DesktopCaptureSession session = { render_process_id, render_view_id,
921 page_request_id };
922 desktop_capture_sessions_.push_back(session);
923 } else if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
924 for (DesktopCaptureSessions::iterator it =
925 desktop_capture_sessions_.begin();
926 it != desktop_capture_sessions_.end();
927 ++it) {
928 if (it->render_process_id == render_process_id &&
929 it->render_view_id == render_view_id &&
930 it->page_request_id == page_request_id) {
931 desktop_capture_sessions_.erase(it);
932 break;
933 }
934 }
935 }
936 }
937
938 // Cancel the request.
939 if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
940 bool found = false;
941 for (RequestsQueues::iterator rqs_it = pending_requests_.begin();
942 rqs_it != pending_requests_.end(); ++rqs_it) {
943 RequestsQueue& queue = rqs_it->second;
944 for (RequestsQueue::iterator it = queue.begin();
945 it != queue.end(); ++it) {
946 if (it->request.render_process_id == render_process_id &&
947 it->request.render_view_id == render_view_id &&
948 it->request.page_request_id == page_request_id) {
949 queue.erase(it);
950 found = true;
951 break;
952 }
953 }
954 if (found)
955 break;
956 }
957 }
958
959 #if defined(OS_CHROMEOS)
960 if (IsOriginForCasting(security_origin) && IsVideoMediaType(device.type)) {
961 // Notify ash that casting state has changed.
962 if (state == content::MEDIA_REQUEST_STATE_DONE) {
963 ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(true);
964 } else if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
965 ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(false);
966 }
967 }
968 #endif
969
970 FOR_EACH_OBSERVER(Observer, observers_,
971 OnRequestUpdate(render_process_id,
972 render_view_id,
973 device,
974 state));
975 }
976
OnCreatingAudioStreamOnUIThread(int render_process_id,int render_frame_id)977 void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread(
978 int render_process_id,
979 int render_frame_id) {
980 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
981 FOR_EACH_OBSERVER(Observer, observers_,
982 OnCreatingAudioStream(render_process_id, render_frame_id));
983 #if defined(AUDIO_STREAM_MONITORING)
984 content::WebContents* const web_contents =
985 content::WebContents::FromRenderFrameHost(
986 content::RenderFrameHost::FromID(render_process_id, render_frame_id));
987 if (web_contents) {
988 // Note: Calling CreateForWebContents() multiple times is valid (see usage
989 // info for content::WebContentsUserData).
990 AudioStreamMonitor::CreateForWebContents(web_contents);
991 }
992 #endif
993 }
994
IsDesktopCaptureInProgress()995 bool MediaCaptureDevicesDispatcher::IsDesktopCaptureInProgress() {
996 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
997 return desktop_capture_sessions_.size() > 0;
998 }
999
1000
SetTestAudioCaptureDevices(const MediaStreamDevices & devices)1001 void MediaCaptureDevicesDispatcher::SetTestAudioCaptureDevices(
1002 const MediaStreamDevices& devices) {
1003 test_audio_devices_ = devices;
1004 }
1005
SetTestVideoCaptureDevices(const MediaStreamDevices & devices)1006 void MediaCaptureDevicesDispatcher::SetTestVideoCaptureDevices(
1007 const MediaStreamDevices& devices) {
1008 test_video_devices_ = devices;
1009 }
1010