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