Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(285)

Side by Side Diff: chrome/browser/media/desktop_capture_access_handler.cc

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

Powered by Google App Engine
This is Rietveld 408576698