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

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

Powered by Google App Engine
This is Rietveld 408576698