OLD | NEW |
| (Empty) |
1 // Copyright 2015 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/desktop_capture_access_handler.h" | |
6 | |
7 #include <utility> | |
8 | |
9 #include "base/command_line.h" | |
10 #include "base/strings/string_number_conversions.h" | |
11 #include "base/strings/string_util.h" | |
12 #include "base/strings/utf_string_conversions.h" | |
13 #include "build/build_config.h" | |
14 #include "chrome/browser/media/desktop_streams_registry.h" | |
15 #include "chrome/browser/media/media_capture_devices_dispatcher.h" | |
16 #include "chrome/browser/ui/browser.h" | |
17 #include "chrome/browser/ui/browser_finder.h" | |
18 #include "chrome/browser/ui/browser_window.h" | |
19 #include "chrome/browser/ui/screen_capture_notification_ui.h" | |
20 #include "chrome/browser/ui/simple_message_box.h" | |
21 #include "chrome/common/chrome_switches.h" | |
22 #include "chrome/grit/generated_resources.h" | |
23 #include "content/public/browser/browser_thread.h" | |
24 #include "content/public/browser/desktop_media_id.h" | |
25 #include "content/public/browser/render_frame_host.h" | |
26 #include "content/public/browser/render_process_host.h" | |
27 #include "content/public/common/content_switches.h" | |
28 #include "content/public/common/media_stream_request.h" | |
29 #include "content/public/common/origin_util.h" | |
30 #include "extensions/browser/app_window/app_window.h" | |
31 #include "extensions/browser/app_window/app_window_registry.h" | |
32 #include "extensions/common/constants.h" | |
33 #include "extensions/common/extension.h" | |
34 #include "extensions/common/switches.h" | |
35 #include "media/audio/audio_device_description.h" | |
36 #include "net/base/url_util.h" | |
37 #include "third_party/webrtc/modules/desktop_capture/desktop_capture_types.h" | |
38 #include "ui/base/l10n/l10n_util.h" | |
39 | |
40 #if defined(OS_CHROMEOS) | |
41 #include "ash/shell.h" | |
42 #endif // defined(OS_CHROMEOS) | |
43 | |
44 using content::BrowserThread; | |
45 | |
46 namespace { | |
47 | |
48 // Helper to get title of the calling application shown in the screen capture | |
49 // notification. | |
50 base::string16 GetApplicationTitle(content::WebContents* web_contents, | |
51 const extensions::Extension* extension) { | |
52 // Use extension name as title for extensions and host/origin for drive-by | |
53 // web. | |
54 std::string title; | |
55 if (extension) { | |
56 title = extension->name(); | |
57 return base::UTF8ToUTF16(title); | |
58 } | |
59 GURL url = web_contents->GetURL(); | |
60 title = content::IsOriginSecure(url) ? net::GetHostAndOptionalPort(url) | |
61 : url.GetOrigin().spec(); | |
62 return base::UTF8ToUTF16(title); | |
63 } | |
64 | |
65 base::string16 GetStopSharingUIString( | |
66 const base::string16& application_title, | |
67 const base::string16& registered_extension_name, | |
68 bool capture_audio, | |
69 content::DesktopMediaID::Type capture_type) { | |
70 if (!capture_audio) { | |
71 if (application_title == registered_extension_name) { | |
72 switch (capture_type) { | |
73 case content::DesktopMediaID::TYPE_SCREEN: | |
74 return l10n_util::GetStringFUTF16( | |
75 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT, application_title); | |
76 case content::DesktopMediaID::TYPE_WINDOW: | |
77 return l10n_util::GetStringFUTF16( | |
78 IDS_MEDIA_WINDOW_CAPTURE_NOTIFICATION_TEXT, application_title); | |
79 case content::DesktopMediaID::TYPE_WEB_CONTENTS: | |
80 return l10n_util::GetStringFUTF16( | |
81 IDS_MEDIA_TAB_CAPTURE_NOTIFICATION_TEXT, application_title); | |
82 case content::DesktopMediaID::TYPE_NONE: | |
83 NOTREACHED(); | |
84 } | |
85 } else { | |
86 switch (capture_type) { | |
87 case content::DesktopMediaID::TYPE_SCREEN: | |
88 return l10n_util::GetStringFUTF16( | |
89 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT_DELEGATED, | |
90 registered_extension_name, application_title); | |
91 case content::DesktopMediaID::TYPE_WINDOW: | |
92 return l10n_util::GetStringFUTF16( | |
93 IDS_MEDIA_WINDOW_CAPTURE_NOTIFICATION_TEXT_DELEGATED, | |
94 registered_extension_name, application_title); | |
95 case content::DesktopMediaID::TYPE_WEB_CONTENTS: | |
96 return l10n_util::GetStringFUTF16( | |
97 IDS_MEDIA_TAB_CAPTURE_NOTIFICATION_TEXT_DELEGATED, | |
98 registered_extension_name, application_title); | |
99 case content::DesktopMediaID::TYPE_NONE: | |
100 NOTREACHED(); | |
101 } | |
102 } | |
103 } else { // The case with audio | |
104 if (application_title == registered_extension_name) { | |
105 switch (capture_type) { | |
106 case content::DesktopMediaID::TYPE_SCREEN: | |
107 return l10n_util::GetStringFUTF16( | |
108 IDS_MEDIA_SCREEN_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT, | |
109 application_title); | |
110 case content::DesktopMediaID::TYPE_WEB_CONTENTS: | |
111 return l10n_util::GetStringFUTF16( | |
112 IDS_MEDIA_TAB_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT, | |
113 application_title); | |
114 case content::DesktopMediaID::TYPE_NONE: | |
115 case content::DesktopMediaID::TYPE_WINDOW: | |
116 NOTREACHED(); | |
117 } | |
118 } else { | |
119 switch (capture_type) { | |
120 case content::DesktopMediaID::TYPE_SCREEN: | |
121 return l10n_util::GetStringFUTF16( | |
122 IDS_MEDIA_SCREEN_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT_DELEGATED, | |
123 registered_extension_name, application_title); | |
124 case content::DesktopMediaID::TYPE_WEB_CONTENTS: | |
125 return l10n_util::GetStringFUTF16( | |
126 IDS_MEDIA_TAB_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT_DELEGATED, | |
127 registered_extension_name, application_title); | |
128 case content::DesktopMediaID::TYPE_NONE: | |
129 case content::DesktopMediaID::TYPE_WINDOW: | |
130 NOTREACHED(); | |
131 } | |
132 } | |
133 } | |
134 return base::string16(); | |
135 } | |
136 // Helper to get list of media stream devices for desktop capture in |devices|. | |
137 // Registers to display notification if |display_notification| is true. | |
138 // Returns an instance of MediaStreamUI to be passed to content layer. | |
139 std::unique_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture( | |
140 content::MediaStreamDevices* devices, | |
141 content::DesktopMediaID media_id, | |
142 bool capture_audio, | |
143 bool mute_system_audio, | |
144 bool display_notification, | |
145 const base::string16& application_title, | |
146 const base::string16& registered_extension_name) { | |
147 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
148 std::unique_ptr<content::MediaStreamUI> ui; | |
149 | |
150 // Add selected desktop source to the list. | |
151 devices->push_back(content::MediaStreamDevice( | |
152 content::MEDIA_DESKTOP_VIDEO_CAPTURE, media_id.ToString(), "Screen")); | |
153 if (capture_audio) { | |
154 if (media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS) { | |
155 devices->push_back( | |
156 content::MediaStreamDevice(content::MEDIA_DESKTOP_AUDIO_CAPTURE, | |
157 media_id.ToString(), "Tab audio")); | |
158 } else if (mute_system_audio) { | |
159 // Use the special loopback device ID for system audio capture. | |
160 devices->push_back(content::MediaStreamDevice( | |
161 content::MEDIA_DESKTOP_AUDIO_CAPTURE, | |
162 media::AudioDeviceDescription::kLoopbackWithMuteDeviceId, | |
163 "System Audio")); | |
164 } else { | |
165 // Use the special loopback device ID for system audio capture. | |
166 devices->push_back(content::MediaStreamDevice( | |
167 content::MEDIA_DESKTOP_AUDIO_CAPTURE, | |
168 media::AudioDeviceDescription::kLoopbackInputDeviceId, | |
169 "System Audio")); | |
170 } | |
171 } | |
172 | |
173 // If required, register to display the notification for stream capture. | |
174 if (!display_notification) { | |
175 return ui; | |
176 } | |
177 | |
178 ui = ScreenCaptureNotificationUI::Create(GetStopSharingUIString( | |
179 application_title, registered_extension_name, capture_audio, | |
180 media_id.type)); | |
181 | |
182 return ui; | |
183 } | |
184 | |
185 #if !defined(OS_ANDROID) | |
186 // Find browser or app window from a given |web_contents|. | |
187 gfx::NativeWindow FindParentWindowForWebContents( | |
188 content::WebContents* web_contents) { | |
189 Browser* browser = chrome::FindBrowserWithWebContents(web_contents); | |
190 if (browser && browser->window()) | |
191 return browser->window()->GetNativeWindow(); | |
192 | |
193 const extensions::AppWindowRegistry::AppWindowList& window_list = | |
194 extensions::AppWindowRegistry::Get(web_contents->GetBrowserContext()) | |
195 ->app_windows(); | |
196 for (extensions::AppWindowRegistry::AppWindowList::const_iterator iter = | |
197 window_list.begin(); | |
198 iter != window_list.end(); ++iter) { | |
199 if ((*iter)->web_contents() == web_contents) | |
200 return (*iter)->GetNativeWindow(); | |
201 } | |
202 | |
203 return NULL; | |
204 } | |
205 #endif | |
206 | |
207 } // namespace | |
208 | |
209 DesktopCaptureAccessHandler::DesktopCaptureAccessHandler() { | |
210 } | |
211 | |
212 DesktopCaptureAccessHandler::~DesktopCaptureAccessHandler() { | |
213 } | |
214 | |
215 void DesktopCaptureAccessHandler::ProcessScreenCaptureAccessRequest( | |
216 content::WebContents* web_contents, | |
217 const content::MediaStreamRequest& request, | |
218 const content::MediaResponseCallback& callback, | |
219 const extensions::Extension* extension) { | |
220 content::MediaStreamDevices devices; | |
221 std::unique_ptr<content::MediaStreamUI> ui; | |
222 | |
223 DCHECK_EQ(request.video_type, content::MEDIA_DESKTOP_VIDEO_CAPTURE); | |
224 | |
225 UpdateExtensionTrusted(request, extension); | |
226 | |
227 bool loopback_audio_supported = false; | |
228 #if defined(USE_CRAS) || defined(OS_WIN) | |
229 // Currently loopback audio capture is supported only on Windows and ChromeOS. | |
230 loopback_audio_supported = true; | |
231 #endif | |
232 | |
233 bool component_extension = false; | |
234 component_extension = | |
235 extension && extension->location() == extensions::Manifest::COMPONENT; | |
236 | |
237 bool screen_capture_enabled = | |
238 base::CommandLine::ForCurrentProcess()->HasSwitch( | |
239 switches::kEnableUserMediaScreenCapturing) || | |
240 MediaCaptureDevicesDispatcher::IsOriginForCasting( | |
241 request.security_origin) || | |
242 IsExtensionWhitelistedForScreenCapture(extension) || | |
243 IsBuiltInExtension(request.security_origin); | |
244 | |
245 const bool origin_is_secure = | |
246 content::IsOriginSecure(request.security_origin) || | |
247 base::CommandLine::ForCurrentProcess()->HasSwitch( | |
248 switches::kAllowHttpScreenCapture); | |
249 | |
250 // If basic conditions (screen capturing is enabled and origin is secure) | |
251 // aren't fulfilled, we'll use "invalid state" as result. Otherwise, we set | |
252 // it after checking permission. | |
253 // TODO(grunell): It would be good to change this result for something else, | |
254 // probably a new one. | |
255 content::MediaStreamRequestResult result = | |
256 content::MEDIA_DEVICE_INVALID_STATE; | |
257 | |
258 // Approve request only when the following conditions are met: | |
259 // 1. Screen capturing is enabled via command line switch or white-listed for | |
260 // the given origin. | |
261 // 2. Request comes from a page with a secure origin or from an extension. | |
262 if (screen_capture_enabled && origin_is_secure) { | |
263 // Get title of the calling application prior to showing the message box. | |
264 // chrome::ShowQuestionMessageBox() starts a nested message loop which may | |
265 // allow |web_contents| to be destroyed on the UI thread before the messag | |
266 // box is closed. See http://crbug.com/326690. | |
267 base::string16 application_title = | |
268 GetApplicationTitle(web_contents, extension); | |
269 #if !defined(OS_ANDROID) | |
270 gfx::NativeWindow parent_window = | |
271 FindParentWindowForWebContents(web_contents); | |
272 #else | |
273 gfx::NativeWindow parent_window = NULL; | |
274 #endif | |
275 web_contents = NULL; | |
276 | |
277 bool whitelisted_extension = | |
278 IsExtensionWhitelistedForScreenCapture(extension); | |
279 | |
280 // For whitelisted or component extensions, bypass message box. | |
281 bool user_approved = false; | |
282 if (!whitelisted_extension && !component_extension) { | |
283 base::string16 application_name = | |
284 base::UTF8ToUTF16(request.security_origin.spec()); | |
285 if (extension) | |
286 application_name = base::UTF8ToUTF16(extension->name()); | |
287 base::string16 confirmation_text = l10n_util::GetStringFUTF16( | |
288 request.audio_type == content::MEDIA_NO_SERVICE | |
289 ? IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT | |
290 : IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT, | |
291 application_name); | |
292 chrome::MessageBoxResult result = chrome::ShowQuestionMessageBox( | |
293 parent_window, | |
294 l10n_util::GetStringFUTF16( | |
295 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name), | |
296 confirmation_text); | |
297 user_approved = (result == chrome::MESSAGE_BOX_RESULT_YES); | |
298 } | |
299 | |
300 if (user_approved || component_extension || whitelisted_extension) { | |
301 content::DesktopMediaID screen_id; | |
302 #if defined(OS_CHROMEOS) | |
303 screen_id = content::DesktopMediaID::RegisterAuraWindow( | |
304 content::DesktopMediaID::TYPE_SCREEN, | |
305 ash::Shell::GetInstance()->GetPrimaryRootWindow()); | |
306 #else // defined(OS_CHROMEOS) | |
307 screen_id = content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, | |
308 webrtc::kFullDesktopScreenId); | |
309 #endif // !defined(OS_CHROMEOS) | |
310 | |
311 bool capture_audio = | |
312 (request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE && | |
313 loopback_audio_supported); | |
314 | |
315 // Unless we're being invoked from a component extension, register to | |
316 // display the notification for stream capture. | |
317 bool display_notification = !component_extension; | |
318 | |
319 ui = GetDevicesForDesktopCapture(&devices, screen_id, capture_audio, true, | |
320 display_notification, application_title, | |
321 application_title); | |
322 DCHECK(!devices.empty()); | |
323 } | |
324 | |
325 // The only case when devices can be empty is if the user has denied | |
326 // permission. | |
327 result = devices.empty() ? content::MEDIA_DEVICE_PERMISSION_DENIED | |
328 : content::MEDIA_DEVICE_OK; | |
329 } | |
330 | |
331 callback.Run(devices, result, std::move(ui)); | |
332 } | |
333 | |
334 bool DesktopCaptureAccessHandler::SupportsStreamType( | |
335 const content::MediaStreamType type, | |
336 const extensions::Extension* extension) { | |
337 return type == content::MEDIA_DESKTOP_VIDEO_CAPTURE || | |
338 type == content::MEDIA_DESKTOP_AUDIO_CAPTURE; | |
339 } | |
340 | |
341 bool DesktopCaptureAccessHandler::CheckMediaAccessPermission( | |
342 content::WebContents* web_contents, | |
343 const GURL& security_origin, | |
344 content::MediaStreamType type, | |
345 const extensions::Extension* extension) { | |
346 return false; | |
347 } | |
348 | |
349 void DesktopCaptureAccessHandler::HandleRequest( | |
350 content::WebContents* web_contents, | |
351 const content::MediaStreamRequest& request, | |
352 const content::MediaResponseCallback& callback, | |
353 const extensions::Extension* extension) { | |
354 content::MediaStreamDevices devices; | |
355 std::unique_ptr<content::MediaStreamUI> ui; | |
356 | |
357 if (request.video_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE) { | |
358 callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, std::move(ui)); | |
359 return; | |
360 } | |
361 | |
362 // If the device id wasn't specified then this is a screen capture request | |
363 // (i.e. chooseDesktopMedia() API wasn't used to generate device id). | |
364 if (request.requested_video_device_id.empty()) { | |
365 ProcessScreenCaptureAccessRequest(web_contents, request, callback, | |
366 extension); | |
367 return; | |
368 } | |
369 | |
370 // The extension name that the stream is registered with. | |
371 std::string original_extension_name; | |
372 // Resolve DesktopMediaID for the specified device id. | |
373 content::DesktopMediaID media_id; | |
374 // TODO(miu): Replace "main RenderFrame" IDs with the request's actual | |
375 // RenderFrame IDs once the desktop capture extension API implementation is | |
376 // fixed. http://crbug.com/304341 | |
377 content::WebContents* const web_contents_for_stream = | |
378 content::WebContents::FromRenderFrameHost( | |
379 content::RenderFrameHost::FromID(request.render_process_id, | |
380 request.render_frame_id)); | |
381 content::RenderFrameHost* const main_frame = | |
382 web_contents_for_stream ? web_contents_for_stream->GetMainFrame() : NULL; | |
383 if (main_frame) { | |
384 media_id = MediaCaptureDevicesDispatcher::GetInstance() | |
385 ->GetDesktopStreamsRegistry() | |
386 ->RequestMediaForStreamId(request.requested_video_device_id, | |
387 main_frame->GetProcess()->GetID(), | |
388 main_frame->GetRoutingID(), | |
389 request.security_origin, | |
390 &original_extension_name); | |
391 } | |
392 | |
393 // Received invalid device id. | |
394 if (media_id.type == content::DesktopMediaID::TYPE_NONE) { | |
395 callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, std::move(ui)); | |
396 return; | |
397 } | |
398 | |
399 bool loopback_audio_supported = false; | |
400 #if defined(USE_CRAS) || defined(OS_WIN) | |
401 // Currently loopback audio capture is supported only on Windows and ChromeOS. | |
402 loopback_audio_supported = true; | |
403 #endif | |
404 | |
405 // This value essentially from the checkbox on picker window, so it | |
406 // corresponds to user permission. | |
407 const bool audio_permitted = media_id.audio_share; | |
408 | |
409 // This value essentially from whether getUserMedia requests audio stream. | |
410 const bool audio_requested = | |
411 request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE; | |
412 | |
413 // This value shows for a given capture type, whether the system or our code | |
414 // can support audio sharing. Currently audio is only supported for screen and | |
415 // tab/webcontents capture streams. | |
416 const bool audio_supported = | |
417 (media_id.type == content::DesktopMediaID::TYPE_SCREEN && | |
418 loopback_audio_supported) || | |
419 media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS; | |
420 | |
421 const bool check_audio_permission = | |
422 !base::CommandLine::ForCurrentProcess()->HasSwitch( | |
423 extensions::switches::kDisableDesktopCaptureAudio); | |
424 const bool capture_audio = | |
425 (check_audio_permission ? audio_permitted : true) && audio_requested && | |
426 audio_supported; | |
427 | |
428 ui = GetDevicesForDesktopCapture(&devices, media_id, capture_audio, false, | |
429 true, | |
430 GetApplicationTitle(web_contents, extension), | |
431 base::UTF8ToUTF16(original_extension_name)); | |
432 UpdateExtensionTrusted(request, extension); | |
433 callback.Run(devices, content::MEDIA_DEVICE_OK, std::move(ui)); | |
434 } | |
435 | |
OLD | NEW |